【重温设计模式】行为型设计模式之访问者模式,备忘录模式,命令模式,解释器模式,中介模式
一、访问者模式
1、访问者模式的概念
- 概念:允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。
2、为什么要使用访问者模式
- 访问者模式针对的是一组类型不同的对象(PdfFile、PPTFile、WordFile)。不过,尽管这组对象的类型是不同的,但是,它们继承相同的父类(ResourceFile)或者实现相同的接口。在不同的应用场景下,我们需要对这组对象进行一系列不相关的业务操作
- 访问者模式允许一个或者多个操作应用到一组对象上,设计意图是解耦操作和对象本身,保持类职责单一、满足开闭原则以及应对代码的复杂性。
3、访问者模式案例
【1】以对不同文件的提取和压缩的案例,示范访问者。
特点:对文件的其他操作,直接扩展访问者,无需对文件对象进行改动,无需对已有的操作类进行改动,符合开闭原则。
(1)ResourceFile声明数据节点接口
public abstract class ResourceFile { protected String filePath; public ResourceFile(String filePath) { this.filePath = filePath; } abstract public void accept(Visitor vistor); }
(2)PdfFile声明具体的数据节点实现类,(省略了//...PPTFile、WordFile跟PdfFile类似,这里就省略了...)
public class PdfFile extends ResourceFile { public PdfFile(String filePath) { super(filePath); } @Override public void accept(Visitor visitor) { visitor.visit(this); } //... }
(3)Visitor声明访问者接口
public interface Visitor { void visit(PdfFile pdfFile); void visit(PPTFile pdfFile); void visit(WordFile pdfFile); }
(4)Extractor提取文件内容的访问者实现
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."); } }
(5)Compressor 压缩文件内容访问者实现
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."); } }
(7)客户端代码
import java.util.ArrayList; import java.util.List; import ch.qos.logback.core.rolling.helper.Compressor; public class ToolApplication { public static void main(String[] args) { //声明提取文件内容访问者 Extractor extractor = new Extractor(); List 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 listAllResourceFiles(String resourceDirectory) { List 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; } }
【2】sql语法树的访问。主要是alibaba的开源数据库连接池框架中druid的sql语法树的访问。
二、备忘录模式
1、备忘录模式的概念
- 概念:在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。
- 备忘录模式和备份的区别:备份在我们平时的开发中更常听到。那备忘录模式跟“备份”有什么区别和联系呢?实际上,这两者的应用场景很类似,都应用在防丢失、恢复、撤销等场景中。它们的区别在于,备忘录模式更侧重于代码的设计和实现,备份更侧重架构设计或产品设计
2、如何优化内存和时间消耗
担忧:假设每当有数据改动,我们都需要生成一个备份,以备之后恢复。如果需要备份的数据很大,这样高频率的备份,不管是对存储(内存或者硬盘)的消耗,还是对时间的消耗,都可能是无法接受的。
解决方案:采用“低频率全量备份”和“高频率增量备份”相结合的方法。
- 全量备份:把所有的数据“拍个快照”保存下来。
- 增量备份:记录每次操作或数据变动。
- 当我们需要恢复到某一时间点的备份的时候,如果这一时间点有做全量备份,我们直接拿来恢复就可以了。如果这一时间点没有对应的全量备份,我们就先找到最近的一次全量备份,然后用它来恢复,之后执行此次全量备份跟这一时间点之间的所有增量备份,也就是对应的操作或者数据变动。这样就能减少全量备份的数量和频率,减少对时间、内存的消耗。
3、备忘录模式案例
import java.util.Stack; public class InputText { private StringBuilder text = new StringBuilder(); public String getText() { return text.toString(); } public void append(String input) { text.append(input); } /** * 创建快照 * @return */ public Snapshot createSnapshot() { return new Snapshot(text.toString()); } /** * 恢复上一个版本的快照 * @param snapshot */ 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 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); } } } }
三、命令模式
1、命令模式概念
- 概念:命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能。
- 核心实现手段:命令模式用的最核心的实现手段,是将函数封装成对象
2、命令模式的价值
- 命令模式的主要作用和应用场景:是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等等,这才是命令模式能发挥独一无二作用的地方。
3、命令模式和策略模式,工厂模式的区别
每个设计模式都应该由两部分组成:第一部分是应用场景,即这个模式可以解决哪类问题;第二部分是解决方案,即这个模式的设计思路和具体的代码实现。
策略模式和工厂模式的区别
- 策略模式:包含策略的定义、创建和使用三部分,从代码结构上来,它非常像工厂模式。它们的区别在于,策略模式侧重“策略”或“算法”这个特定的应用场景,用来解决根据运行时状态从一组策略中选择不同策略的问题。
- 工厂模式:侧重封装对象的创建过程,这里的对象没有任何业务场景的限定,可以是策略,但也可以是其他东西。从设计意图上来,这两个模式完全是两回事儿。
策略模式和命令模式的区别
- 策略模式:不同的策略具有相同的目的、不同的实现、互相之间可以替换,比如,BubbleSort、SelectionSort 都是为了实现排序的,只不过一个是用冒泡排序算法来实现的,另一个是用选择排序算法来实现的
- 命令模式:不同的命令具有不同的目的,对应不同的处理逻辑,并且互相之间不可替换。
4、命令模式的案例
命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等等,这才是命令模式能发挥独一无二作用的地方。
activit工作流框架,就使用了命令模式
import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; 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 queue = new LinkedList<>(); public void mainloop() { while (true) { List 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(); } } } }
四、解释器模式
1、解释器模式的概念
- 概念:解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。
- 从广义上来讲,只要是能承载信息的载体,我们都可以称之为“语言”,比如,古代的结绳记事、盲文、哑语、摩斯密码等。
2、解释器模式的应用场景和价值
- 它用来描述如何构建一个简单的“语言”解释器。比起命令模式,解释器模式更加小众,只在一些特定的领域会被用到,比如编译器、规则引擎、正则表达式。
3、解释器模式案例
表达式需求
import java.util.HashMap; import java.util.Map; public class AlertRuleInterpreter { // key1 > 100 && key2 < 1000 || key3 == 200 public AlertRuleInterpreter(String ruleExpression) { //TODO:由你来完善 } // apiStat = new HashMap<>(); // apiStat.put("key1", 103); // apiStat.put("key2", 987); public boolean interpret(Map stats) { //TODO:由你来完善 } } public class DemoTest { public static void main(String[] args) { String rule = "key1 > 100 && key2 < 30 || key3 < 100 || key4 == 88"; AlertRuleInterpreter interpreter = new AlertRuleInterpreter(rule); Map stats = new HashMap<>(); stats.put("key1", 101l); stats.put("key3", 121l); stats.put("key4", 88l); boolean alert = interpreter.interpret(stats); System.out.println(alert); } }
解析器表达式实现思路
import sun.tools.tree.EqualExpression; import sun.tools.tree.LessExpression; import java.util.ArrayList; import java.util.List; public interface Expression { boolean interpret(Map 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 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 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 expressions) { this.expressions.addAll(expressions); } @Override public boolean interpret(Map stats) { for (Expression expr : expressions) { if (!expr.interpret(stats)) { return false; } } return true; } } /** * ||表达式解析器 */ public class OrExpression implements Expression { private List expressions = new ArrayList<>(); public OrExpression(String strOrExpression) { String[] andExpressions = strOrExpression.split("\\|\\|"); for (String andExpr : andExpressions) { expressions.add(new AndExpression(andExpr)); } } public OrExpression(List expressions) { this.expressions.addAll(expressions); } @Override public boolean interpret(Map 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 stats) { return expression.interpret(stats); } }
五、中介模式
1、中介模式的概念
中介模式定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。
2、中介模式的应用场景
3、中介模式和观察者模式的区别
- 中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。
- 观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。而在中介模式的应用场景中,参与者之间的交互关系错综复杂,既可以是消息的发送者、也可以同时是消息的接收者。
3、中介模式的案例
中介模式的缺点:当参与者之间的交互关系错综复杂,维护成本很高的时候,我们才考虑使用中介模式。毕竟,中介模式的应用会带来一定的副作用,前面也讲到,它有可能会产生大而复杂的上帝类。除此之外,如果一个参与者状态的改变,其他参与者执行的操作有一定先后顺序的要求,这个时候,中介模式就可以利用中介类