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