【設(shè)計(jì)模式】行為型設(shè)計(jì)模式匯總(二)

行為型設(shè)計(jì)模式范圍

  1. 觀察者模式
  2. 模板方法
  3. 策略模式
  4. 職責(zé)鏈模式
  5. 狀態(tài)模式
  6. 迭代器模式
  7. 訪問者模式
  8. 備忘錄模式
  9. 命令模式
  10. 解釋器模式
  11. 中介模式

行為型設(shè)計(jì)模式作用

行為型設(shè)計(jì)模式主要關(guān)注的是類與類之間的交互問題。

7. 訪問者模式

7.1 定義

允許一個(gè)或多個(gè)操作應(yīng)用到一組對(duì)象上,解耦操作和對(duì)象本身。

7.2 作用

  1. 解耦操作和對(duì)象本身,使得操作和對(duì)象本身都可以單獨(dú)擴(kuò)展,且滿足職責(zé)單一、開閉原則等設(shè)計(jì)思想和原則
  2. 變向支持雙分派實(shí)現(xiàn),即調(diào)用哪個(gè)對(duì)象的哪個(gè)方法都可以在運(yùn)行時(shí)決定

7.3 類結(jié)構(gòu)圖

image

7.4 經(jīng)典實(shí)現(xiàn)

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、WordFile跟PdfFile類似,這里就省略了...

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<>();
    //...根據(jù)后綴(pdf/ppt/word)由工廠方法創(chuàng)建不同的類對(duì)象(PdfFile/PPTFile/WordFile)
    resourceFiles.add(new PdfFile("a.pdf"));
    resourceFiles.add(new WordFile("b.word"));
    resourceFiles.add(new PPTFile("c.ppt"));
    return resourceFiles;
  }
}

具體實(shí)現(xiàn):

  1. 定義 Visotor 接口,根據(jù)不同的功能實(shí)現(xiàn) Visotor 接口
  2. 在使用 Visotor 提供功能的類中通過 accept(Visitor visitor) 接收這個(gè) Visotor 對(duì)象的函數(shù)注入操作
  3. 通過調(diào)用 accept(visitor) 方法來將當(dāng)前對(duì)象通過 this 傳入到 Visitor 中并進(jìn)行處理

擴(kuò)展問題

當(dāng)需要擴(kuò)展功能時(shí),只需要實(shí)現(xiàn) Visotor 接口,并實(shí)現(xiàn)新的功能邏輯,并在最終調(diào)用方創(chuàng)建新的 Visotor 對(duì)象并傳入到待處理的業(yè)務(wù)類中即可。

不需要改動(dòng)待處理的業(yè)務(wù)類中的邏輯。

7.5 多態(tài)和函數(shù)重載的區(qū)別

多態(tài)是一種動(dòng)態(tài)綁定,也就是在運(yùn)行時(shí)獲取到對(duì)象的實(shí)現(xiàn)類型。

而重載是一種靜態(tài)綁定,在代碼編譯的過程中,并不能獲取對(duì)象的實(shí)現(xiàn)類型,只能根據(jù)聲明類型執(zhí)行類型對(duì)象的方法。

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<>();
    //...根據(jù)后綴(pdf/ppt/word)由工廠方法創(chuàng)建不同的類對(duì)象(PdfFile/PPTFile/WordFile)
    resourceFiles.add(new PdfFile("a.pdf"));
    resourceFiles.add(new WordFile("b.word"));
    resourceFiles.add(new PPTFile("c.ppt"));
    return resourceFiles;
  }
}

由于Extractor 類中定義了幾個(gè)重載方法,分別接收繼承了同一個(gè)接口的不同實(shí)現(xiàn)的類。當(dāng)在使用過程中,傳入接口聲明類型給方法時(shí),由于沒有實(shí)現(xiàn)參數(shù)為該接口的函數(shù),導(dǎo)致 無法匹配到任意函數(shù),而使代碼無法通過編譯。

7.6 應(yīng)用場(chǎng)景

一般來說,訪問者模式針對(duì)繼承或?qū)崿F(xiàn)了同一個(gè)類或接口的不同對(duì)象(PdfFile、PPTFile 和 WordFile)進(jìn)行一系列不相關(guān)的業(yè)務(wù)操作。將業(yè)務(wù)操作抽離出來,定義在獨(dú)立細(xì)分的訪問者類中,并通過組合的方式,將業(yè)務(wù)操作類與對(duì)象本身解耦。

7.7 什么是單分派(Single Dispatch)和雙分派(Double Dispatch)

單分派

執(zhí)行哪個(gè)對(duì)象的方法,根據(jù)對(duì)象的運(yùn)行時(shí)類型決定;而執(zhí)行對(duì)象的哪個(gè)方法,根據(jù)方法參數(shù)的編譯時(shí)類型來決定。

雙分派

執(zhí)行哪個(gè)對(duì)象的方法,根據(jù)對(duì)象的運(yùn)行時(shí)類型決定;而執(zhí)行對(duì)象的哪個(gè)方法,也根據(jù)方法參數(shù)的運(yùn)行時(shí)類型來決定。

如何理解 Single 和 Double 這兩個(gè)詞

Single 說明執(zhí)行對(duì)象的哪個(gè)方法只跟對(duì)象的“運(yùn)行時(shí)”類型有關(guān);
Double 說明執(zhí)行對(duì)象的哪個(gè)方法跟對(duì)象和參數(shù)的“運(yùn)行時(shí)”類型兩者有關(guān)。

C++/JAVA 都只支持 Single Dispatch。

7.8 為什么支持 Double Dispatch 的語言不需要訪問者模式

由于 Double Dispatch 支持調(diào)用哪種對(duì)象的哪個(gè)方法,即可以通過對(duì)象的運(yùn)行時(shí)類型,決定調(diào)用哪個(gè)重載函數(shù)。那直接將對(duì)象本身傳遞給操作相關(guān)類中的重載函數(shù)即可執(zhí)行。無需將操作相關(guān)類反向注入到對(duì)象本身中,并通過 this 獲取當(dāng)前運(yùn)行時(shí)類型來完成根據(jù)運(yùn)行時(shí)類型調(diào)用重載函數(shù)的操作。

7.9 使用其它方式實(shí)現(xiàn)不同類型文件對(duì)象的多種操作

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代碼結(jié)構(gòu)類似,此處省略...

public interface Extractor {
  void extract2txt(ResourceFile resourceFile);
}

public class PdfExtractor implements Extractor {
  @Override
  public void extract2txt(ResourceFile resourceFile) {
    //...
  }
}

//...PPTExtractor/WordExtractor跟PdfExtractor代碼結(jié)構(gòu)類似,此處省略...

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<>();
    //...根據(jù)后綴(pdf/ppt/word)由工廠方法創(chuàng)建不同的類對(duì)象(PdfFile/PPTFile/WordFile)
    resourceFiles.add(new PdfFile("a.pdf"));
    resourceFiles.add(new WordFile("b.word"));
    resourceFiles.add(new PPTFile("c.ppt"));
    return resourceFiles;
  }
}

使用工廠類創(chuàng)建不同的操作對(duì)象,并根據(jù)不同類型的文件獲取不同的操作對(duì)象來完成一個(gè)或多個(gè)操作。

8. 備忘錄模式

8.1 定義

在不違反封裝原則的前提下,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài),以便之后恢復(fù)對(duì)象為之前的狀態(tài)。

8.2 作用

  1. 防丟失,快速撤銷、恢復(fù)數(shù)據(jù)。

8.3 類結(jié)構(gòu)圖

image

8.4 經(jīng)典實(shí)現(xiàn)

public class InputText {
  private StringBuilder text = new StringBuilder();

  public String getText() {
    return text.toString();
  }

  public void append(String input) {
    text.append(input);
  }

  public void setText(String text) {
    this.text.replace(0, this.text.length(), text);
  }
}

public class SnapshotHolder {
  private Stack<InputText> snapshots = new Stack<>();

  public InputText popSnapshot() {
    return snapshots.pop();
  }

  public void pushSnapshot(InputText inputText) {
    InputText deepClonedInputText = new InputText();
    deepClonedInputText.setText(inputText.getText());
    snapshots.push(deepClonedInputText);
  }
}

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.getText());
      } else if (input.equals(":undo")) {
        InputText snapshot = snapshotsHolder.popSnapshot();
        inputText.setText(snapshot.getText());
      } else {
        snapshotsHolder.pushSnapshot(inputText);
        inputText.append(input);
      }
    }
  }
}

實(shí)現(xiàn)存在問題

  1. InputText 快照類中中提供的 setText() 只是為了實(shí)現(xiàn)備忘錄模式而定義的一個(gè)函數(shù),而這個(gè)函數(shù)可能被其它業(yè)務(wù)調(diào)用,暴露了不該暴露的方法違背了封裝原則。
  2. 對(duì)于快照類來說,是不可變的,不應(yīng)該提供任何 set 函數(shù)。所以,InputText 這個(gè)快照類的設(shè)計(jì)違背了封裝原則。

改進(jìn)版本

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);
      }
    }
  }
}

創(chuàng)建新的 Snapshot 類來表示快照類,不提供任何修改快照狀態(tài)的函數(shù)。將 InputText 類的 setText() 方法改為 restoreSnapShot() 方法更為達(dá)意。

8.5 備忘錄模式和備份的區(qū)別

備忘錄模式更側(cè)重于代碼的設(shè)計(jì)和實(shí)現(xiàn),而備份側(cè)重于架構(gòu)設(shè)計(jì)或產(chǎn)品設(shè)計(jì)上。

8.6 如何優(yōu)化內(nèi)存和時(shí)間消耗

不同的應(yīng)用場(chǎng)景下有不同的解決方法。像上面的例子,我們只需要記錄每次變化后的數(shù)據(jù)長(zhǎng)度,結(jié)合 InputText 原始數(shù)據(jù)就可以完成撤銷和恢復(fù)的工作。

而對(duì)于備份的對(duì)象通常比較大,里面記錄了很多的狀態(tài)等信息,這個(gè)時(shí)候,需要采用“低頻全量更新、高頻增量更新的策略”。也就是在一段時(shí)間內(nèi)記錄只記錄每次變動(dòng)的增量信息,當(dāng)這段時(shí)間過后,對(duì)數(shù)據(jù)進(jìn)行一次全量備份,然后,再在一段時(shí)間內(nèi)記錄增量信息,循環(huán)往復(fù)這個(gè)過程來完成備份的操作。如果恢復(fù)數(shù)據(jù)呢?

通過要恢復(fù)的點(diǎn)往前找到最近一次全量備份的數(shù)據(jù),然后,再把增量部分的數(shù)據(jù)進(jìn)行累加,完成備份數(shù)據(jù)的恢復(fù)操作。

8.7 應(yīng)用場(chǎng)景

備忘錄模式主要是用來對(duì)數(shù)據(jù)進(jìn)行備份,防止數(shù)據(jù)丟失、撤銷和恢復(fù)數(shù)據(jù)等應(yīng)用場(chǎng)景。

9. 命令模式

9.1 定義

命令模式將請(qǐng)求(命令)封裝為一個(gè)對(duì)象,這樣可以使用不同的請(qǐng)求參數(shù)化其它對(duì)象(將不同請(qǐng)求依賴注入到其它對(duì)象),并且能夠支持請(qǐng)求(命令)的排隊(duì)執(zhí)行、記錄日志、撤銷等(附加控制)功能。

核心手段

將函數(shù)封裝成對(duì)象。借助命令模式我們可以將函數(shù)封裝成對(duì)象。

具體實(shí)現(xiàn)

設(shè)計(jì)一個(gè)包含函數(shù)的類,實(shí)例化一個(gè)對(duì)象進(jìn)行傳遞,這樣就近似地實(shí)現(xiàn)了把函數(shù)像對(duì)象一樣傳遞(主要是從設(shè)計(jì)意圖上來看)。從實(shí)現(xiàn)角度來看,類似于回調(diào)。

9.2 作用

  1. 控制命令的執(zhí)行,使用命令模式來模擬將函數(shù)像對(duì)象一樣傳遞,提高業(yè)務(wù)的易理解性及實(shí)現(xiàn)的靈活性。
  2. 封裝數(shù)據(jù)和數(shù)據(jù)處理邏輯到同一對(duì)象,提高了代碼的封裝性和易讀性。

9.3 類結(jié)構(gòu)圖

image

9.4 經(jīng)典實(shí)現(xiàn)

public interface Command {
  void execute();
}

public class GotDiamondCommand implements Command {
  // 省略成員變量

  public GotDiamondCommand(/*數(shù)據(jù)*/) {
    //...
  }

  @Override
  public void execute() {
    // 執(zhí)行相應(yīng)的邏輯
  }
}
//GotStartCommand/HitObstacleCommand/ArchiveCommand類省略

public class GameApplication {
  private static final int MAX_HANDLED_REQ_COUNT_PER_LOOP = 100;
  private Queue<Command> queue = new LinkedList<>();

  public void mainloop() {
    while (true) {
      List<Request> requests = new ArrayList<>();
      
      //省略從epoll或者select中獲取數(shù)據(jù),并封裝成Request的邏輯,
      //注意設(shè)置超時(shí)時(shí)間,如果很長(zhǎng)時(shí)間沒有接收到請(qǐng)求,就繼續(xù)下面的邏輯處理。
      
      for (Request request : requests) {
        Event event = request.getEvent();
        Command command = null;
        if (event.equals(Event.GOT_DIAMOND)) {
          command = new GotDiamondCommand(/*數(shù)據(jù)*/);
        } else if (event.equals(Event.GOT_STAR)) {
          command = new GotStartCommand(/*數(shù)據(jù)*/);
        } else if (event.equals(Event.HIT_OBSTACLE)) {
          command = new HitObstacleCommand(/*數(shù)據(jù)*/);
        } else if (event.equals(Event.ARCHIVE)) {
          command = new ArchiveCommand(/*數(shù)據(jù)*/);
        } // ...一堆else if...

        queue.add(command);
      }

      int handledCount = 0;
      while (handledCount < MAX_HANDLED_REQ_COUNT_PER_LOOP) {
        if (queue.isEmpty()) {
          break;
        }
        Command command = queue.poll();
        command.execute();
      }
    }
  }
}

9.5 設(shè)計(jì)模式兩個(gè)部分及不同設(shè)計(jì)模式的區(qū)分

第一部分為:應(yīng)用場(chǎng)景(設(shè)計(jì)意圖)。即這個(gè)模式可以解決哪類問題。

第二部分為:解決方案。即這個(gè)模式的設(shè)計(jì)思想和代碼實(shí)現(xiàn)。

設(shè)計(jì)模式的主要區(qū)別

不同設(shè)計(jì)模式的主要區(qū)別在于設(shè)計(jì)意圖,也就是應(yīng)用場(chǎng)景。

9.5 命令模式和策略模式的區(qū)別

在策略模式中,不同的策略具有相同的目的、不同的實(shí)現(xiàn)、互相之間可以替換。而命令模式中,不同的命令目的不同,對(duì)應(yīng)的處理邏輯也不一樣,無法相互替換。

9.6 應(yīng)用場(chǎng)景

用來控制命令的執(zhí)行。比如:異步、延遲、排隊(duì)執(zhí)行命令、撤銷重做命令、存儲(chǔ)命令等。

10. 解釋器模式

10.1 定義

解釋器模式為某個(gè)語言定義它的語法表示,并定義一個(gè)解釋器用來處理這些語法。

10.2 作用

  1. 通過定義語法和語法解釋器來應(yīng)對(duì)復(fù)雜的需求,提高代碼的復(fù)用性。
  2. 將語法解析的工作拆分到各個(gè)小類中,然后對(duì)每個(gè)語法單元進(jìn)行解析,最終合并為對(duì)整個(gè)語法規(guī)則進(jìn)行解析。

10.3 類結(jié)構(gòu)圖

image

10.4 經(jīng)典實(shí)現(xiàn)

public class ExpressionInterpreter {
  private Deque<Long> numbers = new LinkedList<>();

  public long interpret(String expression) {
    String[] elements = expression.split(" ");
    int length = elements.length;
    for (int i = 0; i < (length+1)/2; ++i) {
      numbers.addLast(Long.parseLong(elements[i]));
    }

    for (int i = (length+1)/2; i < length; ++i) {
      String operator = elements[i];
      boolean isValid = "+".equals(operator) || "-".equals(operator)
              || "*".equals(operator) || "/".equals(operator);
      if (!isValid) {
        throw new RuntimeException("Expression is invalid: " + expression);
      }

      long number1 = numbers.pollFirst();
      long number2 = numbers.pollFirst();
      long result = 0;
      if (operator.equals("+")) {
        result = number1 + number2;
      } else if (operator.equals("-")) {
        result = number1 - number2;
      } else if (operator.equals("*")) {
        result = number1 * number2;
      } else if (operator.equals("/")) {
        result = number1 / number2;
      }
      numbers.addFirst(result);
    }

    if (numbers.size() != 1) {
      throw new RuntimeException("Expression is invalid: " + expression);
    }

    return numbers.pop();
  }
}

解耦之后的版本

public interface Expression {
  long interpret();
}

public class NumberExpression implements Expression {
  private long number;

  public NumberExpression(long number) {
    this.number = number;
  }

  public NumberExpression(String number) {
    this.number = Long.parseLong(number);
  }

  @Override
  public long interpret() {
    return this.number;
  }
}

public class AdditionExpression implements Expression {
  private Expression exp1;
  private Expression exp2;

  public AdditionExpression(Expression exp1, Expression exp2) {
    this.exp1 = exp1;
    this.exp2 = exp2;
  }

  @Override
  public long interpret() {
    return exp1.interpret() + exp2.interpret();
  }
}
// SubstractionExpression/MultiplicationExpression/DivisionExpression與AdditionExpression代碼結(jié)構(gòu)類似,這里就省略了

public class ExpressionInterpreter {
  private Deque<Expression> numbers = new LinkedList<>();

  public long interpret(String expression) {
    String[] elements = expression.split(" ");
    int length = elements.length;
    for (int i = 0; i < (length+1)/2; ++i) {
      numbers.addLast(new NumberExpression(elements[i]));
    }

    for (int i = (length+1)/2; i < length; ++i) {
      String operator = elements[i];
      boolean isValid = "+".equals(operator) || "-".equals(operator)
              || "*".equals(operator) || "/".equals(operator);
      if (!isValid) {
        throw new RuntimeException("Expression is invalid: " + expression);
      }

      Expression exp1 = numbers.pollFirst();
      Expression exp2 = numbers.pollFirst();
      Expression combinedExp = null;
      if (operator.equals("+")) {
        combinedExp = new AdditionExpression(exp1, exp2);
      } else if (operator.equals("-")) {
        combinedExp = new AdditionExpression(exp1, exp2);
      } else if (operator.equals("*")) {
        combinedExp = new AdditionExpression(exp1, exp2);
      } else if (operator.equals("/")) {
        combinedExp = new AdditionExpression(exp1, exp2);
      }
      long result = combinedExp.interpret();
      numbers.addFirst(new NumberExpression(result));
    }

    if (numbers.size() != 1) {
      throw new RuntimeException("Expression is invalid: " + expression);
    }

    return numbers.pop().interpret();
  }
}

10.5 應(yīng)用場(chǎng)景

自定義接口告警規(guī)則功能

對(duì)自定義告警規(guī)則的語法規(guī)則進(jìn)行解析,并通過解析出來的結(jié)果進(jìn)行過濾,滿足條件的即觸發(fā)告警通知。

public interface Expression {
  boolean interpret(Map<String, Long> stats);
}

public class GreaterExpression implements Expression {
  private String key;
  private long value;

  public GreaterExpression(String strExpression) {
    String[] elements = strExpression.trim().split("\\s+");
    if (elements.length != 3 || !elements[1].trim().equals(">")) {
      throw new RuntimeException("Expression is invalid: " + strExpression);
    }
    this.key = elements[0].trim();
    this.value = Long.parseLong(elements[2].trim());
  }

  public GreaterExpression(String key, long value) {
    this.key = key;
    this.value = value;
  }

  @Override
  public boolean interpret(Map<String, Long> stats) {
    if (!stats.containsKey(key)) {
      return false;
    }
    long statValue = stats.get(key);
    return statValue > value;
  }
}

// LessExpression/EqualExpression跟GreaterExpression代碼類似,這里就省略了

public class AndExpression implements Expression {
  private List<Expression> expressions = new ArrayList<>();

  public AndExpression(String strAndExpression) {
    String[] strExpressions = strAndExpression.split("&&");
    for (String strExpr : strExpressions) {
      if (strExpr.contains(">")) {
        expressions.add(new GreaterExpression(strExpr));
      } else if (strExpr.contains("<")) {
        expressions.add(new LessExpression(strExpr));
      } else if (strExpr.contains("==")) {
        expressions.add(new EqualExpression(strExpr));
      } else {
        throw new RuntimeException("Expression is invalid: " + strAndExpression);
      }
    }
  }

  public AndExpression(List<Expression> expressions) {
    this.expressions.addAll(expressions);
  }

  @Override
  public boolean interpret(Map<String, Long> stats) {
    for (Expression expr : expressions) {
      if (!expr.interpret(stats)) {
        return false;
      }
    }
    return true;
  }

}

public class OrExpression implements Expression {
  private List<Expression> expressions = new ArrayList<>();

  public OrExpression(String strOrExpression) {
    String[] andExpressions = strOrExpression.split("\\|\\|");
    for (String andExpr : andExpressions) {
      expressions.add(new AndExpression(andExpr));
    }
  }

  public OrExpression(List<Expression> expressions) {
    this.expressions.addAll(expressions);
  }

  @Override
  public boolean interpret(Map<String, Long> stats) {
    for (Expression expr : expressions) {
      if (expr.interpret(stats)) {
        return true;
      }
    }
    return false;
  }
}

public class AlertRuleInterpreter {
  private Expression expression;

  public AlertRuleInterpreter(String ruleExpression) {
    this.expression = new OrExpression(ruleExpression);
  }

  public boolean interpret(Map<String, Long> stats) {
    return expression.interpret(stats);
  }
}

11. 中介者模式

11.1 定義

中介者模式定義了一個(gè)單獨(dú)的(中介)對(duì)象,來封裝一組對(duì)象之間的交互。將這組對(duì)象之間的交互委派給與中介對(duì)象交互,來避免對(duì)象之間的直接交互。

11.2 作用

  1. 將對(duì)象之間多對(duì)多復(fù)雜的交互關(guān)系,轉(zhuǎn)化為一對(duì)多的交互關(guān)系,大大降低對(duì)象之間交互的復(fù)雜性。
  2. 每個(gè)對(duì)象只需要和中介者對(duì)象交互,提高了代碼可讀性和可維護(hù)性。

11.3 類結(jié)構(gòu)圖

image

11.4 經(jīng)典實(shí)現(xiàn)

public interface Mediator {
  void handleEvent(Component component, String event);
}

public class LandingPageDialog implements Mediator {
  private Button loginButton;
  private Button regButton;
  private Selection selection;
  private Input usernameInput;
  private Input passwordInput;
  private Input repeatedPswdInput;
  private Text hintText;

  @Override
  public void handleEvent(Component component, String event) {
    if (component.equals(loginButton)) {
      String username = usernameInput.text();
      String password = passwordInput.text();
      //校驗(yàn)數(shù)據(jù)...
      //做業(yè)務(wù)處理...
    } else if (component.equals(regButton)) {
      //獲取usernameInput、passwordInput、repeatedPswdInput數(shù)據(jù)...
      //校驗(yàn)數(shù)據(jù)...
      //做業(yè)務(wù)處理...
    } else if (component.equals(selection)) {
      String selectedItem = selection.select();
      if (selectedItem.equals("login")) {
        usernameInput.show();
        passwordInput.show();
        repeatedPswdInput.hide();
        hintText.hide();
        //...省略其他代碼
      } else if (selectedItem.equals("register")) {
        //....
      }
    }
  }
}

public class UIControl {
  private static final String LOGIN_BTN_ID = "login_btn";
  private static final String REG_BTN_ID = "reg_btn";
  private static final String USERNAME_INPUT_ID = "username_input";
  private static final String PASSWORD_INPUT_ID = "pswd_input";
  private static final String REPEATED_PASSWORD_INPUT_ID = "repeated_pswd_input";
  private static final String HINT_TEXT_ID = "hint_text";
  private static final String SELECTION_ID = "selection";

  public static void main(String[] args) {
    Button loginButton = (Button)findViewById(LOGIN_BTN_ID);
    Button regButton = (Button)findViewById(REG_BTN_ID);
    Input usernameInput = (Input)findViewById(USERNAME_INPUT_ID);
    Input passwordInput = (Input)findViewById(PASSWORD_INPUT_ID);
    Input repeatedPswdInput = (Input)findViewById(REPEATED_PASSWORD_INPUT_ID);
    Text hintText = (Text)findViewById(HINT_TEXT_ID);
    Selection selection = (Selection)findViewById(SELECTION_ID);

    Mediator dialog = new LandingPageDialog();
    dialog.setLoginButton(loginButton);
    dialog.setRegButton(regButton);
    dialog.setUsernameInput(usernameInput);
    dialog.setPasswordInput(passwordInput);
    dialog.setRepeatedPswdInput(repeatedPswdInput);
    dialog.setHintText(hintText);
    dialog.setSelection(selection);

    loginButton.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        dialog.handleEvent(loginButton, "click");
      }
    });

    regButton.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        dialog.handleEvent(regButton, "click");
      }
    });

    //....
  }
}

中介者模式的實(shí)現(xiàn)是有一定副作用的,中介者模式里面不應(yīng)該包含太多具體實(shí)現(xiàn)的邏輯,而應(yīng)該只是作用類與類之間交互的調(diào)度存在的。

11.5 應(yīng)用場(chǎng)景

主要應(yīng)用于類與類之間的交互非常復(fù)雜的情況。

說明

此文是根據(jù)王爭(zhēng)設(shè)計(jì)模式之美相關(guān)專欄內(nèi)容整理而來,非原創(chuàng)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容