• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • YouClaw
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

无信不立

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【重温设计模式】行为型设计模式之访问者模式,备忘录模式,命令模式,解释器模式,中介模式

一、访问者模式

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);
}
View Code

(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);
    }
    
    //...
}
View Code

(3)Visitor声明访问者接口

public interface Visitor {
    void visit(PdfFile pdfFile);

    void visit(PPTFile pdfFile);

    void visit(WordFile pdfFile);
}
View Code

(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.");
    }
}
View Code

(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."); 
    }
}
View Code

(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;
    }
}
View Code

 

【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);
            }
        }
    }
}
View Code

 

三、命令模式

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();
            }
        }
    }
}
View Code

 

四、解释器模式

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);
    }
}
View Code

解析器表达式实现思路

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);
    }
}
View Code

 

 

五、中介模式

1、中介模式的概念

中介模式定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。

2、中介模式的应用场景

3、中介模式和观察者模式的区别

  • 中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。
  • 观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。而在中介模式的应用场景中,参与者之间的交互关系错综复杂,既可以是消息的发送者、也可以同时是消息的接收者。

3、中介模式的案例

中介模式的缺点:当参与者之间的交互关系错综复杂,维护成本很高的时候,我们才考虑使用中介模式。毕竟,中介模式的应用会带来一定的副作用,前面也讲到,它有可能会产生大而复杂的上帝类。除此之外,如果一个参与者状态的改变,其他参与者执行的操作有一定先后顺序的要求,这个时候,中介模式就可以利用中介类

posted on 2020-08-02 21:45  无信不立  阅读(214)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3