Design patterns and paradigms: behavioral

Visitor mode

  • The English translation of visitor pattern is Visitor Design Pattern. In GoF's book design patterns, it is defined as: allow for one or more operations to be applied to a set of objects at runtime, decoupling the operations from the object structure.

The principle and code implementation of visitor pattern

  • Visitor mode allows one or more operations to be applied to a group of objects. The design intention is to decouple the operation and the object itself, keep the class responsibility single, meet the opening and closing principle and deal with the complexity of the code.
  • For visitor mode, the main difficulty of learning is code implementation. The main reason for the complexity of code implementation is that function overloading is statically bound in most object-oriented programming languages. That is, which overloaded function of the class to call is determined by the declared type of the parameter during compilation, rather than the actual type of the parameter at run time.
  • Implementation code of standard visitor mode:
public abstract class ResourceFile {
  protected String filePath;
  public ResourceFile(String filePath) {
    this.filePath = filePath;
  }
  abstract public void accept(Visitor vistor);
}
public class PdfFile extends ResourceFile {
  public PdfFile(String filePath) {
    super(filePath);
  }
  @Override
  public void accept(Visitor visitor) {
    visitor.visit(this);
  }
  //...
}
//... PPTFile and WordFile are similar to PdfFile, and are omitted here
public interface Visitor {
  void visit(PdfFile pdfFile);
  void visit(PPTFile pdfFile);
  void visit(WordFile pdfFile);
}
public class Extractor implements Visitor {
  @Override
  public void visit(PPTFile pptFile) {
    //...
    System.out.println("Extract PPT.");
  }
  @Override
  public void visit(PdfFile pdfFile) {
    //...
    System.out.println("Extract PDF.");
  }
  @Override
  public void visit(WordFile wordFile) {
    //...
    System.out.println("Extract WORD.");
  }
}
public class Compressor implements Visitor {
  @Override
  public void visit(PPTFile pptFile) {
    //...
    System.out.println("Compress PPT.");
  }
  @Override
  public void visit(PdfFile pdfFile) {
    //...
    System.out.println("Compress PDF.");
  }
  @Override
  public void visit(WordFile wordFile) {
    //...
    System.out.println("Compress WORD.");
  }
}
public class ToolApplication {
  public static void main(String[] args) {
    Extractor extractor = new Extractor();
    List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);
    for (ResourceFile resourceFile : resourceFiles) {
      resourceFile.accept(extractor);
    }
    Compressor compressor = new Compressor();
    for(ResourceFile resourceFile : resourceFiles) {
      resourceFile.accept(compressor);
    }
  }
  private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) {
    List<ResourceFile> resourceFiles = new ArrayList<>();
    //... create different class objects (PdfFile/PPTFile/WordFile) by factory methods according to the suffix (pdf/ppt/word)
    resourceFiles.add(new PdfFile("a.pdf"));
    resourceFiles.add(new WordFile("b.word"));
    resourceFiles.add(new PPTFile("c.ppt"));
    return resourceFiles;
  }
}

Application scenario of visitor mode

  • The visitor pattern is for a set of objects of different types (PdfFile, PPTFile, WordFile). However, although the types of these objects are different, they inherit the same parent class (ResourceFile) or implement the same interface. In different application scenarios, we need to perform a series of irrelevant business operations (text extraction, compression, etc.) on this group of objects. However, in order to avoid the continuous expansion of classes (PdfFile, PPTFile, WordFile) caused by adding functions, more and more different responsibilities, and avoid frequent code modifications caused by frequent adding functions, we use the visitor mode, Decouple objects from operations, pull out these business operations and define them in independently subdivided visitor classes (Extractor and Compressor).

Why do languages that support double dispatch not need visitor mode?

  • The so-called Single Dispatch refers to the method of which object to execute, which is determined according to the runtime type of the object; Which method of the object to execute depends on the compile time type of the method parameter. The so-called Double Dispatch refers to the method of which object to execute, which is determined according to the runtime type of the object; Which method of the object to execute depends on the runtime type of the method parameter.
    • Specific to the syntax mechanism of programming language, Single Dispatch and Double Dispatch are directly related to polymorphism and function overloading. The current mainstream object-oriented programming languages (such as Java, C + +, c#) only support Single Dispatch and do not support Double Dispatch.

Memo mode

  • Memo mode, also known as Snapshot mode, is translated into English as memo design pattern. In GoF's book design patterns, memo patterns are defined as follows: Captures and externalizes an object's internal state so that it can be restored later, all without breaching encapsulation, So that the object can be restored to its previous state later.

Principle and implementation of memo mode

  • The definition of memo mode mainly expresses two parts. In part, store replicas for later recovery. This part is easy to understand. The other part is to backup and restore objects without violating the encapsulation principle. This part is not easy to understand.
  • Code implementation of typical memo mode:
    • Suppose there is such an interview question. I hope you can write a small program that can receive command line input. When the user inputs text, the program will append and store it in the memory text; The user inputs ": list", and the program outputs the contents of memory text in the command line; If the user enters ": undo", the program will undo the last text entered, that is, delete the last text from the memory text.
public class InputText {
  private StringBuilder text = new StringBuilder();
  public String getText() {
    return text.toString();
  }
  public void append(String input) {
    text.append(input);
  }
  public Snapshot createSnapshot() {
    return new Snapshot(text.toString());
  }
  public void restoreSnapshot(Snapshot snapshot) {
    this.text.replace(0, this.text.length(), snapshot.getText());
  }
}
public class Snapshot {
  private String text;
  public Snapshot(String text) {
    this.text = text;
  }
  public String getText() {
    return this.text;
  }
}
public class SnapshotHolder {
  private Stack<Snapshot> snapshots = new Stack<>();
  public Snapshot popSnapshot() {
    return snapshots.pop();
  }
  public void pushSnapshot(Snapshot snapshot) {
    snapshots.push(snapshot);
  }
}
public class ApplicationMain {
  public static void main(String[] args) {
    InputText inputText = new InputText();
    SnapshotHolder snapshotsHolder = new SnapshotHolder();
    Scanner scanner = new Scanner(System.in);
    while (scanner.hasNext()) {
      String input = scanner.next();
      if (input.equals(":list")) {
        System.out.println(inputText.toString());
      } else if (input.equals(":undo")) {
        Snapshot snapshot = snapshotsHolder.popSnapshot();
        inputText.restoreSnapshot(snapshot);
      } else {
        snapshotsHolder.pushSnapshot(inputText.createSnapshot());
        inputText.append(input);
      }
    }
  }
}
  • What is the difference and relationship between memo mode and "backup"? In fact, the application scenarios of the two are very similar, both of which are used in loss prevention, recovery, revocation and other scenarios. The difference between them is that memo mode focuses more on code design and implementation, and backup focuses more on architecture design or product design.

How to optimize memory and time consumption?

  • Suppose that whenever there are data changes, we need to generate a backup for later recovery. If the data to be backed up is large, such a high-frequency backup, whether it is the consumption of storage (memory or hard disk) or time, may be unacceptable. To solve this problem, we usually use the combination of "low frequency full backup" and "high frequency incremental backup".
    • When we need to restore the backup to a certain point in time, if we do a full backup at this point in time, we can restore it directly. If there is no corresponding full backup at this time point, we will first find the latest full backup, then use it to restore, and then perform all incremental backups between this full backup and this time point, that is, corresponding operations or data changes. This can reduce the number and frequency of full backup and reduce the consumption of time and memory.
  • For the backup of large objects, the storage space occupied by backup will be large, and the time-consuming of backup and recovery will be long. To solve this problem, different business scenarios have different processing methods. For example, only the necessary recovery information is backed up and restored in combination with the latest data; Another example is the combination of full backup and incremental backup, low-frequency full backup and high-frequency incremental backup.

The more you know, the more you don't know.

Tags: Design Pattern

Posted on Tue, 09 Nov 2021 05:10:28 -0500 by Moharo