设计模式学习笔记(Design pattern learning notes)

开放封闭原则

软件实体(类,模块,函数等等)应该可以扩展,但是不可以修改

不要指望系统一开始确定需求之后就再也不会变化,要使得设计的软件要容易维护又不容易出问题就得多扩展,少修改

但模块没有绝对的封闭,所以就需要对设计的模块有哪些变化作出选择:猜测那些类最有可能发生变化,然后构造抽象来隔离那些变化。

对发生改变的类;立即作出反应,抽象出其功能然后利用继承、多态、复合等方法来隔离具体的实现细节和高层模块,使得之后对模块的修改都是基于抽

  • 不要指望系统一开始确定需求之后就再也不会变化,要使得设计的软件要容易维护又不容易出问题就得多扩展,少修改
  • 但模块没有绝对的封闭,所以就需要对设计的模块有哪些变化作出选择:猜测那些类最有可能发生变化,然后构造抽象来隔离那些变化。
  • 对发生改变的类;立即作出反应,抽象出其功能然后利用继承、多态、复合等方法来隔离具体的实现细节和高层模块,使得之后对模块的修改都是基于抽

可维护,可扩展,可复用,灵活性好,拒绝不成熟的抽象和抽象本身一样重要。

其实就是对经常变化的类(需要经常更改的类)功能抽象出来,从对这个

其实就是对经常变化的类(需要经常更改的类)功能抽象出来,从对这个

依赖倒转原则

依赖倒转原则(Dependency Inversion Principle,简称DIP)是指将两个模块之间的依赖关系倒置为依赖抽象类或接口。

  • 高层模块不应该依赖于低层模块,二者都应该依赖于抽象;
  • 抽象不应该依赖于细节,细节应该依赖于抽象。

依赖:在程序设计中,如果一个模块a使用/调用了另一个模块b,我们称模块a依赖模块b。

高层模块与低层模块:往往在一个应用程序中,我们有一些低层次的类,这些类实现了一些基本的或初级的操作,我们称之为低层模块;另外有一些高层次的类,这些类封装了某些复杂的逻辑,并且依赖于低层次的类,这些类我们称之为高层模块。

依赖倒置(Dependency Inversion):面向对象程序设计相对于面向过程(结构化)程序设计而言,依赖关系被倒置了。因为传统的结构化程序设计中,高层模块总是依赖于低层模块。

一个良好的设计应该是系统的每一部分都是可替换的。如果“高层模块”过分依赖“低层模块”,一方面一旦“低层模块”需要替换或者修改,“高层模块”将受到影响;另一方面,高层模块很难可以重用。

一个良好的设计应该是系统的每一部分都是可替换的。如果“高层模块”过分依赖“低层模块”,一方面一旦“低层模块”需要替换或者修改,“高层模块”将受到影响;另一方面,高层模块很难可以重用。

DIP解决方案:在高层模块与低层模块之间,引入一个抽象接口层。

High Level Classes(高层模块) –> Abstraction Layer(抽象接口层) –> Low Level Classes(低层模块)
抽象接口是对低层模块的抽象,低层模块继承或实现该抽象接口。
这样,高层模块不直接依赖低层模块,而是依赖抽象接口层。抽象接口也不依赖低层模块的实现细节,而是低层模块依赖(继承或实现)抽象接口。
类与类之间都通过抽象接口层来建立关系。

High Level Classes(高层模块) –> Abstraction Layer(抽象接口层) –> Low Level Classes(低层模块)

抽象接口是对低层模块的抽象,低层模块继承或实现该抽象接口。

这样,高层模块不直接依赖低层模块,而是依赖抽象接口层。抽象接口也不依赖低层模块的实现细节,而是低层模块依赖(继承或实现)抽象接口。

类与类之间都通过抽象接口层来建立关系。

怎么使用依赖倒置原则

1. 依赖于抽象

  • 任何变量都不应该持有一个指向具体类的指针或引用。
  • 任何类都不应该从具体类派生。

2. 设计接口而非设计实现

  • 使用继承避免对类的直接绑定
  • 抽象类/接口: 倾向于较少的变化;抽象是关键点,它易于修改和扩展;不要强制修改那些抽象接口/类

3. 避免传递依赖

  • 避免高层依赖于低层
  • 使用继承和抽象类来有效地消除传递依赖

依赖倒转减少了类间的耦合性、提高了系统稳定性,提高了代码可读性和可维护性,可降低修改程序所造成的风险。

单一职责原则

  • 就一个类而言,应该仅有一个引起它变化的原因
  • 如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致错若的设计,当变化发生时,设计会遭受到意想不到的破坏。
  • 在软甲设计时,就需要发现职责并把那些还在这互相分离。就是你能想到多余一个动机去改变一个类,那么这个类就具有多于一个的职责。
  • 在编写代码的过程中,尽可能的让接口和方法保持单一职责,对项目后期的维护是由很大帮助的。

接口隔离原则

接口隔离原则(Interface Segregation Pinciple,ISP)是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。

所以设计接口时应该注意一下几点:

  • 一个类对应另一个类的依赖应该建立在最小的接口之上。
  • 建立单一接口,不要建立庞大臃肿的接口。
  • 尽量细化接口,接口中的方法尽量少(适度,不要什么都塞进去),对于该分理处其他接口的方法要进行分离。

接口隔离原则符合我们常说的高内聚,低耦合的设计思想(不同模块依赖度低,同一模块内联系紧密),使类具有良好的可读性、可扩展性和可维护性。我们在设计接口时要多花时间去思考,要考虑业务模型,包括以后有可能发生变更的地方做一些预判。所以抽象、对业务模型的理解是非常重要的。

迪米特原则

  • 迪米特原则(Law of Demeter LoD)是指一个对象应该对其他对象保存最少的了解,即最少知道原则(Least Knowledge Principle,LKP),尽量降低类与类之间的耦合度。
  • 其强调之和朋友类交流,不和陌生人说话。

    迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
    但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。

  • 迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
  • 但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。

它强调以下两点:

  • 从依赖者的角度来说,只依赖应该依赖的对象。
  • 从被依赖者的角度说,只暴露应该暴露的方法。

里氏替换原则

如果每个类型T1的对象O1,都有类型成为T2的对象O2,使得T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类。(其实就是一个软件实体适用于一个父类,那么一定适用于其子类,所有引用父类的地方必须能够透明的使用其子类的对象,子类对象能够替换其父类对象并保持程序逻辑不变:子类可以扩展父类功能,但不能改变父类原有的功能

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更加宽松。
  • 当子类实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更加严格或者与父类一样。

里氏替换原则优点:

  • 约束继承泛滥,是开闭原则的一种体现
  • 加强程序的健壮性,同时变更时也可以做到更好的兼容性,提高程序的可维护性可扩展性,降低需求变更时引入的风险。

合成复用原则

  • 合成复用原则是指尽量使用对象组合/继承而不是继承关系达到软件复用的目的、可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。
  • 继承叫做白箱复用,相当于吧所有的实现细节暴露给子类。组合/聚合称为黑线复用,我们是无法获取到类以外的对象的实现细节的。
  • 典型的有依赖注入(不会为依赖创建一个单独的容器来管理,而是将其纳入依赖者中共同管理这个依赖)、装饰者模式(通过继承同一个接口,依赖实现了同一个接口的对象并对需要的方法直接调用依赖的这个对象的方法就行了,而不是通过继承的方式调用父类的方法,完成了对象之间的解耦)

简单工厂模式

SimpleDesgin

简单工厂类的工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说去除了与具体产品的依赖。

但是若想要添加功能,则需要在原有的类上进行修改。这样就违背了“开放-封闭原则”,于是有了工厂方法。

  • 代码规范(命名规范, 容错)
  • 面向对象编程(容易维护, 容易扩展, 容易复用)
  • 分层(将业务逻辑和界面逻辑分开)
  • 松耦合(将不同逻辑分离)
  • 建立简单工厂(使用一个单独的类来创造实例)

用计算器作为实例:

Operation

public abstract class Operation {
    private double lastResult = 0;

    public abstract double doOperation(double numberA, double numberB);

    public double getLastResult() {
        return lastResult;
    }

    public void setLastResult(double lastResult) {
        this.lastResult = lastResult;
    }
}

OperationAdd

public class OperationAdd extends Operation {

    @Override
    public double doOperation(double numberA, double numberB) {
        setLastResult(numberA + numberB);
        return getLastResult();
    }
}

OperationSub

public class OperationSub extends Operation {
    @Override
    public double doOperation(double numberA, double numberB) {
        setLastResult(numberA - numberB);
        return getLastResult();
    }
}

OperationMul

public class OperationMul extends Operation {

    @Override
    public double doOperation(double numberA, double numberB) {
        setLastResult(numberA * numberB);
        return getLastResult();
    }
}

OperationDiv

public class OperationDiv extends Operation{
    @Override
    public double doOperation(double numberA, double numberB) throws Exception {
        if (numberB == 0){
            throw new Exception("除数不能为0");
        }
        setLastResult(numberA / numberB);
        return getLastResult();
    }
}

工厂方法模式

工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类(依赖倒转原则)。

如果一个工厂类和分支耦合(上层工厂类型依赖于下层产品工厂的具体实现),那么我们就根据依赖倒转原则,将工厂类抽象出一个接口。这个接口只有一个方法,就是创建抽象产品的工厂方法。然后所有的要生产具体类的工厂去实现这个抽象出来的工厂接口,这样我们如果需要添加新的功能的话就只需要新增加产品和创建产品的工厂,这两个分别依赖于产品抽象接口和工厂抽象接口(这里就在遵循了开闭原则的公式将高层对下层的依赖倒转成了下层实现上层方法,上层依赖于上层抽象接口,降低了耦合的同时维持了封装创建对象的过程的优点)。

如果一个工厂类和分支耦合(上层工厂类型依赖于下层产品工厂的具体实现),那么我们就根据依赖倒转原则,将工厂类抽象出一个接口。这个接口只有一个方法,就是创建抽象产品的工厂方法。然后所有的要生产具体类的工厂去实现这个抽象出来的工厂接口,这样我们如果需要添加新的功能的话就只需要新增加产品和创建产品的工厂,这两个分别依赖于产品抽象接口和工厂抽象接口(这里就在遵循了开闭原则的公式将高层对下层的依赖倒转成了下层实现上层方法,上层依赖于上层抽象接口,降低了耦合的同时维持了封装创建对象的过程的优点)。

例子:

水果产品类:

public interface Fruit {
    void eatFruit();
}

public class Apple implements Fruit{
    @Override
    public void eatFruit() {
        System.out.println("吃了一个苹果");
    }
}

public class Cherry implements Fruit{
    @Override
    public void eatFruit() {
        System.out.println("吃了一个樱桃");
    }
}

水果工厂类:

public interface FruitFactory {
    Fruit creatFruit();
}

public class AppleFactory implements FruitFactory{
    @Override
    public Fruit creatFruit() {
        return new Apple();
    }
}

public class CherryFactory implements FruitFactory{
    @Override
    public Fruit creatFruit() {
        return new Cherry();
    }
}

抽象工厂模式

抽象工厂(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。(当同一个产品存在不同系列的操作时,我们在抽象工厂中添加不同系列的创建方法)

使用反射实现抽象工厂:

Mapper是操作数据的抽象接口,EmpMapper和DeptMapper是对两个表的数据操作抽象类,EmpMapperImpl是操作数据库中表的实现类,但数据库有两种所以存在两种实现。

Mapper:

public interface Mapper<T> {
    void insert(T t);
    T select();
}

DeptMapper:

public abstract class DeptMapper implements Mapper<Dept>{

}

public class DeptMapperMysqlImpl extends DeptMapper {

    @Override
    public void insert(Dept dept) {
        System.out.println("insert into dept [Mysql]");
    }

    @Override
    public Dept select() {
        System.out.println("select from dept [Mysql]");
        return new Dept();
    }
}

public class DeptMapperOracleImpl extends DeptMapper {
    @Override
    public void insert(Dept dept) {
        System.out.println("insert into mysql [oracle]");
    }

    @Override
    public Dept select() {
        System.out.println("select from dept [oracle]");
        return new Dept();
    }
}

EmpMapper:

public abstract class EmpMapper implements Mapper<Emp>{

}

public class EmpMapperMysqlImpl extends EmpMapper {
    @Override
    public void insert(Emp emp) {
        System.out.println("insert into emp [Mysql]");
    }

    @Override
    public Emp select() {
        System.out.println("select from emp [Mysql]");
        return new Emp();
    }
}

public class EmpMapperOracleImpl extends EmpMapper {
    @Override
    public void insert(Emp emp) {
        System.out.println("insert into emp [Oracle]");
    }

    @Override
    public Emp select() {
        System.out.println("select from emp [Oracle]");
        return new Emp();
    }
}

DataAccess:

public class DataAccess {
    private static final String nameSpace = "com.design.abstractFactoryDesign.pro.";
    private static String database = "Mysql";

    public static EmpMapper getEmpMapper() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        String className = nameSpace + "EmpMapper"+ database + "Impl";
        Class<?> clazz = Class.forName(className);
        return (EmpMapper)clazz.newInstance();
    }

    public static DeptMapper getDeptMapper() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        String className = nameSpace + "DeptMapper"+ database + "Impl";
        Class<?> clazz = Class.forName(className);
        return (DeptMapper)clazz.newInstance();
    }

    public static String getDatabase() {
        return database;
    }

    public static void setDatabase(String database) {
        DataAccess.database = database;
    }
}

创建一个主线程进行测试:

public class AbstractFactoryDesign {
    public static void main(String[] args) {
        try {
            DeptMapper deptMapper = DataAccess.getDeptMapper();
            deptMapper.insert(null);
            deptMapper.select();

            DataAccess.setDatabase("Oracle");

            EmpMapper empMapper = DataAccess.getEmpMapper();
            empMapper.insert(null);
            empMapper.select();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

输出:

insert into dept [Mysql]
select from dept [Mysql]
insert into emp [Oracle]
select from emp [Oracle]

单例模式

单例模式是对唯一实例的受控访问:让类自身保存类的唯一实例(私有化构造,变量保存唯一实例(存在两种保存方式:饿汉式,懒汉式))

懒汉式单例:在自己第一次被引用的时候才会将自己实例化(存在多线程的线程安全问题,需要加锁和判断是否为空)。

采用了双锁检查机制(DCL),并使用了volatile关键字禁止重排序

采用了双锁检查机制(DCL),并使用了volatile关键字禁止重排序

public class SlobSingleton {
    volatile private static SlobSingleton instance;
    private static final Object lock = new Object();
    private SlobSingleton(){}

    public static SlobSingleton getInstance(){
        if (instance == null){
            synchronized(lock){
                if (instance == null){
                    return new SlobSingleton();
                }
            }
        }
        return instance;
    }

}

使用静态内部类实现懒汉模式

使用静态内部类实现懒汉模式

class Demo{
    static {
        System.out.println("Demo static part");
    }
    private Demo(){
        System.out.println("Demo");
    }
    public static void print(){
        System.out.println("Demo static print");
    }
    public static Demo getInstance(){
        return InnerDemo.demo; //InnerDemo只有在被调用的时候才会加载
    }
    private static class InnerDemo{
        private static Demo demo = new Demo(); //先加载静态字段

        static { //后加载静态代码块
            System.out.println("InnerDemo static part");
        }
    }
}

因为类加载一次后会在方法区存放类信息,堆中存放对应Class对象,并且在初始化阶段对非final类变量进行初始赋值,这里就是内部类在加载时在堆中生成了外部类的静态变量。
外部类只有在调用内部类并且是第一次调用的时候内部类才会加载,在内部类加载时实例化了唯一的外部类引用。这样利用类加载机制实现了懒汉模式。

因为类加载一次后会在方法区存放类信息,堆中存放对应Class对象,并且在初始化阶段对非final类变量进行初始赋值,这里就是内部类在加载时在堆中生成了外部类的静态变量。

外部类只有在调用内部类并且是第一次调用的时候内部类才会加载,在内部类加载时实例化了唯一的外部类引用。这样利用类加载机制实现了懒汉模式。

饿汉式单例:在自己被加载时就会将自己实例化(静态变量,在类初始化中的准备阶段进行空间分配,在初始化阶段进行赋值)。

public class HungerSingleton {
    private static HungerSingleton INSTANCE = new HungerSingleton();

    private HungerSingleton(){}

    public static HungerSingleton getInstance(){
        return INSTANCE;
    }

}

注册式单例模式

注册式单例模式又称为登记式单例模式,就是将每个单例都登记到某一个地方,使用唯一的标识获取实例。分为两种:枚举式单例模式、容器式单例模式。

枚举式单例模式

  • 避免了被类加载器加载多次,并且无法通过反射破坏枚举类型单例,因为发现是枚举类型的时候就会抛出异常。
public enum EnumSingleton {
    INSTANCE;
    private Object data;
    
    public Object getData(){
        return data;
    }
    
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

容器式单例

  • 容器式单例适用于实例非常多的情况,便于管理。但它是非线程安全的!
public class ContainerSingleton {
    private ContainerSingleton(){}
    private static Map<String, Object> ioc = new ConcurrentHashMap<>();
    public static Object getBean(String className){
        synchronized (ioc){
            if (!ioc.containsKey(className)){
                Object o = null;
                try {
                    o = Class.forName(className).newInstance();
                    ioc.put(className, o);
                }catch (Exception e){
                    e.printStackTrace();
                }
                return o;
            } else {
                return ioc.get(className);
            }
        }
    }
}

代理模式

  • 代理模式: 为其他对象为提供一种代理以控制对这个对象的访问.

Super类定义了SubObject和Proxy的公用接口, 这样在需要使用SubObject的任何地方都可以使用Proxy.

应用场景:

  • 远程代理: 远程对象的本地代表, 本地方法调用这个对象会被转发到远程中.
  • 虚拟代理: 虚拟化开销大的对象
  • 安全代理: 控制访问权限
  • 智能指引: 调用真实的对象时, 处理另外一些事

例子:

Interface.class

public interface Interface {
    void doSomething();
    void doSomethingElse(String arg);
}

InterfaceImplFirst.class

public class InterfaceImplFirst implements Interface{

    public void doSomething() {
        System.out.println("InterfaceImplFirst: doSomething");
    }

    public void doSomethingElse(String arg) {
        System.out.println("InterfaceImplFirst: doSomethingElse " + arg);
    }
}

InterfaceImplTwo.class

public class InterfaceImplTwo implements Interface {
    private final Interface interfaceImplFirst;

    public InterfaceImplTwo(Interface interfaceImplFirst){
        this.interfaceImplFirst = interfaceImplFirst;
    }

    public void doSomething() {
        interfaceImplFirst.doSomething();
        System.out.println("InterfaceImplTwo: doSomething");
    }

    public void doSomethingElse(String arg) {
        interfaceImplFirst.doSomethingElse(arg);
        System.out.println("doSomethingElse: doSomethingElse " + arg);
    }
}

CGLib和JDK动态代理对比

  • JDK动态代理实现了被代理对象的接口,CGLib代理继承了被代理对象。
  • JDK动态代理和CGLib代理都在运行期生成字节码,JDK动态代理直接写Class字节码,CGLib代理使用ASM框架写Class字节码,CGLib代理实现更加复杂,生成代理类比JDK动态代理效率低。
  • JDK动态代理调用代理类方法通过反射机制,CGLib代理是通过FastClass机制直接调用方法的,CGLib代理的执行效率更高。

动态代理和静态代理的本质区别:

  • 静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类需要同步增加,违背了开闭原则。
  • 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循了开闭原则。
  • 若动态代理需要对目标类增强逻辑进行扩展,结合策略模式,只需要新增策略便可以完成。

代理模式的优缺点:

  • 优点:

    代理模式能将代理对象与真实被调用对象分离。
    在一定程度上降低了系统的耦合性,扩展性号好。
    可以起到保护目标对象的作用。
    可以增强目标对象的功能。

  • 代理模式能将代理对象与真实被调用对象分离。
  • 在一定程度上降低了系统的耦合性,扩展性号好。
  • 可以起到保护目标对象的作用。
  • 可以增强目标对象的功能。
  • 缺点:

    代理模式会造成系统设计中类的数量增加。
    在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢。
    增加了系统的复杂度。

  • 代理模式会造成系统设计中类的数量增加。
  • 在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢。
  • 增加了系统的复杂度。

装饰器模式

装饰模式:“为已有的功能动态添加更多的的功能的一种方式”。

  • 当系统需要新功能的时候可以选择向旧的类中添加新的代码,这些代码通常装饰了原有类的核心职责或主要行为。但这种方式在类中加入了新的字段和新的逻辑,从而增加了主类的复杂度。并且这些新加入的代码只有特定情况下才会执行。
  • 装饰者模式将要添加(装饰如缓冲功能)的功能放在了一个类中,并让这个类包装要装饰的对象(如BufferInputStream包装InputStream,BufferReader包装了Reader)。这样客户端就可以有选择的,按顺序的自定义的使用包装类对象。
  • 包装者模式将类中的装饰功能和核心功能区分开,去除了相关类中重复的装饰逻辑。

Component: 需要装饰的对象接口

ConcertComponent:装饰对象,可以给这个对象扩展功能

Decorator:继承Component,用于扩展Component的功能

ConcertDecoratorA:具体的装饰对象A,扩展了字段

ConcertDecoratorB:具体的装饰对象B,扩展了方法

例如:

DoClass作为父类抽象类, Courses课程类和Teacher老师类都继承DoClass类(或实现同一接口),且Courses含有DoClass对象的成员变量, 构造方法传入DoClass对象重写DoClass对象行为时调用DoClass方法(调用的实际上是传入类这里是Teacher的实现方法)。English英语类和Math数学类继承课程对象分别扩展一个方法,并对上课行为扩展了一些动作。

DoClass

public abstract class DoClass {
    public abstract void doClass();
}

Teacher

public class Teacher extends DoClass{

    private String name;

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

    @Override
    public void doClass() {
        System.out.print(name + " 今天上课 ");
    }
	//getter...setter...
}

Courses

public class Courses extends DoClass{
    DoClass doClass;

    public Courses(DoClass doClass){
        this.doClass = doClass;
    }

    @Override
    public void doClass() {
        if (doClass != null){
            doClass.doClass();
        }
    }
}

English

编写一个英语类,装饰老师对象扩展英语课的动作

编写一个英语类,装饰老师对象扩展英语课的动作

public class English extends Courses{

    public English(DoClass doClass) {
        super(doClass);
    }

    @Override
    public void doClass() {
        doClass.doClass();
        makeEnglish();
    }

    public void makeEnglish() {
        System.out.print(" 上英语课 教英语 ");
    }
}

Math

编写一个数学类,装饰老师对象扩展数学课的动作

编写一个数学类,装饰老师对象扩展数学课的动作

public class Math extends Courses{

    public Math(DoClass doClass) {
        super(doClass);
    }

    @Override
    public void doClass() {
        doClass.doClass();
        makeMath();
    }

    public void makeMath(){
        System.out.println(" 上数学课 教数学 ");
    }
}

写一个主方法Main

public class Main {
    public static void main(String[] args) {
        Teacher teacher = new Teacher("axianibiru"); //实例化装饰对象
        English english = new English(teacher); //实例化英语包装类对装饰对象教师类进行包装
        Math math = new Math(english); //实例化数学包装类对装饰对象教师类进行包装
        math.doClass(); //调用包装后的方法
    }
}

输出:

axianibiru 今天上课  上英语课 教英语  上数学课 教数学 

生产者消费者模式

生产者消费者模式:一种并发同步模式(producer-consumer),由生产者进程/线程产生数据放入缓冲区,由消费者进程/线程取出数据进行计算。

在多线程开发中,如果生产者生产数据的速度很快,而消费者消费数据的速度很慢,那么生产者就必须等待消费者消费完了才能产生数据。

如果消费者大于生产者那么消费者就会经常处于等待状态。

所以引入了缓冲区,供生产者存放数据,供消费者提取数据,起到一个数据缓存的作用用于平衡生产者和消费者之间的速度。同时也达到了解耦的作用(缓冲区作为中间层次分离了生产者和消费者的直接依赖)

  • 在多线程开发中,如果生产者生产数据的速度很快,而消费者消费数据的速度很慢,那么生产者就必须等待消费者消费完了才能产生数据。
  • 如果消费者大于生产者那么消费者就会经常处于等待状态。
  • 所以引入了缓冲区,供生产者存放数据,供消费者提取数据,起到一个数据缓存的作用用于平衡生产者和消费者之间的速度。同时也达到了解耦的作用(缓冲区作为中间层次分离了生产者和消费者的直接依赖)

原型模式

(Prototype)使用原型实例指定创建对象的种类, 并通过拷贝这些原型创建新对象。

  • 原型模式就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节。
  • 使用克隆Clone实现,分为浅拷贝和深拷贝

例子:

package com.design.prototype;

public class Jingubang {
    public float h = 100;
    public float d = 10;
    
    public void big(){
        h *= 2;
        d *= 2;
    }
    
    public void small(){
        h /= 2;
        d /= 2;
    }
}

package com.design.prototype;

import java.util.Date;

public class Monkey {
    public int height;
    public int wight;
    public Date brithday;
}
package com.design.prototype;

import java.io.*;
import java.util.Date;

public class QiTianDaSheng extends Monkey implements Cloneable, Serializable {
    public Jingubang jingubang;

    public QiTianDaSheng(){
        this.jingubang = new Jingubang();
        this.brithday = new Date();
    }

    @Override
    public Object clone(){
        return this.deepClone();
    }

    public Object deepClone(){ //深克隆
        try {
            QiTianDaSheng qiTianDaSheng;
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)){
                objectOutputStream.writeObject(this);
            }

            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            try (ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)){
                qiTianDaSheng = (QiTianDaSheng) objectInputStream.readObject();
                qiTianDaSheng.brithday = new Date();
            }

            return qiTianDaSheng;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
}

委派模式

委派模式的基本作用就是负责任务的调用和分配,跟代理模式很像,可以看做一种特殊情况下的静态的全权代理,但是代理模式注重过程而委派模式注重结果。

DispatcherServlet使用到了委派模式

DispatcherServlet使用到了委派模式

例子:

public interface IEmployee {
    void doing(String command);
}

public class EmployeeA implements IEmployee{
    @Override
    public void doing(String command) {
        System.out.println("我是A,现在开始干" + command +"活");
    }
}

public class EmployeeB implements IEmployee{

    @Override
    public void doing(String command) {
        System.out.println("我是B,现在开始干" + command +"活");
    }
}

public class EmployeeC implements IEmployee{
    @Override
    public void doing(String command) {
        System.out.println("我是C,现在开始干" + command +"活");
    }
}
public class Leader implements IEmployee{
    private Map<String, IEmployee> targets = new HashMap<>();

    public Leader(){
        targets.put("加密", new EmployeeA()); //A负责加密
        targets.put("登录", new EmployeeB()); //B负责登录
    }

    @Override
    public void doing(String command) {
        targets.get(command).doing(command); //接收command,并派发给负责人执行
    }
}

public class Boss {
    public void command(String command, Leader leader) {
        leader.doing(command);
    }
}
public class DelegateApp {
    public static void main(String[] args) {
        new Boss().command("登录", new Leader()); //发送登录指令,交由Leader进行处理,Leader根据传入的指令进行调度,将指令派发给目标类执行
    }
}

输出:

我是B,现在开始干登录活

策略模式

策略模式是指定义了算法家族并分别封装起来,让他们之间可以互相替换,此模式使得算法的变化不会影响使用算法的用户。

应用场景:

  • 系统中有很多类,他们的区别仅存在于行为不同。
  • 一个系统需要动态地在几种算法中选择一种。
//工厂模式月策略模式
public class PayStrategy {
    public static final String ALI_PAY = "AliPay";

    public static final String DEFAULT_PAY = "AliPay";

    private static Map<String, Payment> payStraregy = new HashMap<String, Payment>();
    static {
        payStraregy.put(ALI_PAY, new AliPay());
    }

    public static Payment get(String payKey) {
        if (!payStraregy.containsKey(payKey)) {
            return payStraregy.get(DEFAULT_PAY);
        }
        return payStraregy.get(ALI_PAY);
    }
}

策略模式优点:

  • 符合开闭原则。
  • 策略模式可以避免多重条件语句,如if…else、switch语句。
  • 使用策略模式可以提高算法的保密性和安全性。

缺点:

  • 客户端必须知道所有策略,并自行决定使用哪一种策略类。
  • 代码中会产生非常多的策略类,增加了代码的维护难度。

策略模式和委派模式Demo:

package com.axianibiru.springstudy.dispatcher;

import com.axianibiru.springstudy.controller.MemberController;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class DispatcherServlet extends HttpServlet {

    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init() throws ServletException {
        try {
            Class<?> memberController = MemberController.class;
            handlerMapping.add(new Handler()
                    .setController(memberController.newInstance())
                    .setMethod(memberController.getMethod("getMemberById", new Class[]{String.class}))
                    .setUrl("/web/getMemberById.json"));
        }catch (Exception e){}
    }

    public void doDispatcher(HttpServletRequest request, HttpServletResponse response) {
        //获取用户请求的URL
        //按照J2EE的标准一个URL对应一个Servlet
        String uri = request.getRequestURI();

        //通过URL获取handlerMapping(URL是策略常量)
        Handler handle = null;
        for (Handler h: handlerMapping) {
            if (uri.equals(h.getUrl())) {
                handle = h;
                break;
            }
        }

        //将具体的任务分发给Method(通过反射调用对应的方法)
        Object object = null;
        try {
            object = handle.getMethod().invoke(handle.getController(), request.getParameter("mid"));
        } catch (InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatcher(req, resp);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    class Handler {
        private Object controller;
        private Method method;
        private String url;

        public Object getController(){
            return controller;
        }

        public Handler setController(Object controller){
            this.controller = controller;
            return this;
        }

        public Method getMethod() {
            return method;
        }

        public Handler setMethod(Method method) {
            this.method = method;
            return this;
        }

        public String getUrl() {
            return url;
        }

        public Handler setUrl(String url) {
            this.url = url;
            return this;
        }
    }
}

模板方法模式

  • 当使用继承的时候,并且肯定这个继承有意义,就应该要称为子类的模板,所有重复的代码都应该要上升到父类去,而不是让每个子类都去重复。
  • 当我们要在某一细节层次一致的一个过程或一系列步骤,但其个别步骤在更详细的层次上的实现可能不同时,我们通常考虑使用模板方法模式来处理。
  • 通过在细节上让子类重写,父类建立所有模板的方式完成对重复内容的复用。
  • 所以定义是:顶一个一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤(在父类中给出逻辑框架,而逻辑的组成步骤在响应的抽象操作中,延迟到子类实现)。

    模板方法通过吧不变行为搬到超类,去除子类中重复的代码类体现对它的优势。
    当可变和不可变的行为在方法的子类中混合在一起的时候,不可变的行为就会在子类从重复出现。我们同股哟模板方法模式将这些行为搬到单一的地方,这样就帮助子类拜托重复的不变行为的纠缠。
    一次性实现一个算法的不可变部分,并将可变部分留给子类来实现(例如IO中的一些方法)。
    各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。

  • 模板方法通过吧不变行为搬到超类,去除子类中重复的代码类体现对它的优势。
  • 当可变和不可变的行为在方法的子类中混合在一起的时候,不可变的行为就会在子类从重复出现。我们同股哟模板方法模式将这些行为搬到单一的地方,这样就帮助子类拜托重复的不变行为的纠缠。
  • 一次性实现一个算法的不可变部分,并将可变部分留给子类来实现(例如IO中的一些方法)。
  • 各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。

利用模板模式重构JDBC操作业务场景:

这样DAO层只需要继承JdbcTemplate并实现RowMapper就行了

这样DAO层只需要继承JdbcTemplate并实现RowMapper就行了

package com.design.template;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class JdbcTemplate {
    private DataSource dataSource;

    public JdbcTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public List<?> executeQuery(String sql, RowMapper<?> rowMapper, Object[] values) {
        try {
            //获取连接
            Connection conn = this.getConnection();
            //创建语句集
            PreparedStatement pstm = this.createPreparedStatement(conn, sql);
            //执行语句集
            ResultSet rs = this.executeQuery(pstm, values);
            //处理结果集
            List<?> result = this.paresResultSet(rs, rowMapper);
            //关闭结果集
            this.closeResultSet(rs);
            //关闭语句集
            this.closeStatement(pstm);
            //关闭连接
            this.closeConnection(conn);
            return result;
        }catch (Exception e){
            e.printStackTrace();
        }

        return null;
    }



    protected void closeResultSet(ResultSet rs) throws SQLException {
        if (rs != null){
            rs.close();
        }
    }

    protected void closeConnection(Connection conn) throws SQLException {
        if (conn != null){
            conn.close();
        }
    }

    protected void closeStatement(PreparedStatement pstm) throws SQLException {
        if (pstm != null){
            pstm.close();
        }
    }

    protected ResultSet executeQuery(PreparedStatement pstm, Object[] values) throws SQLException {
        for (int i = 0; i < values.length; i++) {
            pstm.setObject(i, values[i]);
        }

        return pstm.executeQuery();
    }

    protected List<?> paresResultSet(ResultSet rs, RowMapper<?> rowMapper) throws Exception {
        List<Object> result = new ArrayList<Object>();
        int rowNum = 1;
        while (rs.next()){
            result.add(rowMapper.mapRow(rs, rowNum++));
        }

        return result;
    }

    protected PreparedStatement createPreparedStatement(Connection conn, String sql) throws SQLException {
        return conn.prepareStatement(sql);
    }

    protected Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

}

RowMapper接口:

package com.design.template;

import java.sql.ResultSet;

public interface RowMapper<T> {
    T mapRow(ResultSet rs, int rowNum) throws Exception;
}

优点:

  • 利用模板模式将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性。
  • 将不同的代码放到不同的子类中,通过对子类的扩展,增加新的行为,可以提高代码的扩展性。
  • 把不变的行为写在父类中,去除了子类重复的代码,提供了一个很好地代码复用平台,符合开闭原则。

缺点:

  • 每个抽象类都需要一个子类来实现,导致了类的数量增加。
  • 类数量的增加间接地增加了系统的复杂性。
  • 因为继承关系自身的缺点,如果父类添加新的抽象方法,所有子类都需要修改一遍。

适配器模式

将一个类的接口转换成客户端希望的另一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。

当系统的数据和行为都正确,但接口不符合时,我们应该考虑使用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些显存的类,但是接口又与复用环境要求不一致的情况。

  • 当系统的数据和行为都正确,但接口不符合时,我们应该考虑使用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些显存的类,但是接口又与复用环境要求不一致的情况。
  • 想使用一个类,但她的接口,也就是她的方法和要求的不相同时,就应该考虑使用适配器模式(已经存在的类方法和需求不匹配,但是方法结果相同或者相似)。
  • 两个类所做的事情相同或者相似,但是具有不同的接口时使用它。
  • 在双方都不太容易修改的时候再使用适配器模式(适配器模式不是软件初始阶段考虑的设计模式,而是随着软件的发展,由于不同产品,不同厂家造成功能类似而接口不同的问题的解决方案)。
  • 只有预防接口不同的问题,防止不匹配的问题发生:在只有小的接口不统一的问题发生时,及时重构,问题不至于扩大;只有碰到无法改变原有设计和代码的情况时,才考虑适配(事后控制不如事中控制,事中控制不如事前控制)。
  • 策略模式主要用于将一系列家族封装(依赖于接口),适配器模式讲究对于不同接口/类之间的兼容(适配器的实现不依赖于接口)。

Spring DispatcherServlet中的适配器模式:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) { //使用简单工厂模式进行适配器的选择
				if (adapter.supports(handler)) { //查看适配器是否匹配
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

优点:

  • 能提高类的透明性和复用性,现有的类会被复用当不需要改变。

    目标类和配适器类解耦(使用Object传入,真实的处理在目标类中(强转为目标类就可以复用目标类的方法),传入高层抽象,不依赖于具体实现遵循了依赖倒转原则,完成了对于原有类方法的扩展/复用)。

  • 目标类和配适器类解耦(使用Object传入,真实的处理在目标类中(强转为目标类就可以复用目标类的方法),传入高层抽象,不依赖于具体实现遵循了依赖倒转原则,完成了对于原有类方法的扩展/复用)。
  • 目标类和适配器解耦,提高程序的扩展性。
  • 在很多业务场景中符合开闭原则。

缺点:

  • 在适配器代码编写过程中需要进行全面考虑,可能会增加系统的复杂性。
  • 增加了代码的阅读难度,降低了代码的可读性,过多使用适配器会使得系统的代码变得混乱。

装饰器模式和适配器模式的对比:

  • 装饰者模式和配适器模式都属于包装器模式(Wrapper Pattern)
  • 装饰者模式主要用于解决父类同一的问题,配置器主要用于解决兼容问题。
装饰者模式 适配器模式
形式 属于一种特别的配适器模式 没有承继关系,装饰者模式有层级关系
定义 装饰者和被装饰者实现同一个接口,主要目的是扩展之后依然保留OOP关系。 适配器进和被适配者没有必然的联系,通常采用继承或者代理的形式进行包装。
关系 满足is-a的关系(a是特殊的b,所以a继承b) 满足has-a(a包括b,但不是b)
功能 注重覆盖、扩展 注重兼容、转换
设计 前置考虑 后置考虑

观察者模式

观察者模式又叫做发布-订阅模式(Publish/Subscribe)模式

观察者模式又叫做发布-订阅模式(Publish/Subscribe)模式

观察者模式定一个一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象再状态发生改变时,会通知所有观察者对象,使他们能够自动更新自己。

  • 观察者模式将相互依赖的对象抽象成两方面,一方面依赖于另一方面,这时使用观察这模式可以将这两者独立封装在独立的对象中各自独立地改变和复用。
  • 观察者模式所做的工作就是解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。使得各自的变化都不会影响到另一边的变化。
  • 观察者和模式让多个对象同时监听一个对象主题,当主体对象发生变化时,她的所有依赖者(观察者)都会收到通知并更新,属于行为型模式。
  • 观察者模式有时又会被叫做发布订阅模式。观察者模式主要用于在关联行为之间建立一套触发机制的场景。

Spring中的监听机制:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

   public ContextLoaderListener() {
   }

   public ContextLoaderListener(WebApplicationContext context) {
      super(context);
   }

   @Override
   public void contextInitialized(ServletContextEvent event) {
      initWebApplicationContext(event.getServletContext());
   }

   @Override
   public void contextDestroyed(ServletContextEvent event) {
      closeWebApplicationContext(event.getServletContext());
      ContextCleanupListener.cleanupAttributes(event.getServletContext());
   }

}

优点:

  • 在观察者和被观察者之间建立了一个抽象的耦合
  • 观察者模式支持广播通信

缺点:

  • 观察者之间有过多的细节依赖、时间消耗多、程序的复杂性更高
  • 使用不当会出现循环调用。
————————

Open and closed principle

< strong > software entities (classes, modules, functions, etc.) should be extensible, but cannot be modified < / strong >

Don’t expect that the system will never change after determining the requirements at the beginning. To make the designed software easy to maintain and not easy to have problems, you have to < strong > expand more and modify less < / strong >
But the module is not absolutely closed, so you need to choose what changes the designed module has: guess which classes are most likely to change, and then construct abstractions to isolate those changes.
Classes that have changed; React immediately, abstract its functions, and then use inheritance, polymorphism, composition and other methods to isolate the specific implementation details and high-level modules, so that the subsequent modifications to the modules are based on extraction

  • Don’t expect that the system will never change after determining the requirements at the beginning. To make the designed software easy to maintain and not easy to have problems, we have to expand more and modify less
  • But the module is not absolutely closed, so you need to choose what changes the designed module has: guess which classes are most likely to change, and then construct abstractions to isolate those changes.
  • Classes that have changed; React immediately, abstract its functions, and then use inheritance, polymorphism, composition and other methods to isolate the specific implementation details and high-level modules, so that the subsequent modifications to the modules are based on extraction

< strong > it is maintainable, extensible, reusable and flexible. Rejecting immature abstraction is as important as abstraction itself

In fact, it abstracts the functions of frequently changing classes (classes that need to be changed frequently)

In fact, it abstracts the functions of frequently changing classes (classes that need to be changed frequently)

Dependence Inversion Principle

The dependency inversion principle (DIP) refers to inverting the dependency relationship between two modules into a dependency abstract class or interface.

  • High level modules should not rely on low-level modules, and both should rely on abstraction;
  • Abstract should not depend on details, details should depend on abstraction.

< strong > dependency < / strong >: in programming, if one module a uses / calls another module B, we call module a dependent on module B.

< strong > high level module and low level module < / strong >: often in an application, we have some low-level classes that implement some basic or primary operations, which we call low-level modules; In addition, there are some high-level classes that encapsulate some complex logic and rely on low-level classes, which we call high-level modules.

< strong > dependency inversion: compared with process oriented (structured) programming, dependency is inverted in object-oriented programming. Because in traditional structured programming, high-level modules always rely on low-level modules.

< strong > a good design should make every part of the system replaceable. If the “high-level module” relies too much on the “low-level module”, on the one hand, once the “low-level module” needs to be replaced or modified, the “high-level module” will be affected; On the other hand, high-level modules are difficult to reuse

< strong > a good design should make every part of the system replaceable. If the “high-level module” relies too much on the “low-level module”, on the one hand, once the “low-level module” needs to be replaced or modified, the “high-level module” will be affected; On the other hand, high-level modules are difficult to reuse

< strong > dip solution: introduce an abstract interface layer between high-level modules and low-level modules

High level classes — & gt; Abstraction layer — & gt; Low level classes
< strong > abstract interface is the abstraction of low-level modules, which inherit or implement the abstract interface
In this way, the high-level module does not directly rely on the low-level module, but on the abstract interface layer. Abstract interfaces also do not depend on the implementation details of low-level modules, but low-level modules rely on (inherit or implement) abstract interfaces.
< strong > relationships between classes are established through the abstract interface layer

High level classes — & gt; Abstraction layer — & gt; Low level classes

< strong > abstract interface is the abstraction of low-level modules, which inherit or implement the abstract interface

In this way, the high-level module does not directly rely on the low-level module, but on the abstract interface layer. Abstract interfaces also do not depend on the implementation details of low-level modules, but low-level modules rely on (inherit or implement) abstract interfaces.

< strong > relationships between classes are established through the abstract interface layer

< strong > how to use the dependency inversion principle < / strong >

1. Rely on abstraction < / strong >

  • No variable should hold a pointer or reference to a specific class.
  • No class should derive from a concrete class.

2. Design interfaces instead of design implementations < / strong >

  • Use inheritance to avoid direct binding to classes
  • Abstract classes / interfaces: tend to change less; Abstraction is the key point, which is easy to modify and expand; Do not force modifications to those abstract interfaces / classes

3. Avoid passing dependencies < / strong >

  • Avoid high-level dependence on low-level
  • Use inheritance and abstract classes to effectively eliminate delivery dependencies

< strong > dependency inversion reduces the coupling between classes, improves system stability, improves code readability and maintainability, and reduces the risk caused by program modification

Single responsibility principle

  • As far as a class is concerned, there should be only one reason for its change
  • If a class assumes too many responsibilities, it is equivalent to coupling these responsibilities. The change of one responsibility may weaken or inhibit the ability of the class to complete other responsibilities. This coupling will lead to wrong design. When changes occur, the design will suffer unexpected damage.
  • In soft armor design, we need to find responsibilities and separate those who are still here from each other. If you can think of more than one motivation to change a class, then the class has more than one responsibility.
  • In the process of writing code, keep the interface and method single responsibility as much as possible, which is very helpful for the later maintenance of the project.

Interface isolation principle

Interface Segregation Principle (ISP) refers to using multiple special interfaces instead of a single general interface. The client should not rely on the interface it does not need.

Therefore, the following points should be paid attention to when designing the interface:

  • The dependence of one class on another should be based on the smallest interface.
  • Establish a single interface, not a huge and bloated interface.
  • The interface shall be refined as much as possible, and the methods in the interface shall be as few as possible (moderate, don’t plug everything in). The methods of other interfaces of the banking office shall be separated.

The principle of interface isolation conforms to the design idea of high cohesion and low coupling (low dependency of different modules and close connection within the same module), so that the class has good readability, extensibility and maintainability. When designing the interface, we should spend more time thinking, consider the business model, and make some predictions where changes may occur in the future. Therefore, it is very important to abstract and understand the business model.

Dimitri principle

  • Law of Demeter LOD means that an object should keep the least knowledge of other objects, that is, the least knowledge principle (LKP), to minimize the coupling between classes.
  • It emphasizes communication with friends and doesn’t talk to strangers.
    “Friend” in Dimitri’s Law refers to the current object itself, the member object of the current object, the object created by the current object, the method parameters of the current object, etc. these objects are associated, aggregated or combined with the current object, and the methods of these objects can be accessed directly.
    However, the excessive use of Dimitri’s law will make the system produce a large number of intermediary classes, which will increase the complexity of the system and reduce the communication efficiency between modules. Therefore, it is necessary to weigh repeatedly when using Dimitri’s law to ensure high cohesion and low coupling and clear structure of the system.
  • “Friend” in Dimitri’s Law refers to the current object itself, the member object of the current object, the object created by the current object, the method parameters of the current object, etc. these objects are associated, aggregated or combined with the current object, and the methods of these objects can be accessed directly.
  • However, the excessive use of Dimitri’s law will make the system produce a large number of intermediary classes, which will increase the complexity of the system and reduce the communication efficiency between modules. Therefore, it is necessary to weigh repeatedly when using Dimitri’s law to ensure high cohesion and low coupling and clear structure of the system.

< strong > it emphasizes the following two points: < / strong >

  • From the perspective of dependencies, only the objects that should be relied on.
  • From the perspective of the dependent, only the methods that should be exposed are exposed.

Richter substitution principle

If each object O1 of type T1 has an object O2 of type T2, so that the behavior of all programs P defined by T1 does not change when all objects O1 are replaced with O2, then type T2 is a subclass of type T1. (< strong > in fact, if a software entity is applicable to a parent class, it must be applicable to its subclass. All places that reference the parent class must be able to use the objects of its subclass transparently. The subclass object can replace its parent object and keep the program logic unchanged: the subclass can expand the functions of the parent class, but cannot change the original functions of the parent class < / strong >)

  • Subclasses can implement the abstract methods of the parent class, but they cannot override the non abstract methods of the parent class.
  • Subclasses can add their own unique methods.
  • When the method of the subclass overloads the method of the parent class, the preconditions of the method (i.e. the input / input parameters of the method) are more relaxed than the input parameters of the parent method.
  • When a subclass implements a method of a parent class (overriding / overloading or implementing an abstract method), the post condition of the method (i.e. the output / return value of the method) is more stringent than or the same as the parent class.

< strong > advantages of Richter’s replacement principle: < / strong >

  • The overflow of constraint inheritance is an embodiment of the opening and closing principle
  • Strengthen the robustness of the program. At the same time, it can achieve better compatibility when changing, improve the maintainability and scalability of the program, and reduce the risk introduced when changing the requirements.

Synthetic Reuse Principle

  • The principle of composite reuse refers to using object combination / inheritance instead of inheritance relationship as much as possible to achieve the purpose of software reuse, which can make the system more flexible and reduce the coupling degree between classes. The change of one class has relatively less impact on other classes.
  • Inheritance is called white box reuse, which is equivalent to exposing all implementation details to subclasses. Composition / aggregation is called black line reuse. We can’t get the implementation details of objects other than classes.
  • Typical include dependency injection (instead of creating a separate container for dependency management, it will be incorporated into the dependents to jointly manage the dependency), decorator mode (by inheriting the same interface, relying on the object that implements the same interface and directly calling the method of the dependent object for the required method, rather than calling the method of the parent class through inheritance, which completes the decoupling between objects)

Simple factory mode

SimpleDesgin

The factory class of the simple factory class contains the necessary logical judgment. The relevant classes are dynamically instantiated according to the selection conditions of the client, which removes the dependence on specific products for the client.

However, if you want to add functions, you need to modify the original class. This violates the “open closed principle”, so there is a factory method.

  • Code specification (naming specification, fault tolerance)
  • Object oriented programming (easy to maintain, expand and reuse)
  • Layering (separating business logic from interface logic)
  • Loose coupling (separating different logic)
  • Build a simple factory (create instances using a separate class)

Using a calculator as an example:

Operation

public abstract class Operation {
    private double lastResult = 0;

    public abstract double doOperation(double numberA, double numberB);

    public double getLastResult() {
        return lastResult;
    }

    public void setLastResult(double lastResult) {
        this.lastResult = lastResult;
    }
}

OperationAdd

public class OperationAdd extends Operation {

    @Override
    public double doOperation(double numberA, double numberB) {
        setLastResult(numberA + numberB);
        return getLastResult();
    }
}

OperationSub

public class OperationSub extends Operation {
    @Override
    public double doOperation(double numberA, double numberB) {
        setLastResult(numberA - numberB);
        return getLastResult();
    }
}

OperationMul

public class OperationMul extends Operation {

    @Override
    public double doOperation(double numberA, double numberB) {
        setLastResult(numberA * numberB);
        return getLastResult();
    }
}

OperationDiv

public class OperationDiv extends Operation{
    @Override
    public double doOperation(double numberA, double numberB) throws Exception {
        if (numberB == 0){
            throw new Exception("除数不能为0");
        }
        setLastResult(numberA / numberB);
        return getLastResult();
    }
}

Factory method model

Factory method pattern: define an interface for creating objects and let subclasses decide which class to instantiate. Factory methods delay the instantiation of a class to its subclasses (dependency inversion principle).

If a factory class and branch are coupled (the upper factory type < strong > depends on the specific implementation of the lower product factory), we will abstract the factory class into an interface according to the dependency inversion principle. This interface has only one method, which is to create the factory method of the abstract product. Then all factories that want to produce concrete classes implement the abstract factory interface, so if we need to add new functions, we only need to add products and create factories of products, These two depend on the product abstract interface and the factory abstract interface respectively (here, following the formula of the opening and closing principle, the dependence of the upper layer on the lower layer is inverted into the upper layer method implemented by the lower layer. The upper layer depends on the upper layer abstract interface, which reduces the coupling and maintains the advantages of encapsulating the process of creating objects).

If a factory class and branch are coupled (the upper factory type < strong > depends on the specific implementation of the lower product factory), we will abstract the factory class into an interface according to the dependency inversion principle. This interface has only one method, which is to create the factory method of the abstract product. Then all factories that want to produce concrete classes implement the abstract factory interface, so if we need to add new functions, we only need to add products and create factories of products, These two depend on the product abstract interface and the factory abstract interface respectively (here, following the formula of the opening and closing principle, the dependence of the upper layer on the lower layer is inverted into the upper layer method implemented by the lower layer. The upper layer depends on the upper layer abstract interface, which reduces the coupling and maintains the advantages of encapsulating the process of creating objects).

< strong > example: < / strong >

Fruit products:

public interface Fruit {
    void eatFruit();
}

public class Apple implements Fruit{
    @Override
    public void eatFruit() {
        System.out.println("吃了一个苹果");
    }
}

public class Cherry implements Fruit{
    @Override
    public void eatFruit() {
        System.out.println("吃了一个樱桃");
    }
}

Fruit factories:

public interface FruitFactory {
    Fruit creatFruit();
}

public class AppleFactory implements FruitFactory{
    @Override
    public Fruit creatFruit() {
        return new Apple();
    }
}

public class CherryFactory implements FruitFactory{
    @Override
    public Fruit creatFruit() {
        return new Cherry();
    }
}

Abstract factory pattern

< strong > Abstract Factory: < / strong > provides an interface to create a series of related or interdependent objects without specifying their specific classes. (when there are different series of operations for the same product, we add the creation methods of different series in the abstract factory)

< strong > using reflection to implement Abstract Factory: < / strong >

Mapper is an abstract interface for operating data. Empmapper and deptmapper are abstract classes for data operation of two tables. Empmapperinpl is an implementation class for operating tables in the database, but there are two kinds of databases, so there are two implementations.

Mapper:

public interface Mapper<T> {
    void insert(T t);
    T select();
}

DeptMapper:

public abstract class DeptMapper implements Mapper<Dept>{

}

public class DeptMapperMysqlImpl extends DeptMapper {

    @Override
    public void insert(Dept dept) {
        System.out.println("insert into dept [Mysql]");
    }

    @Override
    public Dept select() {
        System.out.println("select from dept [Mysql]");
        return new Dept();
    }
}

public class DeptMapperOracleImpl extends DeptMapper {
    @Override
    public void insert(Dept dept) {
        System.out.println("insert into mysql [oracle]");
    }

    @Override
    public Dept select() {
        System.out.println("select from dept [oracle]");
        return new Dept();
    }
}

EmpMapper:

public abstract class EmpMapper implements Mapper<Emp>{

}

public class EmpMapperMysqlImpl extends EmpMapper {
    @Override
    public void insert(Emp emp) {
        System.out.println("insert into emp [Mysql]");
    }

    @Override
    public Emp select() {
        System.out.println("select from emp [Mysql]");
        return new Emp();
    }
}

public class EmpMapperOracleImpl extends EmpMapper {
    @Override
    public void insert(Emp emp) {
        System.out.println("insert into emp [Oracle]");
    }

    @Override
    public Emp select() {
        System.out.println("select from emp [Oracle]");
        return new Emp();
    }
}

DataAccess:

public class DataAccess {
    private static final String nameSpace = "com.design.abstractFactoryDesign.pro.";
    private static String database = "Mysql";

    public static EmpMapper getEmpMapper() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        String className = nameSpace + "EmpMapper"+ database + "Impl";
        Class<?> clazz = Class.forName(className);
        return (EmpMapper)clazz.newInstance();
    }

    public static DeptMapper getDeptMapper() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        String className = nameSpace + "DeptMapper"+ database + "Impl";
        Class<?> clazz = Class.forName(className);
        return (DeptMapper)clazz.newInstance();
    }

    public static String getDatabase() {
        return database;
    }

    public static void setDatabase(String database) {
        DataAccess.database = database;
    }
}

Create a main thread to test:

public class AbstractFactoryDesign {
    public static void main(String[] args) {
        try {
            DeptMapper deptMapper = DataAccess.getDeptMapper();
            deptMapper.insert(null);
            deptMapper.select();

            DataAccess.setDatabase("Oracle");

            EmpMapper empMapper = DataAccess.getEmpMapper();
            empMapper.insert(null);
            empMapper.select();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

Output:

insert into dept [Mysql]
select from dept [Mysql]
insert into emp [Oracle]
select from emp [Oracle]

Singleton mode

Singleton mode is a controlled access to the unique instance: let the class itself save the unique instance of the class (privatized construction, variables save the unique instance (there are two ways to save: hungry and lazy))

< strong > lazy singleton < / strong >: instantiate yourself only when you are referenced for the first time (there is a thread safety problem of multithreading, you need to lock and judge whether it is empty).

Double lock checking mechanism (DCL) is adopted, and volatile keyword is used to prohibit reordering

Double lock checking mechanism (DCL) is adopted, and volatile keyword is used to prohibit reordering

public class SlobSingleton {
    volatile private static SlobSingleton instance;
    private static final Object lock = new Object();
    private SlobSingleton(){}

    public static SlobSingleton getInstance(){
        if (instance == null){
            synchronized(lock){
                if (instance == null){
                    return new SlobSingleton();
                }
            }
        }
        return instance;
    }

}

Using static inner classes to implement lazy mode

Using static inner classes to implement lazy mode

class Demo{
    static {
        System.out.println("Demo static part");
    }
    private Demo(){
        System.out.println("Demo");
    }
    public static void print(){
        System.out.println("Demo static print");
    }
    public static Demo getInstance(){
        return InnerDemo.demo; //InnerDemo只有在被调用的时候才会加载
    }
    private static class InnerDemo{
        private static Demo demo = new Demo(); //先加载静态字段

        static { //后加载静态代码块
            System.out.println("InnerDemo static part");
        }
    }
}

Because after a class is loaded once, the class information will be stored in the method area, the corresponding class object will be stored in the heap, and the initial assignment will be made to the non final class variable in the initialization stage. Here is the static variable of the external class generated by the internal class in the heap during loading.
The external class will be loaded only when the internal class is called for the first time. The only external class reference is instantiated when the internal class is loaded. In this way, the lazy mode is realized by using the class loading mechanism.

Because after a class is loaded once, the class information will be stored in the method area, the corresponding class object will be stored in the heap, and the initial assignment will be made to the non final class variable in the initialization stage. Here is the static variable of the external class generated by the internal class in the heap during loading.

The external class will be loaded only when the internal class is called for the first time. The only external class reference is instantiated when the internal class is loaded. In this way, the lazy mode is realized by using the class loading mechanism.

< strong > hungry Chinese single example < / strong >: instantiate yourself when you are loaded (static variables, space allocation in the preparation stage of class initialization, and assignment in the initialization stage).

public class HungerSingleton {
    private static HungerSingleton INSTANCE = new HungerSingleton();

    private HungerSingleton(){}

    public static HungerSingleton getInstance(){
        return INSTANCE;
    }

}

Registered singleton mode

Registered singleton mode, also known as registered singleton mode, is to register each singleton to a certain place and obtain the instance with a unique ID. There are two types: enumeration singleton mode and container singleton mode.

< strong > enumerated singleton mode < / strong >

  • It avoids being loaded by the class loader many times, and cannot destroy the enumeration type singleton through reflection, because an exception will be thrown when it is found to be an enumeration type.
public enum EnumSingleton {
    INSTANCE;
    private Object data;
    
    public Object getData(){
        return data;
    }
    
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

< strong > container single example < / strong >

  • Container singleton is applicable to the case with many instances, which is convenient for management. But it’s non thread safe!
public class ContainerSingleton {
    private ContainerSingleton(){}
    private static Map<String, Object> ioc = new ConcurrentHashMap<>();
    public static Object getBean(String className){
        synchronized (ioc){
            if (!ioc.containsKey(className)){
                Object o = null;
                try {
                    o = Class.forName(className).newInstance();
                    ioc.put(className, o);
                }catch (Exception e){
                    e.printStackTrace();
                }
                return o;
            } else {
                return ioc.get(className);
            }
        }
    }
}

proxy pattern

  • Proxy mode: provides a proxy for other objects to control access to this object

Super class defines the common interface between subobject and proxy, so proxy can be used wherever subobject needs to be used

Application scenario:

  • Remote proxy: the local representative of a remote object. Local method calls to this object will be forwarded to the remote
  • Virtual agent: virtualize expensive objects
  • Security agent: controlling access
  • Intelligent guidance: when calling real objects, deal with other things

example:

Interface.class

public interface Interface {
    void doSomething();
    void doSomethingElse(String arg);
}

InterfaceImplFirst.class

public class InterfaceImplFirst implements Interface{

    public void doSomething() {
        System.out.println("InterfaceImplFirst: doSomething");
    }

    public void doSomethingElse(String arg) {
        System.out.println("InterfaceImplFirst: doSomethingElse " + arg);
    }
}

InterfaceImplTwo.class

public class InterfaceImplTwo implements Interface {
    private final Interface interfaceImplFirst;

    public InterfaceImplTwo(Interface interfaceImplFirst){
        this.interfaceImplFirst = interfaceImplFirst;
    }

    public void doSomething() {
        interfaceImplFirst.doSomething();
        System.out.println("InterfaceImplTwo: doSomething");
    }

    public void doSomethingElse(String arg) {
        interfaceImplFirst.doSomethingElse(arg);
        System.out.println("doSomethingElse: doSomethingElse " + arg);
    }
}

< strong > comparison between cglib and JDK dynamic agents < / strong >

  • JDK dynamic agent implements the interface of the proxy object, and cglib agent inherits the proxy object.
  • Both JDK dynamic agent and cglib agent generate bytecode during operation. JDK dynamic agent directly writes class bytecode, and cglib agent uses ASM framework to write class bytecode. The implementation of cglib agent is more complex, and the generation agent is inefficient compared with JDK dynamic agent.
  • JDK dynamic proxy calls proxy class methods through reflection mechanism. Cglib proxy calls methods directly through fastclass mechanism. Cglib proxy has higher execution efficiency.

< strong > the essential difference between dynamic agent and static agent: < / strong >

  • The static agent can only complete the agent operation manually. If the proxy class adds new methods, the proxy class needs to be added synchronously, which violates the opening and closing principle.
  • The dynamic agent adopts the method of dynamically generating code at runtime, cancels the extension restrictions on the proxy class, and follows the opening and closing principle.
  • If the dynamic agent needs to extend the target class enhancement logic, combined with the policy mode, it only needs to add a new policy.

< strong > advantages and disadvantages of agent mode: < / strong >

  • advantage:
    Proxy mode can separate the proxy object from the real called object.
    It reduces the coupling of the system to a certain extent and has good expansibility.
    It can protect the target object.
    You can enhance the functionality of the target object.
  • Proxy mode can separate the proxy object from the real called object.
  • It reduces the coupling of the system to a certain extent and has good expansibility.
  • It can protect the target object.
  • You can enhance the functionality of the target object.
  • Disadvantages:
    Agent mode will increase the number of classes in system design.
    Adding a proxy object to the client and target objects will slow down the request processing.
    It increases the complexity of the system.
  • Agent mode will increase the number of classes in system design.
  • Adding a proxy object to the client and target objects will slow down the request processing.
  • It increases the complexity of the system.

Decorator mode

< strong > decoration mode < / strong >: “a way to dynamically add more functions to existing functions”.

  • When the system needs new functions, you can choose to add new code to the old class. These codes usually decorate the core responsibilities or main behaviors of the original class. However, this method adds new fields and new logic to the class, which increases the complexity of the main class. And these newly added code will only be executed under specific circumstances.
  • Decorator mode puts the functions to be added (such as buffer function) into a class, and lets this class wrap the objects to be decorated (such as bufferinputstream wrapping InputStream, bufferreader wrapping reader). In this way, the client can selectively and customize the use of wrapper objects in order.
  • The wrapper pattern separates the decoration function from the core function in the class, and removes the repeated decoration logic in the related class.

< strong > component: < / strong > object interface to be decorated

< strong > concertcomponent: < / strong > decorate an object. You can extend the function of this object

Decorator:继承Component,用于扩展Component的功能

< strong > concertdecoratora: < / strong > the specific decoration object a extends the field

< strong > concertdecoratorb: < / strong > the specific decoration object B extends the method

For example:

Doclass is an abstract class of the parent class. Courses course class and teacher class both inherit the doclass class (or implement the same interface), and courses contain member variables of the doclass object. When the constructor passes in the doclass object to rewrite the behavior of the doclass object, it calls the doclass method (in fact, it is the incoming class, here is the implementation method of teacher). English class and math class inherit the course object, extend a method respectively, and expand some actions for class behavior.

DoClass

public abstract class DoClass {
    public abstract void doClass();
}

Teacher

public class Teacher extends DoClass{

    private String name;

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

    @Override
    public void doClass() {
        System.out.print(name + " 今天上课 ");
    }
	//getter...setter...
}

Courses

public class Courses extends DoClass{
    DoClass doClass;

    public Courses(DoClass doClass){
        this.doClass = doClass;
    }

    @Override
    public void doClass() {
        if (doClass != null){
            doClass.doClass();
        }
    }
}

English

Write an English class to decorate the teacher object and expand the action of English class

Write an English class to decorate the teacher object and expand the action of English class

public class English extends Courses{

    public English(DoClass doClass) {
        super(doClass);
    }

    @Override
    public void doClass() {
        doClass.doClass();
        makeEnglish();
    }

    public void makeEnglish() {
        System.out.print(" 上英语课 教英语 ");
    }
}

Math

Write a math class to decorate the teacher object and expand the action of math class

Write a math class to decorate the teacher object and expand the action of math class

public class Math extends Courses{

    public Math(DoClass doClass) {
        super(doClass);
    }

    @Override
    public void doClass() {
        doClass.doClass();
        makeMath();
    }

    public void makeMath(){
        System.out.println(" 上数学课 教数学 ");
    }
}

Write a main method < strong > main < / strong >

public class Main {
    public static void main(String[] args) {
        Teacher teacher = new Teacher("axianibiru"); //实例化装饰对象
        English english = new English(teacher); //实例化英语包装类对装饰对象教师类进行包装
        Math math = new Math(english); //实例化数学包装类对装饰对象教师类进行包装
        math.doClass(); //调用包装后的方法
    }
}

Output:

axianibiru 今天上课  上英语课 教英语  上数学课 教数学 

Producer consumer model

Producer consumer mode: a producer consumer mode, in which data generated by the producer process / thread is put into the buffer, and the consumer process / thread takes out the data for calculation.

In multithreading development, if the producer produces data very fast and the consumer consumes data very slowly, the producer must wait for the consumer to consume before generating data.
If consumers are larger than producers, consumers will often be in a waiting state.
Therefore, a buffer is introduced for producers to store data and consumers to extract data. It plays a role of data cache to balance the speed between producers and consumers. At the same time, it also achieves the function of understanding coupling (the buffer, as an intermediate level, separates the direct dependence of producers and consumers)

  • In multithreading development, if the producer produces data very fast and the consumer consumes data very slowly, the producer must wait for the consumer to consume before generating data.
  • If consumers are larger than producers, consumers will often be in a waiting state.
  • Therefore, a buffer is introduced for producers to store data and consumers to extract data. It plays a role of data cache to balance the speed between producers and consumers. At the same time, it also achieves the function of understanding coupling (the buffer, as an intermediate level, separates the direct dependence of producers and consumers)

Prototype mode

(prototype) use prototype instances to specify the kind of objects to create, and create new objects by copying these prototypes.

  • Prototype pattern is to create another customizable object from one object without knowing any creation details.
  • Clone is used to realize, which is divided into shallow copy and deep copy

< strong > example: < / strong >

package com.design.prototype;

public class Jingubang {
    public float h = 100;
    public float d = 10;
    
    public void big(){
        h *= 2;
        d *= 2;
    }
    
    public void small(){
        h /= 2;
        d /= 2;
    }
}

package com.design.prototype;

import java.util.Date;

public class Monkey {
    public int height;
    public int wight;
    public Date brithday;
}
package com.design.prototype;

import java.io.*;
import java.util.Date;

public class QiTianDaSheng extends Monkey implements Cloneable, Serializable {
    public Jingubang jingubang;

    public QiTianDaSheng(){
        this.jingubang = new Jingubang();
        this.brithday = new Date();
    }

    @Override
    public Object clone(){
        return this.deepClone();
    }

    public Object deepClone(){ //深克隆
        try {
            QiTianDaSheng qiTianDaSheng;
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)){
                objectOutputStream.writeObject(this);
            }

            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            try (ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)){
                qiTianDaSheng = (QiTianDaSheng) objectInputStream.readObject();
                qiTianDaSheng.brithday = new Date();
            }

            return qiTianDaSheng;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
}

Delegation mode

< strong > the basic function of the delegation mode is to be responsible for the call and allocation of tasks < / strong >, which is very similar to the agent mode. It can be regarded as a static full-power agent under special circumstances, but the agent mode focuses on the process and the delegation mode focuses on the result.

Dispatcher servlet uses delegation mode

Dispatcher servlet uses delegation mode

example:

public interface IEmployee {
    void doing(String command);
}

public class EmployeeA implements IEmployee{
    @Override
    public void doing(String command) {
        System.out.println("我是A,现在开始干" + command +"活");
    }
}

public class EmployeeB implements IEmployee{

    @Override
    public void doing(String command) {
        System.out.println("我是B,现在开始干" + command +"活");
    }
}

public class EmployeeC implements IEmployee{
    @Override
    public void doing(String command) {
        System.out.println("我是C,现在开始干" + command +"活");
    }
}
public class Leader implements IEmployee{
    private Map<String, IEmployee> targets = new HashMap<>();

    public Leader(){
        targets.put("加密", new EmployeeA()); //A负责加密
        targets.put("登录", new EmployeeB()); //B负责登录
    }

    @Override
    public void doing(String command) {
        targets.get(command).doing(command); //接收command,并派发给负责人执行
    }
}

public class Boss {
    public void command(String command, Leader leader) {
        leader.doing(command);
    }
}
public class DelegateApp {
    public static void main(String[] args) {
        new Boss().command("登录", new Leader()); //发送登录指令,交由Leader进行处理,Leader根据传入的指令进行调度,将指令派发给目标类执行
    }
}

Output:

我是B,现在开始干登录活

Strategy mode

Policy mode refers to defining algorithm families and encapsulating them separately so that they can replace each other. This mode makes the change of algorithm not affect the users who use the algorithm.

Application scenario:

  • There are many classes in the system, and their differences only exist in different behaviors.
  • A system needs to dynamically choose one of several algorithms.
//工厂模式月策略模式
public class PayStrategy {
    public static final String ALI_PAY = "AliPay";

    public static final String DEFAULT_PAY = "AliPay";

    private static Map<String, Payment> payStraregy = new HashMap<String, Payment>();
    static {
        payStraregy.put(ALI_PAY, new AliPay());
    }

    public static Payment get(String payKey) {
        if (!payStraregy.containsKey(payKey)) {
            return payStraregy.get(DEFAULT_PAY);
        }
        return payStraregy.get(ALI_PAY);
    }
}

< strong > advantages of strategy mode: < / strong >

  • Comply with the opening and closing principle.
  • Policy mode can avoid multiple conditional statements, such as if Else and switch statements.
  • Using policy mode can improve the confidentiality and security of the algorithm.

< strong > disadvantages: < / strong >

  • The client must know all policies and decide which policy class to use.
  • There are many policy classes in the code, which increases the difficulty of code maintenance.

< strong > policy mode and delegation mode Demo: < / strong >

package com.axianibiru.springstudy.dispatcher;

import com.axianibiru.springstudy.controller.MemberController;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class DispatcherServlet extends HttpServlet {

    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init() throws ServletException {
        try {
            Class<?> memberController = MemberController.class;
            handlerMapping.add(new Handler()
                    .setController(memberController.newInstance())
                    .setMethod(memberController.getMethod("getMemberById", new Class[]{String.class}))
                    .setUrl("/web/getMemberById.json"));
        }catch (Exception e){}
    }

    public void doDispatcher(HttpServletRequest request, HttpServletResponse response) {
        //获取用户请求的URL
        //按照J2EE的标准一个URL对应一个Servlet
        String uri = request.getRequestURI();

        //通过URL获取handlerMapping(URL是策略常量)
        Handler handle = null;
        for (Handler h: handlerMapping) {
            if (uri.equals(h.getUrl())) {
                handle = h;
                break;
            }
        }

        //将具体的任务分发给Method(通过反射调用对应的方法)
        Object object = null;
        try {
            object = handle.getMethod().invoke(handle.getController(), request.getParameter("mid"));
        } catch (InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatcher(req, resp);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    class Handler {
        private Object controller;
        private Method method;
        private String url;

        public Object getController(){
            return controller;
        }

        public Handler setController(Object controller){
            this.controller = controller;
            return this;
        }

        public Method getMethod() {
            return method;
        }

        public Handler setMethod(Method method) {
            this.method = method;
            return this;
        }

        public String getUrl() {
            return url;
        }

        public Handler setUrl(String url) {
            this.url = url;
            return this;
        }
    }
}

Template method mode

  • When using inheritance and affirming that the inheritance is meaningful, it should be called the template of subclasses. All repeated code should rise to the parent class, rather than repeating every subclass.
  • When we want a process or a series of steps to be consistent at a certain level of detail, but the implementation of individual steps at a more detailed level may be different, we usually consider using the template method pattern.
  • By rewriting the subclass in detail and creating all templates for the parent class, the reuse of duplicate content is completed.
  • Therefore, the definition is: top the algorithm skeleton in one operation and delay some steps to subclasses. The template method allows subclasses to redefine some specific steps of an algorithm without changing the structure of the algorithm (the logical framework is given in the parent class, and the logical composition steps are delayed to the subclass implementation in the abstract operation of the response).
    The template method shows its advantages by moving the invariant behavior to the superclass and removing the repeated code classes in the subclass.
    When mutable and immutable behaviors are mixed together in subclasses of methods, immutable behaviors are repeated in subclasses. We move these behaviors to a single place in the same stock yo template method pattern, which helps subclasses avoid the entanglement of repeated invariant behaviors.
    Implement the immutable part of an algorithm at one time, and leave the variable part to subclasses for implementation (such as some methods in IO).
    The common behaviors in each subclass are extracted and concentrated in a common parent class, so as to avoid code duplication.
  • The template method shows its advantages by moving the invariant behavior to the superclass and removing the repeated code classes in the subclass.
  • When mutable and immutable behaviors are mixed together in subclasses of methods, immutable behaviors are repeated in subclasses. We move these behaviors to a single place in the same stock yo template method pattern, which helps subclasses avoid the entanglement of repeated invariant behaviors.
  • Implement the immutable part of an algorithm at one time, and leave the variable part to subclasses for implementation (such as some methods in IO).
  • The common behaviors in each subclass are extracted and concentrated in a common parent class, so as to avoid code duplication.

< strong > reconstruct JDBC operation business scenario using template mode: < / strong >

< strong > in this way, the Dao layer only needs to inherit the jdbctemplate and implement rowmapper < / strong >

< strong > in this way, the Dao layer only needs to inherit the jdbctemplate and implement rowmapper < / strong >

package com.design.template;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class JdbcTemplate {
    private DataSource dataSource;

    public JdbcTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public List<?> executeQuery(String sql, RowMapper<?> rowMapper, Object[] values) {
        try {
            //获取连接
            Connection conn = this.getConnection();
            //创建语句集
            PreparedStatement pstm = this.createPreparedStatement(conn, sql);
            //执行语句集
            ResultSet rs = this.executeQuery(pstm, values);
            //处理结果集
            List<?> result = this.paresResultSet(rs, rowMapper);
            //关闭结果集
            this.closeResultSet(rs);
            //关闭语句集
            this.closeStatement(pstm);
            //关闭连接
            this.closeConnection(conn);
            return result;
        }catch (Exception e){
            e.printStackTrace();
        }

        return null;
    }



    protected void closeResultSet(ResultSet rs) throws SQLException {
        if (rs != null){
            rs.close();
        }
    }

    protected void closeConnection(Connection conn) throws SQLException {
        if (conn != null){
            conn.close();
        }
    }

    protected void closeStatement(PreparedStatement pstm) throws SQLException {
        if (pstm != null){
            pstm.close();
        }
    }

    protected ResultSet executeQuery(PreparedStatement pstm, Object[] values) throws SQLException {
        for (int i = 0; i < values.length; i++) {
            pstm.setObject(i, values[i]);
        }

        return pstm.executeQuery();
    }

    protected List<?> paresResultSet(ResultSet rs, RowMapper<?> rowMapper) throws Exception {
        List<Object> result = new ArrayList<Object>();
        int rowNum = 1;
        while (rs.next()){
            result.add(rowMapper.mapRow(rs, rowNum++));
        }

        return result;
    }

    protected PreparedStatement createPreparedStatement(Connection conn, String sql) throws SQLException {
        return conn.prepareStatement(sql);
    }

    protected Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

}

RowMapper接口:

package com.design.template;

import java.sql.ResultSet;

public interface RowMapper<T> {
    T mapRow(ResultSet rs, int rowNum) throws Exception;
}

< strong > advantages: < / strong >

  • Using template pattern to put the code with the same processing logic into the abstract parent class can improve the reusability of the code.
  • Putting different codes into different subclasses and adding new behaviors through the extension of subclasses can improve the scalability of codes.
  • The invariable behavior is written in the parent class, which removes the repeated code of the child class, and provides a good code reuse platform, which conforms to the opening and closing principle.

< strong > disadvantages: < / strong >

  • Each abstract class needs a subclass to implement, resulting in an increase in the number of classes.
  • The increase of the number of classes indirectly increases the complexity of the system.
  • Because of the shortcomings of the inheritance relationship itself, if a new abstract method is added to the parent class, all subclasses need to be modified.

Adapter mode

Convert the interface of a class into another interface desired by the client. The adapter mode enables classes that cannot work together due to incompatible interfaces to work together.

When the data and behavior of the system are correct, but the interface is inconsistent, we should consider using an adapter to match an original object outside the control range with an interface. The adapter mode is mainly used when you want to reuse some classes of video memory, but the interface is inconsistent with the requirements of the reuse environment.

  • When the data and behavior of the system are correct, but the interface is inconsistent, we should consider using an adapter to match an original object outside the control range with an interface. The adapter mode is mainly used when you want to reuse some classes of video memory, but the interface is inconsistent with the requirements of the reuse environment.
  • If you want to use a class, but her interface, that is, her methods and requirements are different, you should consider using the adapter pattern (the existing class methods do not match the requirements, but the method results are the same or similar).
  • Use it when two classes do the same or similar things, but have different interfaces.
  • Use the adapter mode when it is not easy for both parties to modify (the adapter mode is not the design mode considered in the initial stage of the software, but a solution to the problem of similar functions and different interfaces caused by different products and manufacturers with the development of the software).
  • Only prevent the problems of different interfaces and the occurrence of mismatches: when there are only small problems of inconsistent interfaces, reconstruct them in time to prevent the problems from expanding; Only when the original design and code cannot be changed, the adaptation can be considered (post control is not as good as in-process control, and in-process control is not as good as pre control).
  • The policy mode is mainly used to encapsulate a series of families (depending on the interface). The adapter mode pays attention to the compatibility between different interfaces / classes (the implementation of the adapter does not depend on the interface).

Adapter mode in spring dispatcher servlet:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) { //使用简单工厂模式进行适配器的选择
				if (adapter.supports(handler)) { //查看适配器是否匹配
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

< strong > advantages: < / strong >

  • It can improve the transparency and reusability of classes. Existing classes will be reused when there is no need to change.
    The target class and adapter class are decoupled (passed in by object, and the real processing is in the target class (the method of the target class can be reused if it is forcibly converted to the target class). It is introduced into the high-level abstraction, does not depend on the specific implementation, follows the principle of dependency inversion, and completes the expansion / reuse of the original class method).
  • The target class and adapter class are decoupled (passed in by object, and the real processing is in the target class (the method of the target class can be reused if it is forcibly converted to the target class). It is introduced into the high-level abstraction, does not depend on the specific implementation, follows the principle of dependency inversion, and completes the expansion / reuse of the original class method).
  • The target class and adapter are decoupled to improve the scalability of the program.
  • In many business scenarios, it conforms to the opening and closing principle.

< strong > disadvantages: < / strong >

  • It needs to be fully considered in the process of adapter coding, which may increase the complexity of the system.
  • It increases the difficulty of reading the code and reduces the readability of the code. Excessive use of adapters will make the code of the system chaotic.

< strong > comparison between decorator mode and adapter mode: < / strong >

  • Decorator mode and adapter mode belong to wrapper pattern
  • Decorator mode is mainly used to solve the problem of the same parent class, and configurator is mainly used to solve the problem of compatibility.
装饰者模式 适配器模式
形式 属于一种特别的配适器模式 没有承继关系,装饰者模式有层级关系
定义 装饰者和被装饰者实现同一个接口,主要目的是扩展之后依然保留OOP关系。 适配器进和被适配者没有必然的联系,通常采用继承或者代理的形式进行包装。
关系 满足is-a的关系(a是特殊的b,所以a继承b) 满足has-a(a包括b,但不是b)
功能 注重覆盖、扩展 注重兼容、转换
设计 前置考虑 后置考虑

Observer mode

The observer mode is also called publish / subscribe mode

The observer mode is also called publish / subscribe mode

< strong > observer mode defines a one to many dependency, allowing multiple observer objects to listen to a topic object at the same time. When the state of this subject object changes again, it will notify all observer objects so that they can update themselves automatically

  • The observer pattern abstracts the interdependent objects into two aspects, one is dependent on the other. At this time, using the observer pattern, the two can be encapsulated in independent objects and changed and reused independently.
  • What observer mode does is decouple. Let both sides of the coupling rely on abstraction rather than concrete. So that their changes will not affect the changes on the other side.
  • Observer and pattern allow multiple objects to listen to an object topic at the same time. When the subject object changes, all her dependents (observers) will receive notification and update, which belongs to behavioral pattern.
  • Observer mode is sometimes called publish subscribe mode. Observer mode is mainly used to establish a set of trigger mechanisms between related behaviors.

Listening mechanism in spring:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

   public ContextLoaderListener() {
   }

   public ContextLoaderListener(WebApplicationContext context) {
      super(context);
   }

   @Override
   public void contextInitialized(ServletContextEvent event) {
      initWebApplicationContext(event.getServletContext());
   }

   @Override
   public void contextDestroyed(ServletContextEvent event) {
      closeWebApplicationContext(event.getServletContext());
      ContextCleanupListener.cleanupAttributes(event.getServletContext());
   }

}

< strong > advantages: < / strong >

  • An abstract coupling is established between the observer and the observed
  • Observer mode supports broadcast communication

< strong > disadvantages: < / strong >

  • There is too much detail dependence between observers, too much time consumption and higher complexity of the program
  • Improper use will lead to circular calls.