1. 程式人生 > 實用技巧 >Java筆記21 - 設計模式 - 行為型模式

Java筆記21 - 設計模式 - 行為型模式

  • 行為型模式, 主要負責演算法和物件間責任分配.
  • 通過使用物件組合, 行為型模型可以描述一組物件應該如何協作來完成一個整體物件.

責任鏈

  • 使得多個物件都有機會處理請求, 從而避免請求的傳送者和接受者之間的耦合關係.
  • 將這些物件連成一條鏈, 並沿著這條鏈處理該請求, 知道有一個物件處理它位置
  • 責任鏈模式(Chain of Responsibility)是一種請求處理模式, 讓多個處理器有機會處理該請求, 知道某個成功為止.
  • 責任鏈模式把多個處理器串成鏈, 讓請求在鏈上傳遞.
public class HandlerChain {

  // 持有所有的handler
  private List<Handler> handlers = new ArrayList<>();
  
  public void addHandler(Handler handler) {
    this.handlers.add(handler);
  }

  public boolean process(Request request) {
    // 以此呼叫每個handler
    for (Handler handler : handlers) {
      Boolean r = handler.process(request);
      // 一旦出現結果, 便返回結果
      if (r != null) return r;
    }
    throw new RuntimeException();
  }
}
  • handler的新增順序很重要
public class AHandler implements Handler {
    private Handler next;
    public void process(Request request) {
        if (!canProcess(request)) {
            // 手動交給下一個Handler處理:
            next.process(request);
        } else {
            ...
        }
    }
}
  • 可以讓上一個處理器呼叫下一個處理器

  • 讓每個處理器都處理Request的方法稱為, 攔截器.

  • 目的不是找到某個handler處理掉request, 而是每個handler都做一些工作.

命令

  • 將一個請求封裝成一個物件, 從而使你可以使用不同的引數, 對客戶端的請求進行引數化.
  • 對請求排隊或者記錄請求日誌, 以及支援可撤銷的操作
  • 命令(Command)模式是指: 把請求封裝成一個命令, 然後執行這個命令.

這就是命令模式的結構:

┌──────┐ ┌───────┐
│Client│─ ─ ─>│Command│
└──────┘ └───────┘
│ ┌──────────────┐
├─>│ CopyCommand │
│ ├──────────────┤
│ │editor.copy() │─ ┐
│ └──────────────┘
│ │ ┌────────────┐
│ ┌──────────────┐ ─>│ TextEditor │
└─>│ PasteCommand │ │ └────────────┘
├──────────────┤
│editor.paste()│─ ┘
└──────────────┘

直譯器

  • 給定一個語言, 定義它的文法的一種表示, 並定義一個直譯器, 這個直譯器使用該表示來解釋語言中的句子.
  • 直譯器模式(Interpreter)是一種針對特定問題設計的解決方法.

  • 把正則表示式解析為語法樹, 然後再匹配指定的字串, 就需要一個解析器.

  • 當我們使用JDBC時, 執行的SQL語句雖然是字串, 但最終需要資料庫伺服器的SQL直譯器來把SQL"翻譯"成資料庫伺服器能執行的程式碼, 這個執行引擎也非常複雜, 但對於使用者來說, 僅僅需要寫出SQL字元即可.

  public static void main(String[] args) {
    log("[{}] start {} at {}", LocalTime.now().withNano(0), "engine", LocalDate.now());
  }
  static void log (String format, Object... args) {
    int len = format.length();
    int argIndex = 0;
    char last = '\0';
    StringBuilder sb = new StringBuilder(len + 20);
    for (int i = 0; i < len; i++) {
      char now = format.charAt(i);
      if (last == '{' && now == '}') {
        sb.deleteCharAt(sb.length() - 1);
        sb.append(args[argIndex]);
        argIndex++;
      } else {
        sb.append(now);
      }
      last = now;
    }
    System.out.println(sb.toString());
  }

迭代器

  • 提供一種方法順序訪問一個聚合物件中的各個元素, 而又不需要暴露該物件的內部表示
  • 迭代器(Iterator).

List<String> list = ...
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
    String s = it.next();
}

// 自我定義反向迭代器
public class ReverseArrayCollection<T> implements Iterable<T> {

  private  T[] array;

  @SafeVarargs
  public ReverseArrayCollection(T... objs) {
    this.array = Arrays.copyOfRange(objs, 0, objs.length);
  }

  @Override
  public Iterator<T> iterator() {
    return null;
  }

  class ReverseIterator implements Iterator<T> {

    // 索引位置
    private int index;

    public ReverseIterator() {
      // 初始化的時候, 讓索引指向陣列的末尾
      this.index = ReverseArrayCollection.this.array.length;
    }

    @Override
    public boolean hasNext() {
      // 索引>0, 可以繼續向下移動
      return index > 0;
    }

    @Override
    public T next() {
      index--;
      return array[index];
    }

  }
}

中介

  • 用一箇中介物件來封裝一系列的物件互動. 中介者使各個物件不需要顯式地相互引用, 從而讓耦合鬆散, 而且可以獨立地改變它們之間的互動
  • 中介者模式(Mediator), 又稱為調停者模式, 目的是把多方會談, 變成雙方會談, 從而實現多方的鬆耦合.

public class Main {
  public static void main(String[] args) {
    new OrderFrame("Hamburger", "Nugget", "Chip", "Coffee");
  }
}

/**
 * InnerMain
 */
class OrderFrame extends JFrame {
  /**
   *
   */
  private static final long serialVersionUID = 2665832664109930397L;

  public OrderFrame(String... names) {
    setTitle("Order");
    setSize(460, 200);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    Container c = getContentPane();
    c.setLayout(new FlowLayout(
      FlowLayout.LEADING, 20, 20
    ));
    c.add(new JLabel("Use Mediator Pattern"));

    List<JCheckBox> checkBoxList = addCheckBox(names);
    JButton selectAll = addButton("Select All");
    JButton selectNone = addButton("Select None");
    selectNone.setEnabled(false);
    JButton selectInverse = addButton("Inverse Select");

    new Mediator(checkBoxList, selectAll, selectNone, selectInverse);
    setVisible(true);
  }

  private List<JCheckBox> addCheckBox(String... names) {
    JPanel panel = new JPanel();
    panel.add(new JLabel("Menu: "));
    List<JCheckBox> list = new ArrayList<>();
    for (String name : names) {
      JCheckBox checkBox = new JCheckBox(name);
      list.add(checkBox);
      panel.add(checkBox);
    }
    getContentPane().add(panel);
    return list;
  }

  private JButton addButton(String label) {
    JButton button = new JButton(label);
    getContentPane().add(button);
    return button;
  }
}


public class Mediator {
  // 引入UI元件
  private List<JCheckBox> checkBoxList;
  private JButton selectAll;
  private JButton selectNone;
  private JButton selectInverse;

  public Mediator(List<JCheckBox> checkBoxList, JButton selectAll, JButton selectNone, JButton selectInverse) {
    this.checkBoxList = checkBoxList;
    this.selectAll = selectAll;
    this.selectNone = selectNone;
    this.selectInverse = selectInverse;

    // 繫結事件
    this.checkBoxList.forEach(checkBox -> {
      checkBox.addChangeListener(this::onCheckBoxChanged);
    });
    this.selectAll.addActionListener(this::onSelectAllClicked);
    this.selectNone.addActionListener(this::onSelectNoneClicked);
    this.selectInverse.addActionListener(this::onSelectInverseClicked);

  }

  // 當checkbox有變化時
  public void onCheckBoxChanged(ChangeEvent event) {
    boolean allChecked = true;
    boolean allUnchecked = true;
    for (JCheckBox checkBox: checkBoxList) {
      if (checkBox.isSelected()) {
        allUnchecked = false;
      } else {
        allChecked = false;
      }
    }
    selectAll.setEnabled(!allChecked);
    selectNone.setEnabled(!allUnchecked);
  }

  // 點選select all
  public void onSelectAllClicked(ActionEvent event) {
    checkBoxList.forEach(checkBox -> checkBox.setSelected(true));
    selectAll.setEnabled(false);
    selectNone.setEnabled(true);
  }

  // 當點選select none
  public void onSelectNoneClicked(ActionEvent event) {
    checkBoxList.forEach(checkBox -> checkBox.setSelected(false));
    selectAll.setEnabled(true);
    selectNone.setEnabled(false);
  }

  // 當點選select inverse
  public void onSelectInverseClicked(ActionEvent event) {
    checkBoxList.forEach(checkBox -> checkBox.setSelected(!checkBox.isSelected()));
    onCheckBoxChanged(null);
  }
}
  • 使用Mediator模式後, 我們可以得到以下好處

    • 各個UI元件互不引用, 這樣就減少了元件之間的耦合關係
    • Mediator用於當一個元件發生狀態變化時, 根據當前所有元件的狀態決定更新某些元件.
    • 如果新增一個UI元件, 我們只需要修改Mediator更新狀態的邏輯, 現有的其他UI元件不變化.
  • Mediator模式常用在有眾多互動元件的UI上.

  • 為了簡化UI程式, MVC模式以及MVVM模式都可以看作是Mediator模式的擴充套件.

  • 中介模式就是引入一箇中介物件, 把多邊關係變成多個雙邊關係

備忘錄

在不破壞封閉性的前提下, 捕獲一個物件的內部狀態, 並在該物件之外儲存這個狀態.

  • 備忘錄模式(Memento): 主要是用於捕獲一個物件的內部狀態, 以便在將來的某個時刻恢復此狀態

  • 標準的備忘錄有幾中角色:

    • Memento: 儲存的內部狀態
    • Originator: 建立一個備忘錄並設定其狀態
    • Caretaker: 負責儲存備忘錄

觀察者

定義物件間的一種一對多的依賴關係, 當一個物件的狀態發生改變時, 所有依賴它的物件都得到通知並被自動更新

  • 觀察者模式(Observer)又稱為釋出-訂閱模式(Publish-Subscribe Pub/Sub). 是一種通知機制, 讓傳送通知的一方(被觀察方)和接收通知的一方(觀察者)能彼此分離, 互不影響
public class Store {

  private List<ProductObserver> observers = new ArrayList<>();
  private Map<String, Product> products = new HashMap<>();

  // 註冊觀察者
  public void addObsever(ProductObserver observer) {
    this.observers.add(observer);
  }

  // 取消註冊
  public void removeObserver(ProductObserver observer) {
    this.observers.remove(observer);
  }

  public void addNewProduct(String name, double price) {
    Product p = new Product(name, prize);
    products.put(p.getName(), p);

    // 通知觀察者
    observers.forEach(o -> o.onPublished(p));
  }

  public void setProductPrice(String name, double price) {
    Product p = products.get(name);
    p.setPrice(price);

    // 通知觀察者
    observers.forEach(o -> o.onPriceChanged(p));
  }
}
  • 可以匿名註冊觀察者
    store.addObserver(new ProductObserver() {
      public void onPublised(Product product) {
        System.out.println("[Log] on product published: " + product); 
      }
      public void onPriceChanged(Product product) {
        System.out.println("[Log] on product price changed: " + product);
      }
    });
  • 也可以把被觀察者抽象出介面
public interface  ProductObservable {
  void addObserver(ProductObserver observer);
  void removeObserver(ProductObserver observer);
}
  • 也可以用把通知變成一個Event物件, 從而不再有多種方法通知
interface ProductObserver {

  void onEvent(ProductEvent event);
}
  • 可以非同步執行任務
    observers.forEach(o -> new Thread() {
      @Override
      public void run() {
        o.onPublished(p);
      }
    }.start());

狀態

允許一個對下個在其內部狀態改變時, 改變它的行為. 物件看起來似乎修改了它的類

  • 狀態模式(State)經常用在帶有狀態的物件中.

  • 可以定一個enum就可以表示不同的狀態. 但不同的狀態需要對應不同的行為.

  • 狀態模式的目的是為了讓一搭串的if...else...的邏輯拆分到不同的狀態類中, 使得將來增加狀態比較容易.

  • 狀態模式的核心思想在於狀態轉換

public class BotContent {
  // 預設離線狀態
  private State state = new DisconnectedState();

  public String chat(String input) {
    if ("hello".equalsIgnoreCase(input)) {
      // 切換到線上狀態
      state = new ConnectedState();
      return state.init();
    } else if ("bye".equalsIgnoreCase(input)) {
      // 離線狀態
      state = new DisconnectedState();
      return state.init();
    }
    return state.reply(input);
  }
}

策略

策略定義一系列的演算法, 把它們一個個封裝起來, 並使它們可以相互替換. 本模式主要是演算法, 可以獨立與使用它的客戶而變化.

  • 策略模式: Strategy, 是指, 定義一組演算法, 並把其封裝到一個物件中. 然後在執行的時候, 可以靈活的執行其中某一個演算法.
  • 流程是確定的, 但是, 某些關鍵步驟的演算法依賴呼叫方傳入的策略.
        String[] array = {"apple", "Pear", "Banana", "orange"};
        // Arrays.sort(array, String::compareToIgnoreCase);
        Arrays.sort(array, String::compareTo);
        System.out.println(Arrays.toString(array));

模板方法

定於一個操作中的演算法的骨架, 而將一些步驟延遲到子類中, 使得子類可以不改變一個演算法的結構, 即可重定義該演算法的某些特定步驟.

  • 模板方法(template method)是一個比較簡單的模式. 它的思想主要是, 定義一個操作的一系列步驟, 對於某些暫時確定不了的步驟, 就留給子類去實現好了, 這樣不同的子類就可以定義出不同的步驟.
  • 模板方法的核心在於定義一個"骨架".
public abstract class AbstractSetting {
  public final String getSetting(String key) {
    String value = lookupCache(key);
    if (value == null) {
      value = readFromDatabase(key);
      putIntoCache(key, value);
    }
    return null;
  }

  public String readFromDatabase(String key) {
    return "value..";
  }

  protected abstract String lookupCache(String key);

  protected abstract void putIntoCache(String key, String value);
}

public class LocalSetting extends AbstractSetting {
  private Map<String, String> cache = new HashMap<>();

  @Override
  protected String lookupCache(String key) {
    return cache.get(key);
  }

  @Override
  protected void putIntoCache(String key, String value) {
    cache.put(key, value);
  }

}
  • 模板思想核心: 父類定義骨架, 子類實現某些細節.
  • 為了防止子類重寫父類的骨架方法, 可以在父類中對骨架方法使用final.
  • 對於子類需要實現的抽象方法, 一般宣告為protected, 使得這些方法對外部客戶端不可見.

訪問者

表示一個作用於某物件結構中的各個元素的操作.
它使你可以在不改變各元素的類的前提下, 定義作用這些元素的操作

  • 訪問者模式(Visitor), 是一種操作一組物件的操作, 目的是不改變物件的定義, 但允許新增不同的訪問者, 來定義新的操作.
public class FileStructure {
  // 根目錄
  private File path;

  public void handle(Visitor visitor) {
    scan(path, visitor);
  }

  public void scan(File file, Visitor visitor) {
    if (file.isDirectory()) {
      // 讓訪問者處理資料夾:
      visitor.visitDir(file);
      for (File sub : file.listFiles()) {
        // 遞迴處理子資料夾
        scan(sub, visitor);
      }
    } else if (file.isFile()) {
      // 讓訪問者處理檔案
      visitor.visitFile(file);
    }
  }

  public FileStructure(File path) {
    this.path = path;
  }
}

public class JavaFileVisitor implements Visitor {

  @Override
  public void visitDir(File dir) {
    System.out.println("Visit dir: " + dir);

  }

  @Override
  public void visitFile(File file) {
    if (file.getName().endsWith(".java")) {
      System.out.println("Found java file: " + file);
    }
  }
  
}
public interface Visitor {
  // 訪問資料夾
  void visitDir(File dir);
  // 訪問檔案
  void visitFile(File file);
}

FileStructure fs = new FileStructure(new File("."));
fs.handle(new JavaFileVisitor());
  • 訪問者模式的核心思想是為了訪問比較複雜的資料結構, 不去改變資料結構, 而是把對資料的操作抽象出來
  • 在"訪問"的過程中以回撥形式在訪問者中處理操作邏輯
public class App {

    public static void main(String[] args) throws IOException {
        Files.walkFileTree(Paths.get("."), new MyFileVisitor());
    }
}

class MyFileVisitor extends SimpleFileVisitor<Path> {
    // 處理資料夾
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)  {
        System.out.println("pre visit dir: " + dir);
        return FileVisitResult.CONTINUE;
    }

    // 處理檔案
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        System.out.println("visit file: " + file);
        // 返回CONTINUE表示繼續訪問
        return FileVisitResult.CONTINUE;
    }
}