经典伴读_GOF设计模式_行为模式(上)

经典伴读系列文章,不是读书笔记,自己的理解加上实际项目中运用,旨在5天读懂这本书。如果这篇文章对您有些用处,请点赞告诉我O(∩_∩)O。
在这里插入图片描述

行为模式

GOF中23种设计模式从用途上分为三类,第三类是行为模式,描述的是算法和对象间职责的分配,主要是对象或类之间的通信模式。

Chain Of Responsibility责任链

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这 些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

1、GOF从UI页面的事件冒泡入手解释责任链,一个终端应用(Application),点击弹窗(Dialog)中的按钮(Button),按照事件冒泡规则(从内往外),请求传导应该是Button->Dialog->Application。这就是一种责任链。天塌了也有个高的顶,差不多也是这个意思,那么这样的一条责任链应该怎么构造。
在这里插入图片描述

	public static abstract class Handler {
        private Handler successor; //后继者,是不是叫next更合适些

        public void handleRequest(Request request) { //处理请求
            if (successor != null) {
                successor.handleRequest(request);
            }
        }

        public Handler getSuccessor() {
            return successor;
        }

        public void setSuccessor(Handler successor) {
            this.successor = successor;
        }
    }

    public static class Request {} //封装请求参数

    public static class ConcreteHandler1 extends Handler {
        @Override
        public void handleRequest(Request request) {
            System.out.println("ConcreteHandler1处理请求");
            super.handleRequest(request); //往后继续请求
        }
    }

    public static class ConcreteHandler2 extends Handler {
        @Override
        public void handleRequest(Request request) {
            System.out.println("ConcreteHandler2处理请求");
            super.handleRequest(request); //往后继续请求
        }
    }

    public static void main(String[] args) {
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();
        handler1.setSuccessor(handler2); //构建责任链:handler1->handler2

        Request request = new Request();
        handler1.handleRequest(request); //传导请求handler1->handler2
        handler2.handleRequest(request); //只请求handler2
    }

输出:
ConcreteHandler1处理请求
ConcreteHandler2处理请求
ConcreteHandler2处理请求

2、实际开发中链式处理的场景不少,如:过滤器,拦截器。看看它们又是如何实现责任链。

  • 过滤器Filter核心方法:void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3)
  • doFilter三个入参分别表示:请求参数,响应结果,过滤链,我们重点看FilterChain。
  • 带filter的servlet请求链为:filter1 -> filter2 -> … filtern -> servlet ,这样一个请求链如何实现呢?

ApplicationFilterChain是FilterChain的具体实现:

	......
	private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
	private int pos = 0;
    private int n = 0;
    private Servlet servlet = null;
    ......
	private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (this.pos < this.n) {
            ApplicationFilterConfig filterConfig = this.filters[this.pos++];
			......
                Filter filter = filterConfig.getFilter();
                ......
                    filter.doFilter(request, response, this);
                 ......
        } else {
              	......
                    this.servlet.service(request, response);
               ,,,,,,
        }

其中的精髓学到了么,我们给起个名字责任链2.0,将责任链从处理类中单独抽象出来,如:HandlerChain。
在这里插入图片描述

	public static class Request {} //封装请求参数

    interface Handler {
        void handleRequest(Request request, HandlerChain handlerChain);
    }

    public static class HandlerChain {
        private List<Handler> handlers = new ArrayList<>();
        private int pos = 0;

        public void handleRequest(Request request) { //关键不同
            if (pos < handlers.size()) {
                Handler handler = handlers.get(pos++);
                handler.handleRequest(request, this);
            }
        }

        public void addHandler(Handler handler) {
            handlers.add(handler);
        }
    }

    public static class ConcreteHandler1 implements Handler {
        @Override
        public void handleRequest(Request request, HandlerChain handlerChain) {
            System.out.println("ConcreteHandler1处理请求");
            handlerChain.handleRequest(request);
        }
    }

    public static class ConcreteHandler2 implements Handler {
        @Override
        public void handleRequest(Request request, HandlerChain handlerChain) {
            System.out.println("ConcreteHandler2处理请求");
            handlerChain.handleRequest(request);
        }
    }

    public static void main(String[] args) {
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();

        HandlerChain handlerChain = new HandlerChain();
        handlerChain.addHandler(handler1);
        handlerChain.addHandler(handler2); //构建责任链:handler1->handler2
        handlerChain.handleRequest(new Request());

        handlerChain = new HandlerChain();
        handlerChain.addHandler(handler2); //只请求handler2
        handlerChain.handleRequest(new Request());
    }

输出:
ConcreteHandler1处理请求
ConcreteHandler2处理请求
ConcreteHandler2处理请求

Command命令

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。

定义很难理解,先看示例,如果说责任链模式可以实现事件的冒泡,那么命令模式则可以实现事件的响应。android等终端开发中最为常见。GOF也是以一个客户端程序引入,假设需要开发一个最简单的文本编辑器,左上角“文件”菜单中只有一个“新建”菜单项,点击可以新建并打开文档。
在这里插入图片描述

1、要开发这个客户端程序,得先选择一个漂亮的UI库,如果你是UI库的开发者,如何提供菜单与菜单项控件。

	interface Command { //可以理解为监听器,如OnClickListener
        void execute(); //可以理解为onclick()
    }
    //菜单
    public static class Menu {
        private String label; //菜单标签
        private List<MenuItem> menuItems = new ArrayList<>();

        public Menu(String label) {
            this.label = label;
        }

        public void addItem(MenuItem menuItem) {
            menuItems.add(menuItem);
        }
    }
    //菜单项
    public static class MenuItem {
        private String label; //菜单项标签
        private Command command; //类似于监听器OnClickListener

        public MenuItem(String label) {
            this.label = label;
        }

        public void setCommand(Command command) {
            this.command = command;
        }

        public void click() { //点击菜单项
            command.execute(); //类似调用监听器OnClickListener的onclick方法
        }
    }

从MenuItem控件的click方法看出,这里并没有处理点击菜单项的逻辑代码,而是调用command.execute(),将点击事件传递给Command的实现者。接下来看下客户端开发人员,如何使用这个MenuItem控件。

    //最外层系统应用
    public static class Application {
        private List<Document> documents = new ArrayList<>();

        public void addDocument(Document document) {
            documents.add(document);
        }

        public void removeDocument(Document document) {
            documents.remove(document);
        }
    }

    //文档
    public static class Document {
        public Document() {
            System.out.println("创建文档");
        }

        public void open() {
            System.out.println("打开文档");
        }
    }

    //新建文档命令
    public static class CreateCommand implements Command {
        private Application application;

        public CreateCommand(Application application) {
            this.application = application;
        }

        @Override
        public void execute() { //点击新建文档,真正的逻辑代码在这里
            Document document = new Document();//创建文档
            application.addDocument(document);
            document.open(); //打开文档
        }
    }

    public static void main(String[] args) {
        //创建应用
        Application application = new Application();

        //创建菜单与菜单项
        Menu menu = new Menu("文件");
        MenuItem createMenuItem = new MenuItem("新建");
        menu.addItem(createMenuItem);

        //绑定新建命令
        CreateCommand createCommand = new CreateCommand(application);
        createMenuItem.setCommand(createCommand);

        //触发新建菜单项点击
        createMenuItem.click();
    }

输出:
创建文档
打开文档

2、命令模式支持回滚,撤销
接上例,新建的文档需要回滚,自然是要把它删除。因此创建命令中需要保留下一些创建时的数据(也称为状态status)

	//新建文档命令
    public static class CreateCommand implements Command {
        private Application application;
        private Document document; //保留创建的文档

        public CreateCommand(Application application) {
            this.application = application;
        }

        @Override
        public void execute() {
            document = new Document();
            application.addDocument(document);
            document.open();
        }
        //回滚,撤销
        public void unexecute() {
            if (document != null) {
                application.removeDocument(document);
            }
        }
    }

	public static void main(String[] args) {
        //创建应用
        Application application = new Application();

        //创建菜单与菜单项
        Menu menu = new Menu("文件");
        MenuItem createMenuItem = new MenuItem("新建");
        menu.addItem(createMenuItem);

        //绑定新建命令
        CreateCommand createCommand = new CreateCommand(application);
        createMenuItem.setCommand(createCommand);
        createMenuItem.click();//触发新建菜单项点击

        //新建命令撤销
        createCommand.unexecute();
    }

3、命令模式支持组合命令并排队执行

	public static class MacroCommand implements Command {
        private List<Command> commands = new ArrayList<>();
        @Override
        public void execute() {
            for (Command command : commands) { //排队执行
                command.execute();
            }
        }

        public void addCommand(Command command) {
            commands.add(command);
        }

        public void removeCommand(Command command) {
            commands.remove(command);
        }
    }

    public static void main(String[] args) {
        //创建应用
        Application application = new Application();

        //创建菜单与菜单项
        Menu menu = new Menu("文件");
        MenuItem createMenuItem = new MenuItem("新建");
        menu.addItem(createMenuItem);

        //绑定新建命令
        CreateCommand createCommand1 = new CreateCommand(application);
        CreateCommand createCommand2 = new CreateCommand(application);
		//点击一次创建两个文件(排队执行)
        MacroCommand macroCommand = new MacroCommand(); 
        macroCommand.addCommand(createCommand1);
        macroCommand.addCommand(createCommand2);
        createMenuItem.setCommand(macroCommand);

        //触发新建菜单项点击
        createMenuItem.click();
    }

输出:
创建文档
打开文档
创建文档
打开文档

4、理解示例之后,再看下命令模式结构图(修改了下,更容易理解)。
在这里插入图片描述
对比的示例部分
在这里插入图片描述
对比之下一目了然,不用多说,ConcreteCommand中status用于回滚前保存状态,Invoke调用者是触发事件的人,Receiver接受者是实际处理事件的人。就像电视遥控器和电视的关系,点击电视遥控器,实际是电视开了。

Interpreter解释器

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示 来解释语言中的句子。

解释器模式应用场景最为固定,当一个语言需要解释执行,并且其中的语句可以表示为一个抽象语法树(AST)时,就可以使用解释器模式。
1、先看示例,如执行“x 加 1 减 y”,首先这个语句可以表示为抽象语法树。
在这里插入图片描述
接下来使用解释器模式,其实就是构建语法树,以及遍历执行的过程。(注意是从根节点开始前序遍历)
在这里插入图片描述

	//全局上下文
    public static class Context extends HashMap<String, Integer> {
    }

    interface AbstractExpression {
        int interpret(Context context);
    }

    //变量表达式
    public static class VarExpression implements AbstractExpression {
        private String name;

        public VarExpression(String name) {
            this.name = name;
        }

        @Override
        public int interpret(Context context) {
            return context.get(name);
        }
    }

    //常量表达式
    public static class Constant implements AbstractExpression {
        private int value;

        public Constant(int value) {
            this.value = value;
        }

        @Override
        public int interpret(Context context) {
            return value;
        }
    }

    //加法表达式
    public static class AddExpression implements AbstractExpression {
        private AbstractExpression left;
        private AbstractExpression right;

        public AddExpression(AbstractExpression left, AbstractExpression right) {
            this.left = left;
            this.right = right;
        }

        @Override
        public int interpret(Context context) {
            return left.interpret(context) + right.interpret(context);
        }
    }

    //减法表达式
    public static class SubExpression implements AbstractExpression{
        private AbstractExpression left;
        private AbstractExpression right;

        public SubExpression(AbstractExpression left, AbstractExpression right) {
            this.left = left;
            this.right = right;
        }

        @Override
        public int interpret(Context context) {
            return left.interpret(context) - right.interpret(context);
        }
    }

    public static void main(String[] args) {
        //解释执行x 加 2 减 y
        Context context = new Context();
        context.put("x", 1);
        context.put("y", 3);

        //为了方便理解,这里先参照语法树图,从根节点开始手动构建,后面有扩展
        AbstractExpression root = new SubExpression(
                new AddExpression(
                        new VarExpression("x"), new Constant(2)),
                new VarExpression("y")
        );

        //递归解释执行
        int result = root.interpret(context);
        System.out.println("result:" + result);
    }

输出:
result:0

2、再来理解解释器模式结构
在这里插入图片描述
TerminalExpression终结符表达式,即不能再被分解的元素,如x,1等。
NonterminalExpression非终结符表达式,即可以被再分解的元素,如x+1等。
Context存放全局信息,如变量x,y的值,需要提前设置在Context中,以供递归时使用。
对比示例,其中VarExpression变量表达式如x,y,Constant常量如1,都是TerminalExpression,而AddExpression加法表达式和SubExpression减法表达式,则是NonterminalExpression。

3、最后扩展一下,根据字符串构建简单的语法树。

	//判断是不是非终结符表达式
    public static boolean isNonterminalExpression(String token) {
        return token.equals("加") || token.equals("减");
    }

    //创建终结符表达式
    public static AbstractExpression createTerminalExpression(String token) {
        AbstractExpression terminalExpression = null;
        if (token.chars().allMatch(Character::isDigit)) { //数字
            terminalExpression = new Constant(Integer.parseInt(token));
        } else { //字母
            terminalExpression = new VarExpression(token);
        }
        return terminalExpression;
    }

    //创建非终结符表达式
    public static AbstractExpression createNonterminalExpression(String token, AbstractExpression left, AbstractExpression right) {
        AbstractExpression nonterminalExpression = null;
        if ("加".equals(token)) {
            nonterminalExpression = new AddExpression(left, right);
        } else if ("减".equals(token)) {
            nonterminalExpression = new SubExpression(left, right);
        }
        return nonterminalExpression;
    }

    //构建抽象语法树
    public static AbstractExpression buildSyntaxTree(String text) {
        Stack<AbstractExpression> stack = new Stack<>();
        StringTokenizer tokenizer = new StringTokenizer(text);
        AbstractExpression current, left, right;
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            if (isNonterminalExpression(token)) { //创建非终结符表达式
                left = stack.pop();
                right = createTerminalExpression(tokenizer.nextToken());
                current = createNonterminalExpression(token, left, right);
            } else { //创建终结符表达式
                current = createTerminalExpression(token);
            }
            stack.push(current);
        }
        return stack.pop();
    }

    public static void main(String[] args) {
        //解释执行x 加 2 减 y
        Context context = new Context();
        context.put("x", 1);
        context.put("y", 3);

        //根据字符串文本构建抽象语法树
        AbstractExpression root = buildSyntaxTree("x 加 2 减 y");

        //递归解释执行
        int result = root.interpret(context);
        System.out.println("result:" + result);
    }

Iterator迭代器

提供一种方法顺序访问一个聚合对象中各个元素 , 而又不需暴露该对象的内部表示。

当你想要迭代查询时,可以使用迭代器(听起来像废话),如迭代集合中的元素,迭代学校里的班级,迭代班级里的学生等,不限于集合对象都可以使用迭代器模式。
1、将迭代查询这件事从集合各种操作中统一抽象出来就是GOF对迭代器模式的初衷。
在这里插入图片描述
ConcreteIterator是迭代器即迭代方式,多种迭代方式就有多种迭代器。ConcreteAggregate是聚合类加入了迭代器工厂。在实际代码中,迭代器只是聚合类的查询功能,它们生命周期相同,因此常以内部类形式出现。如:

	//迭代器
    interface Iterator {
        Object first();
        Object next();
        boolean isDone();
        Object currentItem();
    }

    //聚合类(不如叫迭代器工厂)
    interface Aggregate {
        Iterator createIterator();
    }

    //具体聚合类
    public static class Array implements Aggregate{
        private int[] datas;

        public Array(int[] datas) {
            this.datas = datas;
        }

        @Override
        public Iterator createIterator() {
            return new Iterator() { //具体的迭代器(匿名内部类实现)
                private int index = 0;

                @Override
                public Object first() {
                    return Array.this.datas[0];
                }

                @Override
                public Object next() {
                    return Array.this.datas[index++];
                }

                @Override
                public boolean isDone() {
                    return index == Array.this.datas.length;
                }

                @Override
                public Object currentItem() {
                    return Array.this.datas[index];
                }
            };
        }
    }

    public static void main(String[] args) {
        Array arr = new Array(new int[]{1,2,3,4,5});
        Iterator iterator = arr.createIterator();
        while (!iterator.isDone()) {
            System.out.println(iterator.next());
        }
    }

2、JDK中对迭代模式已有支持,同样有Iterator和Iterable接口(对应Aggregate),它们名字相似,相信现在你不会再搞混,前者是迭代器,后者是迭代器工厂。

Mediator中介者

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

GOF讲到的中介者,以客户端程序的页面为例,他们认为页面上多种控件交互,必会造成依赖关系混乱,你中有我,我中有你,于是引入中介者,当所有的控件都只依赖中介者,就解决了依赖混乱的问题。千万不要和命令模式的事件监听搞混,准确说,中介者其实是一种事件总线的思想。
示例,假设页面上有文本框TextView,下拉选框Spinner,清空按钮Button,用户选择下拉选框,文本框及时显示选择信息,用户点击清空按钮,则清空文本框。那么不用事件监听,而改用事件总线如何设计。
在这里插入图片描述
Dialog是中介者,Spinner,TextView,Button等控件都是同事类,Mediator和所有Colleague相互引用,Colleague之间却互不相识,这就是中介者模式的特点。

    //中介者
    interface Mediator {
        void changeEvent(Object event);
    }

    //具体中介者
    public static class Dialog implements Mediator{
        private TextView mTextView;
        private Button mCleanBtn;
        private Spinner mSpinner;

        public Dialog() {
            mTextView = new TextView(this);
            mCleanBtn = new Button(this);
            mSpinner = new Spinner(this);
        }

        @Override
        public void changeEvent(Object event) { //这里是中介者模式的核心,响应事件
            if (event instanceof SpinnerSelectEvent) {
                String selectValue = mSpinner.getValue();
                mTextView.setText(selectValue);
            } else if (event instanceof CleanTextViewEvent) {
                mTextView.setText("");
            }

        }

        //模拟页面测试
        public static void main(String[] args) {
            Dialog dialog = new Dialog();
            dialog.mSpinner.select("武汉"); //选择武汉
            System.out.println("TextView显示:" + dialog.mTextView.getText());
            dialog.mCleanBtn.click(); //清空按钮点击
            System.out.println("TextView显示:" + dialog.mTextView.getText());
        }
    }

    //同事类
    public static abstract class Colleague {
        private Mediator mediator;

        public Colleague(Mediator mediator) {
            this.mediator = mediator;
        }

        public Mediator getMediator() {
            return mediator;
        }
    }

    //下拉选框选中事件
    public static class SpinnerSelectEvent{}
    //下拉选框
    public static class Spinner extends Colleague{
        private String value;

        public Spinner(Mediator mediator) {
            super(mediator);
        }

        public String getValue() {
            return value;
        }

        //模拟页面选中
        public void select(String value) {
            this.value = value;
            getMediator().changeEvent(new SpinnerSelectEvent()); //向总线发送事件
        }
    }

    //清空文本框事件
    public static class CleanTextViewEvent{}
    //清空按钮
    public static class Button extends Colleague {
        public Button(Mediator mediator) {
            super(mediator);
        }

        //模拟页面点击
        public void click() {
            getMediator().changeEvent(new CleanTextViewEvent());//向总线发送事件
        }
    }

    //文本
    public static class TextView extends Colleague {
        private String text = "";

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public TextView(Mediator mediator) {
            super(mediator);
        }
    }

输出:
TextView显示:武汉
TextView显示:

Memento备忘录

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。 这样以后就可将该对象恢复到原先保存的状态。

GOF依然以客户端程序为例(稍有改动),当一个图形从A点移动到B点,此时撤销,图形则回到A点,当需要先保存,再还原到原始状态的场景,可以使用备忘录模式。
在这里插入图片描述
1、备忘录模式结合命令模式,实现客户端操作:
(1)当发出移动命令MoveCommand,图形控件View开始移动,先保存原始位置point到备忘录Memento,再改变位置到targetPoint。
(2)当发出撤销命令,图形控件View从备忘录中获取原始位置point,然后改变位置。

	public static class Point {
        private int x = 0;
        private int y = 0;

        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public String toString() {
            return "("+x + ","+ y + ")";
        }
    }

    //备忘录
    public static class Memento {
        private Point status;

        public Point getStatus() {
            return status;
        }

        public void setStatus(Point status) {
            this.status = status;
        }
    }

    //图形,作为原发器Originator
    public static class View {
        private Point point;

        public void setPoint(Point point) {
            this.point = point;
        }

        //模拟图形渲染
        public void render() {
            System.out.println("图形位置" + point);
        }

        //备忘录保存当前状态
        public Memento createMemento() {
            Memento memento = new Memento();
            memento.setStatus(point);
            return memento;
        }

        //从备忘录当中恢复状态
        public void setMemento(Memento memento) {
            point = memento.getStatus();
        }
    }

    //移动命令,作为负责人Caretaker
    public static class MoveCommand {
        private View view;
        private Point targetPoint;
        private Memento memento;

        public MoveCommand(View view, Point targetPoint) {
            this.view = view;
            this.targetPoint = targetPoint;
        }

        public void execute() {
            memento = view.createMemento(); //移动之前存储位置
            view.setPoint(targetPoint);//移动到指定位置
            view.render(); //渲染图形
        }

        public void unexecute() {
            view.setMemento(memento); //恢复状态
            view.render(); //重新渲染;
        }
    }

    public static void main(String[] args) {
        View view = new View();
        view.setPoint(new Point(50, 50)); //图形初始位置50, 50

        //模拟用户移动图形到100, 100
        MoveCommand moveCommand2 = new MoveCommand(view, new Point(100, 100));
        moveCommand2.execute();

        //模拟用户撤销回到上一步50, 50
        moveCommand2.unexecute();
    }

输出:
图形位置(100,100)
图形位置(50,50)

2、理解示例后,对比理解备忘录模式结构图
在这里插入图片描述
(1)备忘录Memento用来保存状态.
(2)当一个对象需要在业务操作中需要保存状态,则在原有的的属性方法上加入创建和还原备忘录的方法,就变成了原发器Originator,如View。
(3)保存出来的状态存放的位置就是负责人Caretaker,如MoveCommand。这个角色很灵活,可以用客户端代替。
(4)当需要还原的不止一步时,Caretaker中就需要使用memento集合保存状态列表。上例中MoveCommand不适用,一个命令只能改变一次状态。

未完待续

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值