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

本文精讲23种设计模式,包括创建型、结构型及行为型模式,通过具体实例解析每种模式的应用场景与实现原理。

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

Observer观察者

定义对象间的一种一对多的依赖关系 ,当一个对象的状态发生改变时 , 所有依赖于它的对象 都得到通知并被自动更新。

1、以客户端程序开发为例,同一份数据有多个不同的视图展示,如列表(ListView),条状图(BarChar)等。当模型类(DataModel)的数据发生变化,所有关联的视图都要跟着变化。

  • 按照通常设计,模型类依赖所有视图,每多一个视图类,就得在模型类中多引用一种视图。当数据发生变化,依次调用所有的视图渲染方法。如果这么做,你会发现模型类中修改数据的核心方法,会变得越来越长,难以维护,即使如此,也不是全无好处,这也是控制整体事务最简单的方式。
  • 按照观察者模式设计,也称为发布-订阅 ( publish-subscribe),所有视图类先注册到模型类中,一旦数据发生变化,模型类发布通知,所有视图类收到通知后,渲染视图。
    在这里插入图片描述
    图中主要有两个角色,Subject(目标)为发生数据改变的对象,Observer(观察者)为响应数据变化的对象。
	//目标
    public static abstract class Subject {
        private List<Observer> observers = new ArrayList<>();

        public void attach(Observer observer) { //注册观察者
            observers.add(observer);
        }

        public void detach(Observer observer) {
            observers.remove(observer);
        }

        public void notifyObservers() { //通知所有观察者
            for (Observer observer : observers) {
                observer.update();
            }
        }
    }

    //观察者
    interface Observer {
        void update();
    }

    //数据模型
    public static class DataModel extends Subject{
        String data = "";

        public void setData(String data) {
            this.data = data;
        }

        public String getData() {
            return data;
        }
    }

    //列表视图
    public static class ListView implements Observer{
        private DataModel model;

        public ListView(DataModel model) {
            this.model = model;
        }

        @Override
        public void update() {
            System.out.println("ListView渲染DataModel的数据:" + model.getData());
        }
    }

    //柱状图视图
    public static class BarChar implements Observer{
        private DataModel model;

        public BarChar(DataModel model) {
            this.model = model;
        }

        @Override
        public void update() {
            System.out.println("BarChar渲染DataModel的数据:" + model.getData());
        }
    }

    public static void main(String[] args) {
        DataModel model = new DataModel();
        ListView listView = new ListView(model);
        BarChar barChar = new BarChar(model);
        //将所有视图注册到数据模型中
        model.attach(listView);
        model.attach(barChar);

        //数据变化
        model.setData("test1");
        model.notifyObservers();
    }

输出:
ListView渲染DataModel的数据:test1
BarChar渲染DataModel的数据:test1

注意:
发布-订阅 ( publish-subscribe)有两种传递数据的模型,即”推“和”拉“,上例中,Subject通知所有Observer时(notifyObservers()),只是发送数据变化的消息(observer.update()),观察者收到消息后,主动拉取变化数据(model.getData())响应。另外一种模型,Subject通知所有Observer时,不仅发送消息而且推送变化的数据(observer.update(data)),观察者收到数据直接响应。由此可知,如果采用Subject推送模型,具体Subject中就去掉了具体Observer的依赖。
实际项目中,”推“和”拉“常常没有严格区分,如Subject发送消息,推送变化数据主键,Observer接收到数据主键,再根据数据主键拉取数据。

2、和迭代器模式一样,JDK也内置了观察者模式,java.util.Observable和java.util.Observer。
在这里插入图片描述
对应示例,和Subject和Observer结构基本一致,区别在于JDK内置的Observer的update方法,传入了Observable(参数o),也就是具体Observer去掉了具体Subject的依赖,改为参数传入。不仅如此,JDK的这种设计,还可以应对一个Observer对应多个Subject的情况,如一个列表数据同时来源于两个数据模型。

	//数据模型1
    public static class DataModel1 extends Observable {
        String data = "";

        public void setData(String data) {
            this.data = data;
            //必须设置change标志,否则不会调用Observer的update方法
            setChanged();
        }

        public String getData() {
            return data;
        }
    }

    //数据模型1
    public static class DataModel2 extends Observable {
        String data = "";

        public void setData(String data) {
            this.data = data;
            //必须设置change标志,否则不会调用Observer的update方法
            setChanged();
        }

        public String getData() {
            return data;
        }
    }


    //列表视图
    public static class ListView implements Observer {
        @Override
        public void update(Observable o, Object arg) {
            if (o instanceof DataModel1) {
                DataModel1 model = (DataModel1) o;
                System.out.println("ListView渲染DataModel1的数据:" + model.getData());
            } else if (o instanceof DataModel2) {
                DataModel2 model = (DataModel2) o;
                System.out.println("ListView渲染DataModel2的数据:" + model.getData());
            }
        }
    }

    public static void main(String[] args) {
        //一个视图有多钟数据来源
        DataModel1 model1 = new DataModel1();
        DataModel2 model2 = new DataModel2();
        ListView listView = new ListView();

        model1.addObserver(listView);
        model2.addObserver(listView);

        model1.setData("test1");
        model1.notifyObservers();

        model2.setData("test2");
        model2.notifyObservers();
    }

输出:
ListView渲染DataModel1的数据:test1
ListView渲染DataModel2的数据:test2

State状态

允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

1、以TCP连接为例,假设对一个TCPConnection有三种操作,打开open(),确认ackownledge(),关闭close()。每一种操作在不同状态下(CLOSED,LISTEN,ESTABLISED等),有不同处理。那么按照通常的设计,在每个操作方法中都写上一系列状态判断的代码,如IF状态THEN处理…这种设计,每新增一种状态要修改所有操作代码,难以维护。这时,当一个操作中包含大量状态判断,可以使用状态模式。我们尝试将所有状态下的处理代码抽离出来变成各种状态类(单例)。
在这里插入图片描述

	//TCP连接
    public static class TCPConnection {
        private TCPState state;

        public TCPConnection() {
            state = TCPClosed.getInstance(); //初始化close状态
        }
        //改变状态
        public void setState(TCPState state) {
            this.state = state;
        }
        //打开连接
        public void open() {
            state.open(this);
        }
        //关闭连接
        public void close() {
            state.close(this);
        }
        //确认连接
        public void ackownledge() {
            state.ackownledge(this);
        }
    }

    //TCP连接状态
    public static abstract class TCPState {
        public void open(TCPConnection conn) {} //默认不处理
        public void close(TCPConnection conn) {} //默认不处理
        public void ackownledge(TCPConnection conn) {} //默认不处理
    }

    //CLOSE状态
    public static class TCPClosed extends TCPState {
        private static final TCPClosed INSTANCE = new TCPClosed();
        private TCPClosed(){}
        public static TCPClosed getInstance() {
            return INSTANCE;
        }
        @Override
        public void open(TCPConnection conn) {
            System.out.println("开始监听客户端连接,状态:CLOSE->LISTEN");
            conn.setState(TCPListen.getInstance());
        }
    }

    //LISTEN状态
    public static class TCPListen extends TCPState {
        private static final TCPListen INSTANCE = new TCPListen();
        private TCPListen(){}
        public static TCPListen getInstance() {
            return INSTANCE;
        }

        @Override
        public void ackownledge(TCPConnection conn) {
            System.out.println("确认已连接,状态:LISTEN->ESTABLISED");
            conn.setState(TCPEstiablished.getInstance());//状态改变:LISTEN->ESTABLISED
        }
    }

    //ESTABLISED状态
    public static class TCPEstiablished extends TCPState {
        private static final TCPEstiablished INSTANCE = new TCPEstiablished();
        private TCPEstiablished(){}
        public static TCPEstiablished getInstance() {
            return INSTANCE;
        }

        @Override
        public void close(TCPConnection conn) {
            System.out.println("关闭连接,状态:ESTABLISED->CLOSE");
            conn.setState(TCPClosed.getInstance());//状态改变:LISTEN->ESTABLISED
        }
    }

    public static void main(String[] args) {
        TCPConnection conn = new TCPConnection();
        conn.open();
        conn.ackownledge();
        conn.close();
    }

输出:
开始监听客户端连接,状态:CLOSE->LISTEN
确认已连接,状态:LISTEN->ESTABLISED
关闭连接,状态:ESTABLISED->CLOSE

2、理解示例之后,再看状态模式结构图
在这里插入图片描述

其中Context是维护状态的地方,对应示例中的TCPConnection,State根据不同状态,定义具体操作,对应示例中的TCPState。TCPConnection示例中状态切换是由具体State决定,Context无感。状态模式还有另一种使用方式,由Context决定状态切换。如当我们使用画图工具时,总是先给画笔选择图形,然后在画布点击,就会渲染出不同的图形。这个例子中画笔就是Context,选择的图形就是State。

Strategy策略

定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独 立于使用它的客户而变化。

以文章排版为例,目前有两种策略,第一种指定单词数量换行,第二种遇到指定标识符换行。通常的设计方法,在排版方法中传入排版类型。再通过if else或者switch判断排版类型后,写具体排版算法。那么如果加入第三种排版策略,则需要修改整个排版方法。当想要将具体算法从业务中抽离出来时,可以使用策略模式。
在这里插入图片描述

//排版
    public static class Composition {
        private String text;
        private Compositor compositor;
        public Composition(String text) {
            this.text = text;
        }

        public void setCompositor(Compositor compositor) {
            this.compositor = compositor;
        }

        public void repair() { //重新排版
            compositor.compose(text);
        }
    }

    //排版策略
    interface Compositor {
        void compose(String text);
    }

    //排版策略1:满足指定单词数量换行
    public static class NumCompositor implements Compositor{
        private int num;
        public NumCompositor(int num) {
            this.num = num;
        }

        @Override
        public void compose(String text) {
            StringTokenizer tokenizer = new StringTokenizer(text);
            int i = 0;
            while (tokenizer.hasMoreTokens()) {
                String token = tokenizer.nextToken();
                if (++i % num == 0) {//满足指定的单词数量换行
                    System.out.println();
                } else {
                    System.out.print(token + " ");
                }
            }
        }
    }

    //排版策略2:遇到指定标识字符换行
    public static class CharCompositor implements Compositor {
        private char flag;
        public CharCompositor(char flag) {
            this.flag = flag;
        }

        @Override
        public void compose(String text) {
            String[] letters = text.split("");
            for (String letter : letters) {
                if (letter.equals(Character.toString(flag))) { //遇到指定标识字符换行
                    System.out.println(flag);
                } else {
                    System.out.print(letter);
                }
            }
        }
    }

    public static void main(String[] args) {
        String text = "Some doctors said that if you over time on using it, it may cause your eyesight is becoming progressively worse. In addition, you can see unusual scenery in our society. For example, people become indifferent and cold blooded because some people over depend on phone to talk each other. It is not only affecting your physical health but also affecting your spirit health. What’s worse, phone will produce some harmful radiation. It is really hurt our brain because it may cause you memory less and even get a cancer.";
        Composition composition = new Composition(text);
        composition.setCompositor(new NumCompositor(5)); //5个词换一行
        composition.repair();

        System.out.println("\n重新排版:");
        composition.setCompositor(new CharCompositor('.')); //遇到句号换行
        composition.repair();
    }

2、理解完示例,再看下策略模式结构图
![在这里插入图片描述](https://img-blog.csdnimg.cn/fed72a4625574c38bf3b2e724821efc9.png在这里插入图片描述
对比示例,Context策略上下文,用于维护具体策略引用,并提供具体策略需要的数据,对应的是Composition。Strategy策略,用于封装算法,对应的是Compositor,其中的算法方法algorithmInterface()对应的是Compositor的compose()。

Template Method模版方法

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

1、以开发一个文档应用为例,想要搭建一个有Application和Document组成的框架,目前只有一个功能,就是Appliation负责打开文件并读出内容,就能形成文档Document。框架支持多种文档类型,如word,excel等。这时,当想要把不变的操作做成一个框架,其中变化的操作留给多种子类实现时,可以使用模版方法模式。
在这里插入图片描述

	public static abstract class Document {
        public void save() {
            System.out.println("保存文档");
        }

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

        public void close() {
            System.out.println("关闭文档");
        }

        public abstract void doRead();
    }

    //不同类型文档读取方式不同,如word,excel
    public static class WordDocument extends Document {
        @Override
        public void doRead() {
            System.out.println("读取Word文档");
        }
    }

    public static abstract class Application {
        private List<Document> docs = new ArrayList<>();

        //模版方法
        public void openDocument(String name) {
            if (!canOpenDocument(name)) { //判断文件类型能否打开
                System.out.println(name + "无法打开,文件类型不支持");
                return;
            }
            Document doc = createDocument(); //工厂方法模式
            if (doc != null) {
                docs.add(doc);
                doc.open();
                doc.doRead();
            }

        }
        //判断文件类型能否打开
        public abstract boolean canOpenDocument(String name);
        //创建文档
        public abstract Document createDocument();
    }

    public static class WordApplication extends Application {
        @Override
        public boolean canOpenDocument(String name) { //判断是否为word文件
            String extName = name.substring(name.lastIndexOf("."));
            return extName.equals(".doc") || extName.equals(".docs");
        }

        @Override
        public Document createDocument() {
            return new WordDocument();
        }
    }

    public static void main(String[] args) {
        WordApplication app = new WordApplication();
        app.openDocument("test.doc");
        app.openDocument("test.xls");
    }

输出:
打开文档
读取Word文档
test.xls无法打开,文件类型不支持

2、理解完示例,再看下模版方法模式结构图
在这里插入图片描述
对比示例,AbstractClass定义一个框架,对应的是Application,ConcreteClass定义的是框架中特定步骤,对应的是WordApplication。Application的openDocument()是一个模版方法,它使用了一些抽象操作实现算法,如canOpenDocument()和createDocument()。另外createDocument()和createDocument()同时也是一个抽象工厂方法。

Visitor访问者

表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提 下定义作用于这些元素的新操作。

(访问者模式号称最复杂的设计模式,我们需要讲的更细些)
从示例开始,首先程序中已有了电脑三大配件类,显示器,主机,键盘鼠标。它们属性和方法都完全不同,即使是价格和型号字段也都不同,也没有继承或实现相同的父类或接口,可以说他们只是在语义上有关联(都属于电脑部件),实际代码结构上没有任何关联的三个独立类。
在这里插入图片描述
现在要给所有电脑部件增加三个操作,检查型号checkType(),打印配置信息printInfo(),计算总价getTotalPrice()。通常的设计,每个部件都要至少新增两个方法,但如此做,会将这些操作散落到各个部件中,难以维护,如:检查型号改变,打印信息格式变化等,都要重新修改所有部件。有更好的设计方法么?
在这里插入图片描述
这时,当要对一些已有类新增操作(这些类可以完全无关),而这些新增的操作大多要求对不同节点进行不同的处理时,可以使用访问者模式。将每个类中新增相关操作封装到一个独立的对象中,这个新的对象就是访问者Visitor。
在这里插入图片描述

	//访问者,以方法参数的形式,获取要新增操作的所有元素
    interface Visitor {
        void visitDisplay(Display display);
        void visitMainFrame(MainFrame mainFrame);
        void visitKeyboardMouse(KeyboardMouse keyboardMouse);
    }

    //访问元素
    interface Element {
        void accept(Visitor visitor);
    }

    //显示器
    public static class Display implements Element{
        private String displayType; //型号
        private double displayPrice; //价格
        private String size; //尺寸

        public Display(String displayType, double displayPrice, String size) {
            this.displayType = displayType;
            this.displayPrice = displayPrice;
            this.size = size;
        }

        public String getDisplayType() {
            return displayType;
        }

        public double getDisplayPrice() {
            return displayPrice;
        }

        public String getSize() {
            return size;
        }

        @Override
        public void accept(Visitor visitor) {
            visitor.visitDisplay(this);
        }
    }

    //主机
    public static class MainFrame implements Element {
        private String mainFrameType; //型号
        private double mainFramePrice; //价格
        private String cpu; //cpu
        private String memory; //内存

        public MainFrame(String mainFrameType, double mainFramePrice, String cpu, String memory) {
            this.mainFrameType = mainFrameType;
            this.mainFramePrice = mainFramePrice;
            this.cpu = cpu;
            this.memory = memory;
        }

        public String getMainFrameType() {
            return mainFrameType;
        }

        public double getMainFramePrice() {
            return mainFramePrice;
        }

        public String getCpu() {
            return cpu;
        }

        public String getMemory() {
            return memory;
        }

        @Override
        public void accept(Visitor visitor) {
            visitor.visitMainFrame(this);
        }
    }

    //键盘鼠标
    public static class KeyboardMouse implements Element {
        private String keyboardMouseType; //型号
        private double keyboardMousePrice; //价格

        public KeyboardMouse(String keyboardMouseType, double keyboardMousePrice) {
            this.keyboardMouseType = keyboardMouseType;
            this.keyboardMousePrice = keyboardMousePrice;
        }

        public String getKeyboardMouseType() {
            return keyboardMouseType;
        }

        public double getKeyboardMousePrice() {
            return keyboardMousePrice;
        }

        @Override
        public void accept(Visitor visitor) {
            visitor.visitKeyboardMouse(this);
        }
    }

    //使用组合模式,组合多种元素,一起访问
    public static class CompsiteElement implements Element {
        private List<Element> elements = new ArrayList<>();

        public void addElement(Element element) {
            elements.add(element);
        }

        public void removeElement(Test43.Element element) {
            elements.remove(element);
        }

        @Override
        public void accept(Visitor visitor) {
            for (Element element : elements) {
                element.accept(visitor);
            }
        }
    }

    //计算总价
    public static class GetTotalPriceVisitor implements Visitor {
        private double totalPrice = 0;
        public double getTotalPrice() {
            return totalPrice;
        }

        @Override
        public void visitDisplay(Display display) {
            totalPrice += display.getDisplayPrice();
        }

        @Override
        public void visitMainFrame(MainFrame mainFrame) {
            totalPrice += mainFrame.getMainFramePrice();
        }

        @Override
        public void visitKeyboardMouse(KeyboardMouse keyboardMouse) {
            totalPrice += keyboardMouse.getKeyboardMousePrice();
        }
    }

    //检查型号
    public static class CheckTypeVisitor implements Visitor {
        @Override
        public void visitDisplay(Display display) {
            if (display.getDisplayType().equals("三星")) {
                System.out.println("显示器型号检查通过");
            } else {
                System.out.println("显示器型号检查不通过");
            }
        }

        @Override
        public void visitMainFrame(MainFrame mainFrame) {
            if (mainFrame.getMainFrameType().equals("华为")) {
                System.out.println("主机型号检查通过");
            } else {
                System.out.println("主机型号检查不通过");
            }
        }

        @Override
        public void visitKeyboardMouse(KeyboardMouse keyboardMouse) {
            if (keyboardMouse.getKeyboardMouseType().equals("雷蛇")) {
                System.out.println("键盘鼠标型号检查通过");
            } else {
                System.out.println("键盘鼠标型号检查不通过");
            }
        }
    }

    //打印配置信息
    public static class PrintInfoVisitor implements Visitor {
        @Override
        public void visitDisplay(Display display) {
            System.out.println("-----------显示器-----------");
            System.out.println("型号:" + display.getDisplayType());
            System.out.println("尺寸:" + display.getSize());
        }

        @Override
        public void visitMainFrame(MainFrame mainFrame) {
            System.out.println("-----------主机-----------");
            System.out.println("型号:" + mainFrame.getMainFrameType());
            System.out.println("CPU:" + mainFrame.getCpu());
            System.out.println("内存:" + mainFrame.getMemory());
        }

        @Override
        public void visitKeyboardMouse(KeyboardMouse keyboardMouse) {
            System.out.println("-----------键盘鼠标-----------");
            System.out.println("型号:" + keyboardMouse.getKeyboardMouseType());
        }
    }

    public static void main(String[] args) {
        //新建显示器,主机,键盘鼠标
        Display display = new Display("三星", 2000, "24寸");
        MainFrame mainFrame = new MainFrame("联想", 4000, "英特尔i7", "16g");
        KeyboardMouse keyboardMouse = new KeyboardMouse("雷蛇", 1500);

        CompsiteElement compsiteElement = new CompsiteElement();
        compsiteElement.addElement(display);
        compsiteElement.addElement(mainFrame);
        compsiteElement.addElement(keyboardMouse);

        //检查配置
        compsiteElement.accept(new CheckTypeVisitor());
        //打印配置信息
        compsiteElement.accept(new PrintInfoVisitor());
        //计算总价
        GetTotalPriceVisitor visitor = new GetTotalPriceVisitor();
        compsiteElement.accept(visitor);
        System.out.println("总价:" + visitor.getTotalPrice());
    }

输出:
显示器型号检查通过
主机型号检查不通过
键盘鼠标型号检查通过
-----------显示器-----------
型号:三星
尺寸:24寸
-----------主机-----------
型号:联想
CPU:英特尔i7
内存:16g
-----------键盘鼠标-----------
型号:雷蛇
总价:7500.0

2、理解示例之后,再看访问者模式结构图
在这里插入图片描述
对比示例,具体Element是指需要增加操作的元素(也就是将要被访问的元素),如Display、MainFrame和KeyboardMouse。具体Visitor是指需要增加的操作,如:GetTotalPriceVisitor、CheckTypeVisitor和PrintInfoVisitor。
比较特殊的是ObjectStructure,它是指一种的对象结构,能够访问所有元素,如示例中的CompsiteElement(使用了组合模式),或者是一种独立的复合对象,如Computer,这个角色非常灵活。

	//电脑
    public static class Computer {
        private Display display; //显示器
        private MainFrame mainFrame; //主板
        private KeyboardMouse keyboardMouse; //鼠标键盘

        public Computer(Display display, MainFrame mainFrame, KeyboardMouse keyboardMouse) {
            this.display = display;
            this.mainFrame = mainFrame;
            this.keyboardMouse = keyboardMouse;
        }

        //检查型号
        public void checkType() {
            CheckTypeVisitor visitor = new CheckTypeVisitor();
            display.accept(visitor);
            mainFrame.accept(visitor);
            keyboardMouse.accept(visitor);
        }

        //打印配置信息
        public void printInfo() {
            PrintInfoVisitor visitor = new PrintInfoVisitor();
            display.accept(visitor);
            mainFrame.accept(visitor);
            keyboardMouse.accept(visitor);
        }

        //打印总价
        public void printTotalPrice() {
            GetTotalPriceVisitor visitor = new GetTotalPriceVisitor();
            display.accept(visitor);
            mainFrame.accept(visitor);
            keyboardMouse.accept(visitor);
            System.out.println("总价:" + visitor.getTotalPrice());
        }
    }

    public static void main(String[] args) {
        //新建显示器,主机,键盘鼠标
        Display display = new Display("三星", 2000, "24寸");
        MainFrame mainFrame = new MainFrame("联想", 4000, "英特尔i7", "16g");
        KeyboardMouse keyboardMouse = new KeyboardMouse("雷蛇", 1500);

        Computer computer = new Computer(display, mainFrame, keyboardMouse);
        computer.checkType();
        computer.printInfo();
        computer.printTotalPrice();
    }

23种设计模式已经讲完,只剩下总结,加油,未完待续

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值