Architect's Diary - Deep Understanding of Software Design Patterns | JD Cloud Technical Team

Author: JD Retail Liu Huiqing

1 Design Patterns and Programming Languages

1.1 What is a design pattern

Design pattern: The solution to common problems faced by software developers in software development is summed up after a long period of experimentation. It makes the design more flexible and elegant, and the reusability is better. From a practical point of view, it represents the best practice for a certain class of problems.

What problems does the design pattern solve in the development process, and how does it solve it?

Its core is: reuse and decoupling. Make unstable depend on stable and concrete depend on abstraction, so as to enhance the ability of software design to adapt to change.

1.2 What is a programming paradigm

To explore the relationship between design patterns and programming languages, we have to start with programming paradigms. The term programming paradigm first came from Robert Floyd's Turing Award speech in 1979. It is a programmer's view of a program, representing the programmer's view of how a program should be constructed and executed, and software modeling methods and architectural styles. There is a close relationship.

There are currently three mainstream programming paradigms:

1. Structured programming

2. Object-oriented programming

3. Functional programming

The relationship between these programming paradigms is as follows:

1. At first, it was unstructured programming. Instructions (goto instructions) can be jumped at will, and data can be quoted at will. Later, with structured programming, people removed the goto statement, which constrained the directionality of instructions. The processes are one-way, but the data can be accessed globally;

2. Later in object-oriented programming, people simply placed the data and its tightly coupled methods within a logical boundary, constrained the scope of the data, and searched by relationships;

3. When it comes to functional programming, people constrain the variability of data, describe data through the combination of a series of functions, and arrange the mapping rules from source to target, and it is stateless in the middle;

Programming paradigms are abstract, and programming languages ​​are concrete. The programming paradigm is the idea behind the programming language, which should be embodied by the programming language. The mainstream programming paradigm of the C language is structured programming, while the mainstream programming paradigm of the Java language is object-oriented programming. Later, Java8 began to support Lambda expressions, integrating the content of the functional programming paradigm. At the same time, the newly born language supports Multi-paradigm, such as Scala, Go and Rust, etc.

From structured programming to object-oriented programming, and then to functional programming, the level of abstraction is getting higher and higher (farther and farther away from the Turing machine model), and the distance from domain problems is getting closer. Intuitively speaking, the efficiency of solving practical problems has increased, while the flexibility and execution efficiency have decreased.

Design patterns can be implemented in any language. However, due to the differences in languages, not every language can perfectly or uniformly implement various design patterns. For example, there is a strategy pattern in Java. It is because Java 8 does not support method passing, and a method cannot be passed as a parameter to others, so there is a strategy pattern. Languages ​​such as JavaScript can pass functions directly, so there is no need to create a strategy pattern.

1.3 What is a polymorphic feature

Object-oriented programming languages ​​have three major features: encapsulation, inheritance, and polymorphism.

1. Encapsulation means information hiding or data protection. The "data structure" authorizes the outside to access its internal data only through the methods (functions) provided by the "data structure" by exposing limited access interfaces;

2. The advantage of inheritance is that code reuse can be achieved, but it should not be overused. If the inheritance level is too deep, the code readability and maintainability will deteriorate. Therefore, it is recommended to use less inheritance and more combination mode;

3. Polymorphism can be divided into variable polymorphism, method polymorphism, and class polymorphism. Usually the emphasis is on class polymorphism. Polymorphic implementation means that a subclass can replace the parent class and call the method implementation of the subclass during the actual code running process;

Polymorphism can be said to be the most important feature in object-oriented. It is the core to solve the problem of tight coupling in the project and improve the scalability and reusability of the code. It is the code implementation of many design patterns, design principles and programming skills. Base.

The more intuitive understanding of polymorphism is to complete a certain action. When different objects are completed, different states will be generated. The scope of action can be the parameters of the method and the return type of the method.

The feature of polymorphism also requires the programming language to provide a special grammatical mechanism to realize. Polymorphism in Java can be realized by "subclass inheriting parent class + subclass rewriting parent class method + parent class reference pointing to subclass object". It can also be realized by means of "interface syntax". In C++, the virtual (virtual function) keyword is used to implement it. Some dynamic languages ​​such as Python can also be implemented through duck-typing syntax, and the "hidden interface" in Go language is also duck-typing.

In the Python language, an example of polymorphism is as follows:

class MyFile:
    def write(self):
        print('I write a message into file.')
        
class MyDB:
    def write(self):
        print('I write data into db. ')
        
def doIt(writer):
    writer.write()

def demo():
    myFile= MyFile()
    myDB = MyDB()
    doIt(myFile)
    doIt(myDB )

Two Design Patterns and Architecture Patterns

2.1 Understanding Architecture Patterns

A general and reusable solution to common problems in software architecture for a given context can provide corresponding guidance for all aspects of designing large software systems. It not only shows the correspondence between software requirements and software structure, but also specifies the organization and topology of the entire software system, and provides some basic principles for design decisions. Common architectural design patterns are as follows:

architectural pattern schema description Applicable scene
Layered pattern Used for structured programs that can be decomposed into subtasks, each at a particular level of abstraction, and each level provides services for the previous level. desktop applications; e-commerce web applications; mobile apps;
Client-server pattern A server will provide services to multiple clients. Clients request services from servers, and servers provide related services to those clients. Online applications such as email, document sharing, and banking; IPC-based applications;
Master-slave pattern The master node distributes the work to the same slave nodes and calculates the final result based on the results returned by the slave nodes. Database master-slave replication; In-process multi-thread scheduling;
Pipe-filter pattern For constructing systems that generate and process data streams. Each processing step contains a filter component. The data to be processed is passed through the pipeline. These pipes can be used for buffering or synchronization purposes. translater;
Broker pattern Construct distributed systems by decoupling components. message middleware;; agent software in network transmission
Peer-to-peer pattern Each component is called a peer node. A peer node can act as both a client (requesting services from other peers) and a server (providing services to other peers). file sharing network; multimedia protocol;
Event-bus pattern (Event-bus pattern) In the subscription publishing mode, the event source publishes the message to a specific channel on the event bus, and the listener subscribes to a specific channel. Notification Service; Registry;
Model-view-controller pattern (Model-view-controller pattern) The MVC pattern, which decouples components and allows efficient code reuse web application architecture; GUI application;
Blackboard pattern Very useful for problems without a defined solution strategy, all components can reach the blackboard. Components can generate new data objects that are added to the board. Components look for specific types of data on the blackboard and find this data by pattern matching against existing knowledge sources. Voice recognition; Vehicle recognition and tracking;
Interpreter pattern Used to design a program component that interprets a special-purpose language. Database query languages ​​such as SQL are used to describe communication protocols;

2.2 Understanding Design Patterns

In 1995, four seniors in the programming world co-authored a book called "Design Patterns: Elements of Reusable Object-Oriented Software", which translates to "Design Patterns: The Foundation of Reusable Object-Oriented Software" , A total of 23 design patterns are included in the book. This book is an important milestone in the field of software research and development. The four authors who co-authored this book are called GoF (Gang of Four) in the industry, so this book is also called GoF design patterns.

Design patterns are classified according to their purpose: creation, structure, and behavior, and according to their scope of action: class patterns and object patterns.

1. Creational mode: used to create objects, is to separate the creation and use of objects. In this way, the coupling degree of the system is reduced, and users do not need to pay attention to the details of object creation, and the creation of objects is completed by related factories.

2. Structural pattern: Describes how to form a larger structure between classes, objects, and interfaces in a certain layout.

3. Behavioral mode: It is used to describe the complex process control of the program at runtime, that is, to describe how multiple classes or objects cooperate with each other to complete tasks that cannot be completed by a single object alone. It involves the allocation of responsibilities between algorithms and objects .

The 23 design patterns are as follows:

type schema name schema description
Creational Singleton A certain class can only generate one instance, which provides a global access point for external access to the instance, and its extension is the limited multiple instance pattern.
Factory Method Pattern (Factory Method) Define an interface for creating products, and subclasses decide what products to produce.  
Abstract Factory Pattern (AbstractFactory) Provides an interface for creating product families, each of which can produce a series of related products.  
Builder mode (Builder) Decompose a complex object into several relatively simple parts, then create them separately according to different needs, and finally build the complex object.  
Prototype Using an object as a prototype, multiple new instances similar to the prototype are cloned by duplicating it.  
Structural Adapter pattern (Adapter) Convert the interface of a class to another interface that the client wants, so that those classes that could not work together due to incompatible interfaces can work together.
Bridge mode (Bridge) Separate abstraction from implementation so that they can vary independently. It is implemented by replacing the inheritance relationship with the composition relationship, thereby reducing the coupling degree of the two variable dimensions of abstraction and implementation.  
Composite mode (Composite) Combining objects into tree-like hierarchies gives users consistent access to individual and composite objects.  
Decorator mode (Decorator) Dynamically add some responsibilities to the object, that is, add its additional functions.  
Appearance mode (Facade) Provide a consistent interface for multiple complex subsystems, making these subsystems more accessible.  
Hengyuan mode (Flyweight) Use sharing technology to effectively support the reuse of a large number of fine-grained objects.  
Proxy mode (Proxy) Provides a proxy for an object to control access to that object. That is, the client accesses the object indirectly through the proxy, thereby restricting, enhancing or modifying some characteristics of the object.  
Behavioral Template Method Pattern (TemplateMethod) Define the algorithm skeleton in an operation, and defer some steps of the algorithm to subclasses, so that subclasses can redefine some specific steps of the algorithm without changing the structure of the algorithm.
Strategy 定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。  
命令模式(Command) 将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。  
职责链模式(Chain of Responsibility) 把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。  
状态模式(State) 允许一个对象在其内部状态发生改变时改变其行为能力。  
观察者模式(Observer) 多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。  
中介者模式(Mediator) 定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。  
迭代器模式(Iterator) 提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。  
访问者模式(Visitor) 在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。  
备忘录模式(Memento) 在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。  
解释器模式(Interpreter) 提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。  

2.3 小结

•架构模式更像是宏观战略层面的设计,设计模式则更像是战略目标拆解出来的具体任务的实现方案;

•软件架构是软件的一种搭建形式,往往规定了软件的模块组成,通信接口(含通信数据结构),组件模型,集成框架等,往往规定了具体的细节;

•设计模式是一种软件的实现方法,是一种抽象的方法论,是为了更好的实现软件而归纳出来的有效方法;

•实现一种软件架构,不同组成部分可能用到不同的设计模式,某个部分也可能可以采用不同的设计模式来实现;

三 应用实践指南

3.1 适用场景

不使用设计模式也能实现业务诉求,系统也能够正常运行,为什么要使用设计模式呢?

是的,相当一部分场景是不需要进行设计模式的引入的,比如:业务逻辑简单,业务演进方向不明朗,或者就是一个不需要经常迭代的功能点。但当我们遇到了复杂问题设计的时候,就需要借助前人的经验了,而设计模式就是前人为我们沉淀总结的各种常见问题的解决方案。

那么多种设计模式,难道我需要全部系统的学习实现一遍,都要闭着眼睛就能写出来吗?其实不用,这就跟排序算法一样,我们只需要记住每种算法的适用范围和场景就可以了,在有需要的时候,再去深入研究就可以了。以下总结了各种设计模式对应的适用场景:

模式名称 适用场景
单例模式(Singleton) 无状态类使用单例模式可以节省内存资源
工厂方法模式(Factory Method) 在不知道具体实现细节的情况下创建对象的场景
抽象工厂模式(AbstractFactory) 客户端与对象创建解耦,需要创建多个不同类型的对象的场景
建造者模式(Builder) 生成复杂对象的场景
原型模式(Prototype) 快速创建大量同类对象的场景
适配器模式(Adapter) 让两个不兼容的类一起工作的场景
桥接模式(Bridge) 将一个类的抽象部分和实现部分独立改变的场景
组合模式(Composite) 表示树形结构的场景
装饰模式(Decorator) 动态地为对象添加新职责的场景
外观模式(Facade) 为一个复杂的子系统提供一个简单的接口的场景
亨元模式(Flyweight) 在多个地方共享大量细粒度对象的场景
代理模式(Proxy) 在访问某个对象时增加额外控制的场景
模板方法模(TemplateMethod) 在不改变算法结构的情况下重定义算法中的某些步骤的场景
策略模式(Strategy) 在不同情况下使用不同算法的场景
命令模式(Command) 支持命令的撤销和恢复、延迟调用或日志操作的场景
职责链模式(Chain of Responsibility) 在不明确指定接收者的情况下,向多个对象中提交一个请求的场景
状态模式(State) 根据对象的状态来改变它的行为的场景。
观察者模式(Observer) 在对象之间松散耦合的场景
中介者模式(Mediator) 在多个对象之间松散耦合的场景
迭代器模式(Iterator) 为容器对象提供多种遍历方式的场景
访问者模式(Visitor) 在不改变各元素的类的前提下定义对这些元素的新操作的场景
备忘录模式(Memento) 历史回放或者回滚等场景
解释器模式(Interpreter) 定义一个语言并为该语言实现一个解释器的场景

3.2 场景案例

为了让读者对设计模式有个更加直观立体的感知,接下来以实际案例为大家展现一下设计模式在实际场景的应用。案例包含了创建型,结构型,行为型各种模式类型里常用的设计模式,比如:

•用工厂模式隔离业务实现;

•用策略模式消解业务流程分支;

•用模板方法模式提取业务分支公共流程;

•用建造者模式简化入参对象的构建难度;

•用代理模式横向扩展通用能力(日志,异常处理);

•用职责链模式对请求进行敏感词,防刷校验;

•用命令模式让指令拥有了记忆;

中国有个古谚语:“一个和尚挑水吃,两个和尚抬水吃,三个和尚等水吃。” 我们就通过程序来模拟出家人的寺庙生活。

工厂模式

首先,这三个人是如何成为和尚的呢?

一号和尚(贫困潦倒型),出生在一个大山里头,父母怕他孤单,给他生了5个弟弟,在他9岁那年,恰巧家里闹了饥荒,为了吃上饭,进了寺庙,出了家;

二号和尚(走投无路型),出生在一个湖泊旁边,因为生性耿直,18岁那年,走在街头,路见不平,三拳打死街上恶霸,为了赎罪,受了戒,坠入空门;

三号和尚(天选之子型),从小敏而好学,性情温厚,对佛学产生浓厚兴趣,13岁那年,为了继承和光大佛法,断了尘缘,皈依佛门。

N号和尚,......

每一个和尚的来历都不尽相同,但在当下喝不上水,这件事情上,都显得不重要。重要的是,只要凑足三个和尚,就会没水喝。那么寺庙如招收和尚?这里就可以用到工厂模式的思想。

    // 贫困潦倒产生的和尚过程:1.大山里;2.闹饥荒;3.要吃饭;
    一号和尚 = HeShangFactoty.getOneHeshang("贫困潦倒型");
    // 走投无路产生的和尚过程:1.生性耿直;2.打死恶霸;3.要赎罪;
    二号和尚 = HeShangFactoty.getOneHeshang("走投无路型");
    // 天选之子产生的和尚过程:1.敏而好学;2.佛学感兴趣;3.要广大佛法;
    三号和尚 = HeShangFactoty.getOneHeshang("天选之子型");

以上示例想体现的是工厂模式能将复杂的对象创建和使用进行了分离设计。下面就以和尚吃水这件事情,用程序的方式详细展现工厂模式的实现思路。按照和尚的人数,分别有挑,抬,等三种实现方式。以下为基础代码实现:

public interface Waterable {
    Water getWater();
}

public class TiaoShui implements Waterable{
    public Water getWater(){
        System.out.println("先到山下去!");
        return "水是挑上来的!";
    }
} 

public class TaiShui implements Waterable{
    public Water getWater(){
        System.out.println("先到山下去!");
        return "水是抬上来的!";
    }
} 

public class DengShui implements Waterable{
    public Water getWater(){
        System.out.println("就坐在原地!");
        return "水是等不来的!";
    }
} 

具体使用

public class Factory {
    /**
     * 按照和尚数量生成取水对象
     *
     * @param heShangNum 和尚数量
     * @return
     */
    public static Waterable getWaterable(Integer heShangNum) {
        switch (heShangNum) {
            case 1:
                return new TiaoShui();
            case 2:
                return new TaiShui();
            case 3:
                return new DengShui();
            default:
                throw new RuntimeException("庙小,装不下那么多和尚!");
        }
    }
}

策略模式

按照不同的条件(人数),分别有几种获取水的方法:挑,抬,等。可以通过策略模式来实现,前面的实现方式其实就是策略模式和工厂模式的结合。我们再看一下策略模式的具体使用方式如下:


    /**
     * 通过入参和尚人数,就可以动态改变Waterable.getWater()的取水模式
     * @param heShangNum
     * @return
     */
    public void getWater(Integer heShangNum) {
        Waterable waterable = Factory.getWaterable(heShangNum);
        Water water = waterable.getWater();// 取水
    }

1.输入参数1:挑水模式的实现(对应Tiaoshui实现类);

2.输入参数2:抬水模式的实现(对应Taishui实现类);

3.输入参数3:等不到水模式的实现(对应Dengshui实现类);

通过和尚人数,就可以动态获得对应的取水实现,即所谓的通过策略实现业务,对于使用方来说(主流程),无需关注取水的具体实现(解耦:业务流程稳定性的设计体现),新增取水方式时,只需要新增一个类实现就可以了,存量的实现和主流程都不会受到影响。

模板方法

我们细化取水过程,取水过程一般需要三步:

1.拿起工具(扁担或者木棍);

2.到寺庙南面的小河边(步行);

3.装满水带回寺庙(挑水,抬水,等水);

我们可以将取水流程步骤进行模板化。

public interface Waterable {
    Water getWater();
}

public abstract class AbstractWaterable implements Waterable {
    @Override
    public Water getWater() {
        takeTool();
        toRiver();
        return moveWater();
    }
    /**
     * 拿起工具
     */
    protected abstract String takeTool();

    /**
     * 到河边去
     */
    protected String toRiver() {
        System.out.println("走过去!");
        return "步行";
    }

    /**
     * 将水带回来
     *
     * @return
     */
    protected abstract Water moveWater();
}

个性化场景实现

public class TiaoShui extends AbstractWaterable {

    @Override
    protected String takeTool() {
        return "扁担";
    }

    @Override
    protected Water moveWater() {
        return "挑水";
    }
}

public class Taishui extends AbstractWaterable{
    @Override
    protected String takeTool() {
        return "木棍";
    }

    @Override
    protected Water moveWater() {
        return "抬水";
    }
}

public class DengShui extends AbstractWaterable{
    @Override
    protected String takeTool() {
        return "意念";
    }

    @Override
    protected String toRiver() {
        return "一动不动";
    }

    @Override
    protected Water moveWater() {
        return "无水";
    }
}

具体使用

    /**
     * 和尚取水:实现一个和尚挑水喝,两个和尚抬水喝,三个和尚等水喝
     */
    public void fetchWater(){
        // 
        for (int heShangNum = 1; heShangNum < 4; heShangNum++) {
            Waterable waterable = Factory.getWaterable(heShangNum);
            Water water = waterable.getWater();
        }
    }

模板方法讲的是流程标准定义和能力复用。示例中,定义了取水的三个阶段,选择工具,出行方式,搬运方式。单看出行方式中,【挑水】和【抬水】复用了模板方法里的通用实现,【等水】则个性化的重写了出行方式。

建造者模式

我们取水需要一些工具,按照取水方式(挑,抬,等)可以分为扁担+木桶,木棍+木桶,意念(什么也不需要)等装备的组合方式。如何定义getWater(ToolBox toolBox)的入参ToolBox,使其能够按照对应取水方式匹配正确的装备组合呢?这里就可以使用建造者模式。

public class ToolBox {
    private final String bianDan;
    private final String muTong;
    private final String muGun;

    private ToolBox(TiaoBuilder builder){
        this.bianDan=builder.bianDan;
        this.muTong=builder.muTong;
        this.muGun = null;
    }
    private ToolBox(TaiBuilder builder){
        this.bianDan = null;
        this.muTong = null;
        this.muGun=builder.muGun;
    }
    private ToolBox(DengBuilder builder){
        this.bianDan = null;
        this.muTong = null;
        this.muGun=null;
    }
    public static class TiaoBuilder{
        private String bianDan;
        private String muTong;

        public TiaoBuilder setBianDan(String bianDan) {
            this.bianDan = bianDan;
            return this;
        }
        public TiaoBuilder setMuTong(String muTong) {
            this.muTong = muTong;
            return this;
        }
        public ToolBox build(){
            return new ToolBox(this);
        }
    }

    public static class TaiBuilder{
        private String muGun;
        private String muTong;

        public TaiBuilder setMuGun(String muGun) {
            this.muGun = muGun;
            return this;
        }
        public TaiBuilder setMuTong(String muTong) {
            this.muTong = muTong;
            return this;
        }
        public ToolBox build(){
            return new ToolBox(this);
        }
    }

    public static class DengBuilder{
        public ToolBox build(){
            return new ToolBox(this);
        }
    }

    //省略getter方法
}

具体使用

ToolBox oneHeShangToolBox = new ToolBox.TiaoBuilder().setMuTong("小号木桶").setBianDan("小号扁担").build();
ToolBox twoHeShangToolBox = new ToolBox.TaiBuilder().setMuTong("大号木桶").setMuGun("长号木棍").build();
ToolBox threeHeShangToolBox = new ToolBox.DengBuilder().build();

建造者模式属于创建型设计模式,它可以将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

代理模式

为了鼓励多劳多得,庙里取水开始采用积分机制,每取回来一桶水就要敲一下木鱼,打一次卡。这里就可以采用代理模式。代理分为静态代理和动态代理,为了简单起见,这里就用静态代理来举例。

    public class WaterableProxy implements Waterable{
    /**
     * 被代理的原始对象
     */
    private Waterable waterable;
    
    public WaterableProxy(Waterable waterable) {
        this.waterable = waterable;
    }

    @Override
    public Water getWater() {
        Water water = waterable.getWater();
        // 增强的新功能,不管是挑水,抬水,等水,只有带回来水,就可以
        if(water != "无水"){
            System.out.println("我敲一下木鱼,打一次卡!");
        }
        return water;
    }
}

具体使用

    public void doProxy(){
        Waterable waterable = new Taishui();
        WaterableProxy proxyWaterable = new WaterableProxy(waterable);
        proxyWaterable.getWater();
    }

代理模式就是代理对象具备真实对象的功能,并代替真实对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理。(通过代理访问真实对象)

责任链模式

为了升级取水工具,将小木桶升级大金桶,寺庙决定对外提供烧香拜佛,诵经礼佛等增值服务。为了安全起见,寺庙引进了安检机制,流程是这样的:

•禁止携带宠物;

•衣着穿戴整齐;

•其它业障,最终解释权归寺庙;

public interface SecureFilter {
    void filter(Map context);
}

public class PetSecure implements SecureFilter{
    @Override
    public void filter(Map context) {
        if(context.containsKey("宠物")){
            throw new RuntimeException("请出去:禁止携带宠物进入!");
        }
    }
}

public class WearSecure implements SecureFilter{
    @Override
    public void filter(Map context) {
        if(context.containsKey("光膀子")){
            throw new RuntimeException("请出去:有伤风化者!");
        }
    }
}

public class OtherSecure implements SecureFilter{
    @Override
    public void filter(Map context) {
        if(context.containsKey("大声喧哗")){
            throw new RuntimeException("请出去:佛门乃清净之地!");
        }
    }
}

具体使用

/**
 * 安检责任链实现
 */
class SecureChain implements SecureFilter{
    // 注入PetSecure,WearSecure,OtherSecure等过滤器
    private List<SecureFilter> secureFilterList;
    /**
     * 进入寺庙,进行安检逻辑
     * @param context
     */
    @Override
    public void filter(Map context) {
        // 进行安检流程
        for (SecureFilter secureFilter : secureFilterList) {
            secureFilter.filter(context);
        }
        System.out.println("佛祖保佑,安检通过!");
    }
}

流程示意图

责任链模式一般和过滤器模式组合一起使用,即创建一个链条,经过这个链条处理的所有对象和数据分别进行依次加工,每个环节负责处理不同的业务,环节间彼此独立解耦,同时可以复用。这种设计的巧妙之处在于可以链式调用,不同的过滤方式可以灵活的排序和组合。既可以使用单个过滤器进行处理,也可以直接添加一条责任链。

命令模式

寺庙里的和尚除了打水工作之外,还有很多工作要做,所有的工作安排都是按照主持的指令来执行的,比如某日清晨的工作安排如下:

1.一号和尚做早餐;

2.二号和尚扫庭院;

3.三号和尚敲古钟;

结构定义

public class Command implements Serializable {
    // 做早餐,打扫,敲钟等指令标识
    private OrderTypeEnum order;
    // 正向执行OR逆向回滚
    private Integer direction;
    // 省略get和set方法
}

// 指令动作执行器,每种指令对应一个实现
public interface OrderHandler {
 /**
     * 执行逻辑
     *
     * @param callContext
     * @return
     */
    PipeResult execute(CallContext callContext);
    /**
     * 支持的命令类型:做早餐,打扫,敲钟等命令标识
     *
     * @return
     */
    OrderTypeEnum getOrderType();

}

// 指令类型管理器
public interface PipelineCmd {

    /**
     * 指令行定义
     *
     * @return
     */
    Command getCommand();

    /**
     * 执行逻辑
     *
     * @param pipeContext
     * @return
     */
    PipeResult execute(PipeContext pipeContext);

    /**
     * 如果可以撤消指令,则此方法应返回true ,否则返回false
     *
     * @return
     */
    default boolean isReversible() {
        return true;
    }
}
 
 // 指令执行器管理器
 public interface CmdHandler {
    /**
     * 业务执行
     *
     * @param callContext
     * @return
     */
    PipeResult execute(CallContext callContext);

    /**
     * 业务回滚(只回滚当前指令)
     *
     * @param callContext
     * @return
     */
    PipeResult rollback(CallContext callContext);

    /**
     * 全部回滚
     *
     * @param pipeContext
     * @return
     */
    PipeResult rollbackAll(PipeContext pipeContext);
}

命令实现

public class ZhuChiCmd implements PipelineCmd {
    private Command command;
    private transient OrderHandler orderHandler;

    public StepCmd(Command command, OrderHandler orderHandler) {
        this.command = command;
        this.orderHandler= orderHandler;
    }

    @Override
    public PipeResult execute(PipeContext pipeContext) {
        return orderHandler.execute(new CallContext(command, pipeContext));
    }
    // 省略get和set方法
}
    
    
public class Breakfast implements OrderHandler {
 /**
     * 执行逻辑
     *
     * @param callContext
     * @return
     */
    PipeResult execute(CallContext callContext){
        System.out.println("做早餐啦!");
    }
    /**
     * 支持的指令类型:做早餐,打扫,敲钟等指令标识
     *
     * @return
     */
    OrderTypeEnum getOrderType(){
        return OrderTypeEnum.BREAKFAST;
    }

}

public class Clean implements OrderHandler {
 /**
     * 执行逻辑
     *
     * @param callContext
     * @return
     */
    PipeResult execute(CallContext callContext){
        System.out.println("打扫庭院啦!");
    }
    /**
     * 支持的指令类型:做早餐,打扫,敲钟等命令标识
     *
     * @return
     */
    OrderTypeEnum getOrderType(){
        return OrderTypeEnum.CLEAN;
    }

}

public class Ring implements OrderHandler {
 /**
     * 执行逻辑
     *
     * @param callContext
     * @return
     */
    PipeResult execute(CallContext callContext){
        System.out.println("敲钟啦!");
    }
    /**
     * 支持的命令类型:做早餐,打扫,敲钟等指令标识
     *
     * @return
     */
    OrderTypeEnum getOrderType(){
        return OrderTypeEnum.Ring;
    }

}

public class CmdFactory {
    private List<OrderHandler> orderHandlerList;

    /**
     * 获取指定指令条件的指令对象
     *
     * @param command
     * @return
     */
     public PipelineCmd getPipelineCmd(Command command) {
        for (OrderHandler orderHandler : orderHandlerList) {
            OrderTypeEnum orderTypeEnum = orderHandler.getOrderType();
            if (orderTypeEnum.equals(command.getOrder())) {
                return new ZhuChiCmd(command, orderHandler);
            }
        }
        throw new RuntimeException("对不起主持:没有多余的和尚来执行新命令了!");
    }
     /**
     * 获取给定指令的回滚操作指令对象
     *
     * @param command
     * @return
     */
    public PipelineCmd getRollbackPipelineCmd(Command command) {
        Command rollbackCommand = getRollbackCommand(command);
        return getPipelineCmd(rollbackCommand);
    }
}

具体使用

public class CmdHandlerImpl implements CmdHandler {
    private CmdFactory cmdFactory;

    @Override
    public PipeResult execute(CallContext callContext) {
        PipelineCmd pipelineCmd = cmdFactory.getPipelineCmd(callContext.getCommand());
        PipeResult pipeResult = pipelineCmd.execute(callContext.getPipeContext());
        return pipeResult;
    }

    @Override
    public PipeResult rollback(CallContext callContext) {
        Command rollbackCommand = cmdFactory.getRollbackCommand(callContext.getCommand());
        if (rollbackCommand == null) {
            return new PipeResult("不需要回滚");
        }
        PipelineCmd pipelineCmd = cmdFactory.getPipelineCmd(rollbackCommand);
        if (!pipelineCmd.isReversible()) {
            return new PipeResult("不支持回滚");
        }
        PipeResult pipeResult = pipelineCmd.execute(callContext.getPipeContext());
        return pipeResult;
    }

    @Override
    public PipeResult rollbackAll(PipeContext pipeContext) {
        // 命令执行备忘录模式对象,这里不再展开
        Caretaker<Command> caretaker = pipeContext.getCaretaker();
        // 拿到上一步执行命令,依次循环回滚
       Command command = caretaker.pop();
        while (command != null) {
            PipelineCmd pipelineCmd = cmdFactory.getRollbackPipelineCmd(command);
            if (pipelineCmd != null) {
                pipelineCmd.execute(pipeContext);
            }
            command = caretaker.pop();
        }
        return new PipeResult();
    }

}

命令模式将一个请求封装为一个对象,使发出的请求的对象和执行请求的对象分割开。这两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。命令模式可以与备忘录模式组合使用,方便实现Undo和Redo操作。

3.3 实践心得

设计原则

具体包含单一职责原则SRP、开闭原则OCP、里氏替换原则LSP、依赖倒置原则DIP、接口隔离原则ISP、最少知识原则LKP等很多种,其核心还是围绕着低耦合,高复用,高内聚,易扩展,易维护展开的。

模式与原则

1.设计原则是指导思想,设计模式是实现手段之一;

2.设计原则在实际开发中并不能做到完全遵守,往往是打破一些原则,遵守一些原则,来实现设计的合理性;(成本,性能)

3.设计模式往往是问题解决方案的骨架,有时候可以当做开发规范和任务拆分执行落地的技术手段;

4.一个设计模式,往往不仅仅采用一种设计原则,而是一些设计原则的整合;

5.设计模式不是一成不变的,可以根据问题场景,输出新的模式;

6.一个复杂场景问题,有时候需要多种设计模式的组合;

7.学设计模式,死记硬背是没用的,要从实践中习得;

8.避免设计过度,使简单的问题复杂化。一定要牢记简洁原则,设计模式是为了使设计简单,而不是更复杂;

四 总结

本文从设计模式与编程语言的关系,设计模式与架构模式的区别,设计原则和设计模式的关系等几个维度进行了分析和解答。关于设计模式应该如何学习和应用的问题,给出了学习意见和实践心得。当然,为了让设计模式更加的直观和立体,也花了大量篇幅在应用实践案例上面,主要是通过场景化的案例,以设计模式的方式给出解决方案,其中部分场景为了方便理解,将问题做了简化处理,但这不影响我们去理解设计模式要解决的问题类型。冰冻三尺非一日之寒,滴水石穿非一日之功,希望本文能够为你带来帮助。

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/8704823