[Entwurfsmuster und Paradigmen: Verhalten] 69 | Besuchermodus (Teil 2): Warum benötigt eine Sprache, die Double Dispatch unterstützt, keinen Besuchermodus?

Im letzten Kurs lernten wir das Prinzip und die Umsetzung des Besuchermusters kennen und stellten den Denkprozess der Entstehung des Besuchermusters wieder her. Im Allgemeinen ist die Code-Implementierung dieses Modus relativ schwierig, sodass es nicht viele Anwendungsszenarien gibt. Aus Sicht der Anwendungsentwicklung steht dies tatsächlich nicht im Mittelpunkt unserer Studie.

Allerdings haben wir bereits mehrfach darauf hingewiesen, dass das Studium meiner Kolumne nicht nur dazu dient, sich Wissen anzueignen, sondern, was noch wichtiger ist, Ihre Fähigkeit zur Problemanalyse und -lösung sowie Ihr logisches Denkvermögen zu trainieren. Deshalb machen wir heute damit weiter Verwenden Sie den Besuchermodus. Lassen Sie uns zunächst diese beiden Themen gemeinsam besprechen und hoffen, Sie zum Nachdenken anzuregen:

  • Warum benötigt eine Sprache, die Double Dispatch unterstützt, nicht das Visitor-Muster?
  • Gibt es neben dem Besuchermuster noch eine andere Möglichkeit, das Beispiel aus der vorherigen Lektion umzusetzen?

Beginnen wir ohne weitere Umschweife offiziell mit dem heutigen Studium!

Warum benötigt eine Sprache, die Double Dispatch unterstützt, nicht das Visitor-Muster?

Wenn es um das Besuchermuster geht, sprechen die meisten Bücher oder Materialien tatsächlich von „Double Dispatch“, was auf Chinesisch „Double Dispatch“ bedeutet. Obwohl es nicht notwendig ist, dieses Konzept zu verstehen, um den Besuchermodus zu erlernen, haben wir es in der vorherigen Erklärung nicht erwähnt, aber um zu verhindern, dass Sie beim Betrachten anderer Bücher oder Materialien bei diesem Konzept hängen bleiben, halte ich es für notwendig Lass uns hier darüber reden.

Darüber hinaus denke ich, dass das Erlernen von Double Dispatch Ihr Verständnis des Besuchermusters vertiefen und Ihnen auch dabei helfen kann, die Frage im Titel des heutigen Artikels herauszufinden: Warum benötigen Sprachen, die Double Dispatch unterstützen, das Besuchermuster nicht? ? ? Diese Frage wird im Vorstellungsgespräch gestellt!

Da es einen Double Dispatch gibt, gibt es auch einen entsprechenden Single Dispatch. Der sogenannte Single Dispatch bezieht sich auf die Methode, von der das Objekt ausgeführt wird. Dies hängt vom Laufzeittyp des Objekts ab. Welche Methode des Objekts ausgeführt werden soll, hängt vom Kompilierungszeittyp des Methodenparameters ab. Der sogenannte Double Dispatch bezieht sich auf die Methode, von der das Objekt ausgeführt wird, die anhand des Laufzeittyps des Objekts bestimmt wird; welche Methode des Objekts ausgeführt werden soll, wird anhand des Laufzeittyps des Methodenparameters bestimmt.

Wie ist das Wort „Versand“ zu verstehen? In objektorientierten Programmiersprachen können wir Methodenaufrufe als eine Art Nachrichtenübermittlung verstehen, die „Dispatch“ ist. Wenn ein Objekt eine Methode eines anderen Objekts aufruft, entspricht dies dem Senden einer Nachricht an dieses Objekt. Diese Nachricht muss mindestens den Objektnamen, den Methodennamen und die Methodenparameter enthalten.

Wie versteht man die Wörter „Single“ und „Double“? „Single“ und „Double“ beziehen sich darauf, welche Methode welches Objekt ausführt, was mit dem Laufzeittyp mehrerer Faktoren zusammenhängt. Lassen Sie uns das näher erklären. Der Grund, warum Single Dispatch „Single“ genannt wird, liegt darin, dass es nur auf den Laufzeittyp des „Objekts“ ankommt, welche Methode welches Objekts ausführt. Double Dispatch wird „Double“ genannt, da es vom Laufzeittyp von „Objekt“ und „Methodenparameter“ abhängt, welche Methode von welchem ​​Objekt ausgeführt wird.

Speziell für den Syntaxmechanismus von Programmiersprachen stehen Single Dispatch und Double Dispatch in direktem Zusammenhang mit Polymorphismus und Funktionsüberladung. Die aktuellen gängigen objektorientierten Programmiersprachen (z. B. Java, C++, C#) unterstützen nur Single Dispatch, nicht Double Dispatch.

Nehmen wir als nächstes die Java-Sprache als Beispiel.

Java unterstützt Polymorphismus. Der Code kann den tatsächlichen Typ des Objekts zur Laufzeit abrufen (dh den oben genannten Laufzeittyp) und dann basierend auf dem tatsächlichen Typ entscheiden, welche Methode aufgerufen werden soll. Obwohl Java die Funktionsüberladung unterstützt, besteht die von Java entwickelte grammatikalische Regel der Funktionsüberladung nicht darin, zu entscheiden, welche überladene Funktion aufgerufen werden soll, basierend auf dem tatsächlichen Typ des zur Laufzeit an die Funktion übergebenen Parameters, sondern zur Kompilierungszeit gemäß Der deklarierte Typ von Der an die Funktion übergebene Parameter (d. h. der oben erwähnte Typ zur Kompilierungszeit) bestimmt, welche überladene Funktion aufgerufen werden soll. Das heißt, welche Methode welches Objekts ausführt, hängt nur vom Laufzeittyp des Objekts ab und hat nichts mit dem Laufzeittyp des Parameters zu tun. Daher unterstützt die Java-Sprache nur Single Dispatch.

Dies ist abstrakter. Lassen Sie mich ein Beispiel geben, um es konkret zu erklären. Der Code lautet wie folgt:

public class ParentClass {
  public void f() {
    System.out.println("I am ParentClass's f().");
  }
}
public class ChildClass extends ParentClass {
  public void f() {
    System.out.println("I am ChildClass's f().");
  }
}
public class SingleDispatchClass {
  public void polymorphismFunction(ParentClass p) {
    p.f();
  }
  public void overloadFunction(ParentClass p) {
    System.out.println("I am overloadFunction(ParentClass p).");
  }
  public void overloadFunction(ChildClass c) {
    System.out.println("I am overloadFunction(ChildClass c).");
  }
}
public class DemoMain {
  public static void main(String[] args) {
    SingleDispatchClass demo = new SingleDispatchClass();
    ParentClass p = new ChildClass();
    demo.polymorphismFunction(p);//执行哪个对象的方法,由对象的实际类型决定
    demo.overloadFunction(p);//执行对象的哪个方法,由参数对象的声明类型决定
  }
}
//代码执行结果:
I am ChildClass's f().
I am overloadFunction(ParentClass p).

Im obigen Code führt die Funktion polymorphismFunction() in Zeile 31 die f()-Funktion des tatsächlichen Typs von p aus, also die f()-Funktion von ChildClass. Die Funktion „overloadFunction()“ in der 32. Codezeile stimmt mit der Funktion „overloadFunction(ParentClass p)“ in der überladenen Funktion überein, d. h. sie entscheidet, welche überladene Funktion entsprechend dem deklarierten Typ von p abgeglichen werden soll.

Unter der Annahme, dass die Java-Sprache Double Dispatch unterstützt, wird in Zeile 37 im folgenden Code (Auszug aus dem zweiten Code in der vorherigen Lektion, es wird empfohlen, ihn in Verbindung mit der Erklärung in der vorherigen Lektion zu verstehen) kein Fehler gemeldet. Der Code entscheidet anhand des tatsächlichen Typs (PdfFile, PPTFile, WordFile) des Parameters (resourceFile) zur Laufzeit, welche der drei überladenen Funktionen von extract2txt verwendet werden soll. Dann kann die folgende Codeimplementierung normal ausgeführt werden und es ist kein Besuchermodus erforderlich. Dies beantwortet auch, warum Sprachen, die Double Dispatch unterstützen, das Besuchermuster nicht benötigen.

public abstract class ResourceFile {
  protected String filePath;
  public ResourceFile(String filePath) {
    this.filePath = filePath;
  }
}
public class PdfFile extends ResourceFile {
  public PdfFile(String filePath) {
    super(filePath);
  }
  //...
}
//...PPTFile、WordFile代码省略...
public class Extractor {
  public void extract2txt(PPTFile pptFile) {
    //...
    System.out.println("Extract PPT.");
  }
  public void extract2txt(PdfFile pdfFile) {
    //...
    System.out.println("Extract PDF.");
  }
  public void extract2txt(WordFile wordFile) {
    //...
    System.out.println("Extract WORD.");
  }
}
public class ToolApplication {
  public static void main(String[] args) {
    Extractor extractor = new Extractor();
    List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);
    for (ResourceFile resourceFile : resourceFiles) {
      extractor.extract2txt(resourceFile);
    }
  }
  private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) {
    List<ResourceFile> resourceFiles = new ArrayList<>();
    //...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile)
    resourceFiles.add(new PdfFile("a.pdf"));
    resourceFiles.add(new WordFile("b.word"));
    resourceFiles.add(new PPTFile("c.ppt"));
    return resourceFiles;
  }
}

Gibt es neben dem Besuchermuster noch andere Implementierungen für das Beispiel im vorherigen Abschnitt?

Im letzten Kurs habe ich Ihnen anhand eines Beispiels gezeigt, wie das Besuchermuster Schritt für Schritt gestaltet wird. Sehen wir uns dieses Beispiel noch einmal gemeinsam an. Wir haben viele Ressourcendateien von der Website gecrawlt, und es gibt drei Formate davon: PDF, PPT, Word. Wir müssen ein Tool entwickeln, um diesen Stapel von Ressourcendateien zu verarbeiten. Dazu gehören das Extrahieren von Textinhalten, das Komprimieren von Ressourcendateien, das Extrahieren von Dateimetainformationen usw.

Tatsächlich gibt es viele Code-Design- und Implementierungsideen für die Entwicklung dieses Tools. Um das Besuchermuster zu erklären, haben wir uns entschieden, es mit dem Besuchermuster der letzten Lektion umzusetzen. Tatsächlich haben wir andere Implementierungsmethoden. Beispielsweise können wir das Factory-Muster auch verwenden, um eine Extractor-Schnittstelle zu definieren, die die Schnittstellenfunktion extract2txt() enthält. Die Klassen PdfExtractor, PPTExtractor und WordExtractor implementieren die Extractor-Schnittstelle und implementieren die Textinhaltsextraktion von PDF-, PPT- und Word-Formatdateien in ihren jeweiligen extract2txt()-Funktionen. Die Factory-Klasse ExtractorFactory gibt je nach Dateityp unterschiedliche Extraktoren zurück.

Diese Implementierungsidee ist tatsächlich einfacher. Schauen wir uns den Code direkt an.

public abstract class ResourceFile {
  protected String filePath;
  public ResourceFile(String filePath) {
    this.filePath = filePath;
  }
  public abstract ResourceFileType getType();
}
public class PdfFile extends ResourceFile {
  public PdfFile(String filePath) {
    super(filePath);
  }
  @Override
  public ResourceFileType getType() {
    return ResourceFileType.PDF;
  }
  //...
}
//...PPTFile/WordFile跟PdfFile代码结构类似,此处省略...
public interface Extractor {
  void extract2txt(ResourceFile resourceFile);
}
public class PdfExtractor implements Extractor {
  @Override
  public void extract2txt(ResourceFile resourceFile) {
    //...
  }
}
//...PPTExtractor/WordExtractor跟PdfExtractor代码结构类似,此处省略...
public class ExtractorFactory {
  private static final Map<ResourceFileType, Extractor> extractors = new HashMap<>();
  static {
    extractors.put(ResourceFileType.PDF, new PdfExtractor());
    extractors.put(ResourceFileType.PPT, new PPTExtractor());
    extractors.put(ResourceFileType.WORD, new WordExtractor());
  }
  public static Extractor getExtractor(ResourceFileType type) {
    return extractors.get(type);
  }
}
public class ToolApplication {
  public static void main(String[] args) {
    List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);
    for (ResourceFile resourceFile : resourceFiles) {
      Extractor extractor = ExtractorFactory.getExtractor(resourceFile.getType());
      extractor.extract2txt(resourceFile);
    }
  }
  private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) {
    List<ResourceFile> resourceFiles = new ArrayList<>();
    //...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile)
    resourceFiles.add(new PdfFile("a.pdf"));
    resourceFiles.add(new WordFile("b.word"));
    resourceFiles.add(new PPTFile("c.ppt"));
    return resourceFiles;
  }
}

Wenn neue Funktionen hinzugefügt werden müssen, z. B. das Komprimieren von Ressourcendateien oder eine Codeimplementierung ähnlich der Funktion zum Extrahieren von Textinhalten, müssen wir lediglich eine Kompressorschnittstelle, drei Implementierungsklassen PdfCompressor, PPTCompressor, WordCompressor und die Factory-Klasse CompressorFactory hinzufügen um sie zu erschaffen. . Das Einzige, was geändert werden muss, ist die ToolApplication-Klasse der obersten Ebene. Es entspricht im Wesentlichen dem Designprinzip „Offen für Erweiterung, geschlossen für Änderung“.
Wenn das Tool am Beispiel eines Ressourcendateiverarbeitungstools nicht viele, sondern nur wenige Funktionen bietet, empfehle ich die Verwendung der Factory-Mode-Implementierung, da der Code schließlich klarer und verständlicher ist. Im Gegenteil, wenn das Tool viele Funktionen bereitstellt, beispielsweise mehr als ein Dutzend, dann empfehle ich die Verwendung des Besuchermodus, da der Besuchermodus viel weniger Klassen definieren muss als die Factory-Modus-Implementierung, und zu viele Klassen auch Auswirkungen auf die Wartbarkeit des Codes haben.

Schlüsselrezension

Nun, das ist alles für heute. Lassen Sie uns gemeinsam zusammenfassen und überprüfen, worauf Sie sich konzentrieren müssen.

Im Allgemeinen ist der Besuchermodus schwer zu verstehen, die Anwendungsszenarien sind begrenzt und er ist nicht unbedingt erforderlich. Ich empfehle nicht, ihn im Projekt zu verwenden. Daher empfehle ich für das Beispiel der Verarbeitung von Ressourcendateien in der letzten Lektion, das Factory-Muster für Entwurf und Implementierung zu verwenden.

Darüber hinaus haben wir uns heute auf Double Dispatch konzentriert. In einer objektorientierten Programmiersprache kann ein Methodenaufruf als Nachrichtenübermittlung (Dispatch) verstanden werden. Wenn ein Objekt eine Methode eines anderen Objekts aufruft, entspricht dies dem Senden einer Nachricht an dieses. Diese Nachricht muss mindestens den Objektnamen, den Methodennamen und die Methodenparameter enthalten.

Der sogenannte Single Dispatch bezieht sich auf die Methode, von der das Objekt ausgeführt wird. Dies hängt vom Laufzeittyp des Objekts ab. Welche Methode des Objekts ausgeführt werden soll, hängt vom Kompilierungszeittyp des Methodenparameters ab. Der sogenannte Double Dispatch bezieht sich auf die Methode, von der das Objekt ausgeführt wird, die anhand des Laufzeittyps des Objekts bestimmt wird; welche Methode des Objekts ausgeführt werden soll, wird anhand des Laufzeittyps des Methodenparameters bestimmt.

Speziell für den Syntaxmechanismus von Programmiersprachen stehen Single Dispatch und Double Dispatch in direktem Zusammenhang mit Polymorphismus und Funktionsüberladung. Die aktuellen gängigen objektorientierten Programmiersprachen (z. B. Java, C++, C#) unterstützen nur Single Dispatch, nicht Double Dispatch.

Klassendiskussion

  • Trennt das Besuchermuster Operationen von Objekten, verstößt es gegen objektorientierte Designprinzipien? Was denken Sie über dieses Problem?

  • Wenn wir im Codebeispiel zur Erläuterung von Single Dispatch den Code von SingleDispatchClass wie folgt ändern und andere Codes unverändert lassen, was wird dann die Ausgabe von DemoMain sein? Warum ist das das Ergebnis?

public class SingleDispatchClass {
  public void polymorphismFunction(ParentClass p) {
    p.f();
  }
  public void overloadFunction(ParentClass p) {
    p.f();
  }
  public void overloadFunction(ChildClass c) {
    c.f();
  }
}

Guess you like

Origin blog.csdn.net/qq_32907491/article/details/131275408