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

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

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

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

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

7. 訪問者模式

7.1 定義

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

7.2 作用

  1. 解耦操作和對象本身胧后,使得操作和對象本身都可以單獨擴展肴盏,且滿足職責單一科盛、開閉原則等設(shè)計思想和原則
  2. 變向支持雙分派實現(xiàn),即調(diào)用哪個對象的哪個方法都可以在運行時決定

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

image

7.4 經(jīng)典實現(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)建不同的類對象(PdfFile/PPTFile/WordFile)
    resourceFiles.add(new PdfFile("a.pdf"));
    resourceFiles.add(new WordFile("b.word"));
    resourceFiles.add(new PPTFile("c.ppt"));
    return resourceFiles;
  }
}

具體實現(xiàn):

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

擴展問題

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

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

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

多態(tài)是一種動態(tài)綁定翩剪,也就是在運行時獲取到對象的實現(xiàn)類型。

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

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)建不同的類對象(PdfFile/PPTFile/WordFile)
    resourceFiles.add(new PdfFile("a.pdf"));
    resourceFiles.add(new WordFile("b.word"));
    resourceFiles.add(new PPTFile("c.ppt"));
    return resourceFiles;
  }
}

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

7.6 應(yīng)用場景

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

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

單分派

執(zhí)行哪個對象的方法,根據(jù)對象的運行時類型決定眨猎;而執(zhí)行對象的哪個方法抑进,根據(jù)方法參數(shù)的編譯時類型來決定。

雙分派

執(zhí)行哪個對象的方法睡陪,根據(jù)對象的運行時類型決定寺渗;而執(zhí)行對象的哪個方法,也根據(jù)方法參數(shù)的運行時類型來決定兰迫。

如何理解 Single 和 Double 這兩個詞

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

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

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

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

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

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)建不同的類對象(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)建不同的操作對象,并根據(jù)不同類型的文件獲取不同的操作對象來完成一個或多個操作仲吏。

8. 備忘錄模式

8.1 定義

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

8.2 作用

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

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

image

8.4 經(jīng)典實現(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);
      }
    }
  }
}

實現(xiàn)存在問題

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

改進版本

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() 方法更為達意瘪匿。

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

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

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

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

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

通過要恢復的點往前找到最近一次全量備份的數(shù)據(jù)狞悲,然后撮抓,再把增量部分的數(shù)據(jù)進行累加,完成備份數(shù)據(jù)的恢復操作摇锋。

8.7 應(yīng)用場景

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

9. 命令模式

9.1 定義

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

核心手段

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

具體實現(xiàn)

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

9.2 作用

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

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

image

9.4 經(jīng)典實現(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è)置超時時間阅酪,如果很長時間沒有接收到請求旨袒,就繼續(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è)計模式兩個部分及不同設(shè)計模式的區(qū)分

第一部分為:應(yīng)用場景(設(shè)計意圖)术辐。即這個模式可以解決哪類問題砚尽。

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

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

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

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

在策略模式中瑞躺,不同的策略具有相同的目的敷搪、不同的實現(xiàn)兴想、互相之間可以替換。而命令模式中购啄,不同的命令目的不同襟企,對應(yīng)的處理邏輯也不一樣,無法相互替換狮含。

9.6 應(yīng)用場景

用來控制命令的執(zhí)行顽悼。比如:異步、延遲几迄、排隊執(zhí)行命令蔚龙、撤銷重做命令、存儲命令等映胁。

10. 解釋器模式

10.1 定義

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

10.2 作用

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

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

image

10.4 經(jīng)典實現(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)用場景

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

對自定義告警規(guī)則的語法規(guī)則進行解析廷臼,并通過解析出來的結(jié)果進行過濾苍在,滿足條件的即觸發(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 定義

中介者模式定義了一個單獨的(中介)對象寂恬,來封裝一組對象之間的交互。將這組對象之間的交互委派給與中介對象交互莱没,來避免對象之間的直接交互初肉。

11.2 作用

  1. 將對象之間多對多復雜的交互關(guān)系,轉(zhuǎn)化為一對多的交互關(guān)系饰躲,大大降低對象之間交互的復雜性朴译。
  2. 每個對象只需要和中介者對象交互,提高了代碼可讀性和可維護性属铁。

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

image

11.4 經(jīng)典實現(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();
      //校驗數(shù)據(jù)...
      //做業(yè)務(wù)處理...
    } else if (component.equals(regButton)) {
      //獲取usernameInput眠寿、passwordInput、repeatedPswdInput數(shù)據(jù)...
      //校驗數(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");
      }
    });

    //....
  }
}

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

11.5 應(yīng)用場景

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

說明

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市奢浑,隨后出現(xiàn)的幾起案子蛮艰,更是在濱河造成了極大的恐慌,老刑警劉巖雀彼,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壤蚜,死亡現(xiàn)場離奇詭異,居然都是意外死亡徊哑,警方通過查閱死者的電腦和手機袜刷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來莺丑,“玉大人著蟹,你說我怎么就攤上這事∩颐В” “怎么了萧豆?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長昏名。 經(jīng)常有香客問我涮雷,道長,這世上最難降的妖魔是什么葡粒? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任份殿,我火速辦了婚禮膜钓,結(jié)果婚禮上嗽交,老公的妹妹穿的比我還像新娘。我一直安慰自己颂斜,他們只是感情好夫壁,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沃疮,像睡著了一般盒让。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上司蔬,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天邑茄,我揣著相機與錄音,去河邊找鬼俊啼。 笑死肺缕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播同木,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼浮梢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了彤路?” 一聲冷哼從身側(cè)響起秕硝,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎洲尊,沒想到半個月后远豺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡颊郎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年憋飞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姆吭。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡榛做,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出内狸,到底是詐尸還是另有隱情检眯,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布昆淡,位于F島的核電站锰瘸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏昂灵。R本人自食惡果不足惜避凝,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望眨补。 院中可真熱鬧管削,春花似錦、人聲如沸撑螺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甘晤。三九已至含潘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間线婚,已是汗流浹背遏弱。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留塞弊,地道東北人磺芭。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像润脸,于是被迫代替她去往敵國和親柿扣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348