行为型设计模式范围
- 观察者模式
- 模板方法
- 策略模式
- 职责链模式
- 状态模式
- 迭代器模式
- 访问者模式
- 备忘录模式
- 命令模式
- 解释器模式
- 中介模式
行为型设计模式作用
行为型设计模式主要关注的是类与类之间的交互问题。
7. 访问者模式
7.1 定义
允许一个或多个操作应用到一组对象上,解耦操作和对象本身。
7.2 作用
- 解耦操作和对象本身,使得操作和对象本身都可以单独扩展,且满足职责单一、开闭原则等设计思想和原则
- 变向支持双分派实现,即调用哪个对象的哪个方法都可以在运行时决定
7.3 类结构图
image7.4 经典实现
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<>();
//...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile)
resourceFiles.add(new PdfFile("a.pdf"));
resourceFiles.add(new WordFile("b.word"));
resourceFiles.add(new PPTFile("c.ppt"));
return resourceFiles;
}
}
具体实现:
- 定义 Visotor 接口,根据不同的功能实现 Visotor 接口
- 在使用 Visotor 提供功能的类中通过
accept(Visitor visitor)
接收这个 Visotor 对象的函数注入操作 - 通过调用
accept(visitor)
方法来将当前对象通过 this 传入到 Visitor 中并进行处理
扩展问题
当需要扩展功能时,只需要实现 Visotor 接口,并实现新的功能逻辑,并在最终调用方创建新的 Visotor 对象并传入到待处理的业务类中即可。
不需要改动待处理的业务类中的逻辑。
7.5 多态和函数重载的区别
多态是一种动态绑定,也就是在运行时获取到对象的实现类型。
而重载是一种静态绑定,在代码编译的过程中,并不能获取对象的实现类型,只能根据声明类型执行类型对象的方法。
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<>();
//...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile)
resourceFiles.add(new PdfFile("a.pdf"));
resourceFiles.add(new WordFile("b.word"));
resourceFiles.add(new PPTFile("c.ppt"));
return resourceFiles;
}
}
由于Extractor 类中定义了几个重载方法,分别接收继承了同一个接口的不同实现的类。当在使用过程中,传入接口声明类型给方法时,由于没有实现参数为该接口的函数,导致 无法匹配到任意函数,而使代码无法通过编译。
7.6 应用场景
一般来说,访问者模式针对继承或实现了同一个类或接口的不同对象(PdfFile、PPTFile 和 WordFile)进行一系列不相关的业务操作。将业务操作抽离出来,定义在独立细分的访问者类中,并通过组合的方式,将业务操作类与对象本身解耦。
7.7 什么是单分派(Single Dispatch)和双分派(Double Dispatch)
单分派
执行哪个对象的方法,根据对象的运行时类型决定;而执行对象的哪个方法,根据方法参数的编译时类型来决定。
双分派
执行哪个对象的方法,根据对象的运行时类型决定;而执行对象的哪个方法,也根据方法参数的运行时类型来决定。
如何理解 Single 和 Double 这两个词
Single 说明执行对象的哪个方法只跟对象的“运行时”类型有关;
Double 说明执行对象的哪个方法跟对象和参数的“运行时”类型两者有关。
C++/JAVA 都只支持 Single Dispatch。
7.8 为什么支持 Double Dispatch 的语言不需要访问者模式
由于 Double Dispatch 支持调用哪种对象的哪个方法,即可以通过对象的运行时类型,决定调用哪个重载函数。那直接将对象本身传递给操作相关类中的重载函数即可执行。无需将操作相关类反向注入到对象本身中,并通过 this 获取当前运行时类型来完成根据运行时类型调用重载函数的操作。
7.9 使用其它方式实现不同类型文件对象的多种操作
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代码结构类似,此处省略...
public interface Extractor {
void extract2txt(ResourceFile resourceFile);
}
public class PdfExtractor implements Extractor {
@Override
public void extract2txt(ResourceFile resourceFile) {
//...
}
}
//...PPTExtractor/WordExtractor跟PdfExtractor代码结构类似,此处省略...
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<>();
//...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile)
resourceFiles.add(new PdfFile("a.pdf"));
resourceFiles.add(new WordFile("b.word"));
resourceFiles.add(new PPTFile("c.ppt"));
return resourceFiles;
}
}
使用工厂类创建不同的操作对象,并根据不同类型的文件获取不同的操作对象来完成一个或多个操作。
8. 备忘录模式
8.1 定义
在不违反封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为之前的状态。
8.2 作用
- 防丢失,快速撤销、恢复数据。
8.3 类结构图
image8.4 经典实现
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);
}
}
}
}
实现存在问题
- InputText 快照类中中提供的
setText()
只是为了实现备忘录模式而定义的一个函数,而这个函数可能被其它业务调用,暴露了不该暴露的方法违背了封装原则。 - 对于快照类来说,是不可变的,不应该提供任何 set 函数。所以,InputText 这个快照类的设计违背了封装原则。
改进版本
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);
}
}
}
}
创建新的 Snapshot 类来表示快照类,不提供任何修改快照状态的函数。将 InputText 类的 setText()
方法改为 restoreSnapShot()
方法更为达意。
8.5 备忘录模式和备份的区别
备忘录模式更侧重于代码的设计和实现,而备份侧重于架构设计或产品设计上。
8.6 如何优化内存和时间消耗
不同的应用场景下有不同的解决方法。像上面的例子,我们只需要记录每次变化后的数据长度,结合 InputText 原始数据就可以完成撤销和恢复的工作。
而对于备份的对象通常比较大,里面记录了很多的状态等信息,这个时候,需要采用“低频全量更新、高频增量更新的策略”。也就是在一段时间内记录只记录每次变动的增量信息,当这段时间过后,对数据进行一次全量备份,然后,再在一段时间内记录增量信息,循环往复这个过程来完成备份的操作。如果恢复数据呢?
通过要恢复的点往前找到最近一次全量备份的数据,然后,再把增量部分的数据进行累加,完成备份数据的恢复操作。
8.7 应用场景
备忘录模式主要是用来对数据进行备份,防止数据丢失、撤销和恢复数据等应用场景。
9. 命令模式
9.1 定义
命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其它对象(将不同请求依赖注入到其它对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能。
核心手段
将函数封装成对象。借助命令模式我们可以将函数封装成对象。
具体实现
设计一个包含函数的类,实例化一个对象进行传递,这样就近似地实现了把函数像对象一样传递(主要是从设计意图上来看)。从实现角度来看,类似于回调。
9.2 作用
- 控制命令的执行,使用命令模式来模拟将函数像对象一样传递,提高业务的易理解性及实现的灵活性。
- 封装数据和数据处理逻辑到同一对象,提高了代码的封装性和易读性。
9.3 类结构图
image9.4 经典实现
public interface Command {
void execute();
}
public class GotDiamondCommand implements Command {
// 省略成员变量
public GotDiamondCommand(/*数据*/) {
//...
}
@Override
public void execute() {
// 执行相应的逻辑
}
}
//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中获取数据,并封装成Request的逻辑,
//注意设置超时时间,如果很长时间没有接收到请求,就继续下面的逻辑处理。
for (Request request : requests) {
Event event = request.getEvent();
Command command = null;
if (event.equals(Event.GOT_DIAMOND)) {
command = new GotDiamondCommand(/*数据*/);
} else if (event.equals(Event.GOT_STAR)) {
command = new GotStartCommand(/*数据*/);
} else if (event.equals(Event.HIT_OBSTACLE)) {
command = new HitObstacleCommand(/*数据*/);
} else if (event.equals(Event.ARCHIVE)) {
command = new ArchiveCommand(/*数据*/);
} // ...一堆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 设计模式两个部分及不同设计模式的区分
第一部分为:应用场景(设计意图)。即这个模式可以解决哪类问题。
第二部分为:解决方案。即这个模式的设计思想和代码实现。
设计模式的主要区别
不同设计模式的主要区别在于设计意图,也就是应用场景。
9.5 命令模式和策略模式的区别
在策略模式中,不同的策略具有相同的目的、不同的实现、互相之间可以替换。而命令模式中,不同的命令目的不同,对应的处理逻辑也不一样,无法相互替换。
9.6 应用场景
用来控制命令的执行。比如:异步、延迟、排队执行命令、撤销重做命令、存储命令等。
10. 解释器模式
10.1 定义
解释器模式为某个语言定义它的语法表示,并定义一个解释器用来处理这些语法。
10.2 作用
- 通过定义语法和语法解释器来应对复杂的需求,提高代码的复用性。
- 将语法解析的工作拆分到各个小类中,然后对每个语法单元进行解析,最终合并为对整个语法规则进行解析。
10.3 类结构图
image10.4 经典实现
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代码结构类似,这里就省略了
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 应用场景
自定义接口告警规则功能
对自定义告警规则的语法规则进行解析,并通过解析出来的结果进行过滤,满足条件的即触发告警通知。
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 作用
- 将对象之间多对多复杂的交互关系,转化为一对多的交互关系,大大降低对象之间交互的复杂性。
- 每个对象只需要和中介者对象交互,提高了代码可读性和可维护性。
11.3 类结构图
image11.4 经典实现
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();
//校验数据...
//做业务处理...
} else if (component.equals(regButton)) {
//获取usernameInput、passwordInput、repeatedPswdInput数据...
//校验数据...
//做业务处理...
} 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");
}
});
//....
}
}
中介者模式的实现是有一定副作用的,中介者模式里面不应该包含太多具体实现的逻辑,而应该只是作用类与类之间交互的调度存在的。
11.5 应用场景
主要应用于类与类之间的交互非常复杂的情况。
说明
此文是根据王争设计模式之美相关专栏内容整理而来,非原创。
网友评论