一、六大设计原则

1、单一职责原则(Single Responsibility Principle)

  • 含义:一个类\接口\方法只负责完成一个职责

2、里氏替换原则(Liskov Substitution Principle)

  • 多用组合,少用继承(组合是指在一个类中引用另一个类的对象)
  • 含义:
  • 若继承是为了实现代码重用,那么子类就不能重写父类的任何方法;
  • 若继承是为了实现多态,那么将父类应当被定义为抽象类,被重写的方法应该被定义为抽象方法

3、依赖倒置原则(Dependence Inversion Principle)

  • 修改上层模块直接依赖下层模块具体类的结构,改变为下层模块具体类依赖于上层接口
  • 类图示例

4、接口隔离原则(Interface Segregation Principle)

  • 定义:建立单一接口,不要建立臃肿庞大的接口。接口尽量细化,同时接口中的方法尽量少。
  • 含义:
  • 接口尽量小:不要违反单一职责原则,要根据业务场景适度小;
  • 接口中的逻辑需要高内聚:提高接口、类、模块的处理能力,减少对外的交互;
  • 定制服务:通过高质量的接口组装,实现服务的定制化。

5、迪米特法则(Law of Demeter)/最少知识原则

  • 含义:一个类应该对自己需要耦合或调用的类知道的最少,只需要知道其中public的方法即可;
  • 拓展知识:DDD

6、开闭原则(Open Closed Principle)

  • 定义:对扩展开发,对修改关闭
  • 含义:添加一个新功能应该是在原有代码的基础之上进行扩展,而不是修改已有的代码。

二、23种设计模式

1、创建型设计模式

  • 创建型设计模式关注点在于将对象的创建于业务代码解耦,使得业务代码专注于业务的实现。Spring IOC作为对象容器,其中就大量使用了创建型设计模式
  • 创建型设计模式有五种:单例模式、原型模式、工厂方法、抽象工厂、建造者

单例模式(Singleton)

某个类只能生成一个对象,在该类中提供一个对外访问对象的方法,其实现方式有五种:

  • 饿汉式,通常使用该方式即可
/**
  * @description 饿汉式(线程安全,调用效率高,但是不能延时加载)
  **/
public class SingletonDemo1 {
   private static SingletonDemo1 instance = new SingletonDemo1();
   private SingletonDemo1(){}
   public static SingletonDemo1 getInstance(){
       return instance;
   }
}
  • 懒汉式
/**
  * @description 懒汉式(线程安全,调用效率不高,但是能延时加载),注意实例获取方法需要加锁
  **/
public class SingletonDemo2 {
   //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
   private static SingletonDemo2 instance;

   //构造器私有化
   private SingletonDemo2(){}

   //方法同步,调用效率低
   public static synchronized SingletonDemo2 getInstance(){
       if(instance==null){
           instance=new SingletonDemo2();
       }
       return instance;
   }
}
  • 双重校验
/**
  * @description 双重校验
  **/
public class SingletonDemo3 {
   private volatile static SingletonDemo3 singletonDemo3;

   private SingletonDemo3() {
   }

   public static SingletonDemo3 newInstance() {
       // 第一次判断是为了避免在已经创建了实例后的线程进入时依然会被锁堵住
       if (singletonDemo3 == null) {
           synchronized (SingletonDemo3.class) {
               //第二次判断是为了防止有多个线程已经走过第一个判断,但是被中断了
               if (singletonDemo3 == null) {
                   singletonDemo3 = new SingletonDemo3();
               }
           }
       }
       return singletonDemo3;
   }
}
  • 静态内部类
/**
  * @description 静态内部类(线程安全,调用效率高,可以延时加载)
  **/
public class SingletonDemo4 {
   /**
    * 静态内部类
    **/
   private static class SingletonClassInstance{
       private static final SingletonDemo4 instance = new SingletonDemo4();
   }

   private SingletonDemo4(){}

   public static SingletonDemo4 getInstance(){
       return SingletonClassInstance.instance;
   }
}
  • 枚举
/**
  * @description 枚举自动支持线程安全和单例
  **/
public enum SingletonDemo5 {

    //枚举元素本身就是单例
    INSTANCE;

    //添加自己需要的操作,直接通过SingletonDemo5.INSTANCE.doSomething()的方式调用即可。方便、简洁又安全。
    public void doSomething() {
        System.out.println("doSomething");
    }
}

class Test {
    public static void main(String[] args) {
        SingletonDemo5.INSTANCE.doSomething();
    }
}

原型模式(Prototype)

以一个对象为原型,通过复制得到多个和原型相似的其他对象;

  • 浅拷贝与深拷贝
  • 浅拷贝:当对象中存在其他对象时,拷贝得到的新对象使用的内部对象是指向原对象的;
  • 深拷贝:当对象中存在其他对象时,拷贝得到的新对象将会新建其他对象;
import java.util.ArrayList;
import java.util.List;

import lombok.Data;

@Data
public class Prototype1 implements Cloneable {

    private String name;

    private List<String> arrayList = new ArrayList<>();

    public static void main(String[] args) {
        Prototype1 prototype1 = new Prototype1();
        prototype1.setName("orign object");
        prototype1.setValue("orign object");

        Prototype1 clonePrototype1 = prototype1.clone();
        clonePrototype1.setName("clone object");
        /** 发现添加了执行了clone对象的setValue之后,也修改了prototype1中的arrayList中数据 */
        clonePrototype1.setValue("clone object");
        System.out.println(prototype1);
        System.out.println(clonePrototype1);
    }

//    /**
//     * 浅拷贝
//     * @return
//     */
//    @Override
//    protected Prototype1 clone() {
//        try {
//            return (Prototype1)super.clone();
//        } catch (CloneNotSupportedException e) {
//            e.printStackTrace();
//        }
//        return null;
//    }

    /**
     * 深拷贝
     * @return
     */
    @Override
    protected Prototype1 clone() {
        Prototype1 prototype1 = null;
        try {
            prototype1 = (Prototype1)super.clone();
            prototype1.setArrayList(new ArrayList<>());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return prototype1;
    }


    public void setValue(String value) {
        this.arrayList.add(value);
    }

    public List<String> getValue() {
        return this.arrayList;
    }
}
  • 类图

简单工厂(SimpleFactory)

  • 定义:简单工厂不是一个设计模式,而是日常的编程习惯
  • 类图

工厂方法(FactoryMethod)

  • 定义:定义一个创建对象的接口,由子类确定需要创建什么产品;
  • 类图

抽象工厂(AbstractFactory)

  • 定义:提供一个创建产品族的接口,其每个子类会生成一系列相似的产品;
  • 类图

建造者(Builder)

  • 定义:将一个复杂对象的生成过程拆解多个简单的部分,然后依次创建它们,最后组装成为整体。可定制建造者核心在于最后一个流程之前的方法的返回值类型为该对象流程中的接口类型;
  • 类图

2、结构型设计模式

  • 结构型设计模式描述如何将类或对象按照某种布局组成更大的结构 。分为类结构型模式(采用继承机制来组织接口和类)和对象结构型模式(采用组合或聚合来组合对象)
  • 结构型设计模式主要有七种:代理模式、适配器、桥接、装饰者、门面模式、享元模式、组合模式

代理模式(Proxy)

  • 定义:代理模式为另一个对象提供一个替身占位符,以控制对这个对象的访问。使用代理模式创建代理对象,让代理对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。
  • 解决问题:当我们想要对一个业务进行某些横切性的增强时,例如:增加请求与响应的日志,增加权限校验、增加远程请求对象封装等等。我们可以采用代理模式去实现,而不需要修改原有的类。
  • SpringAOP中采用的动态代理,就是最典型的例子
  • 类图
  • 动态代理(JDK动态代理),需要使用InvocationHandler
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 接口
 */
interface DynamicRealInterface {

    void operation();

}


/**
 * 被代理类
 */
public class DynamicRealSubject implements DynamicRealInterface {

    @Override
    public void operation(){
        System.out.println("目标方法执行");
    }

    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        DynamicRealSubject realObject = new DynamicRealSubject();
        DynamicRealInterface proxy = (DynamicRealInterface) getProxy(realObject);
        proxy.operation();
    }


    /**
     * 获取代理对象
     * @param target
     * @return
     */
    private static Object getProxy(final Object target) {
        Object myProxy = Proxy.newProxyInstance(
                target.getClass().getClassLoader(),    // 目标类对象的类加载器
                target.getClass().getInterfaces(),     // 目标类的接口
                (proxy, method, args) -> {             // 创建一个InvocationHandler
                    beforeMethod(proxy, method, args);
                    Object object = method.invoke(target);  //注意:此处需要传入getProxy的target对象,不能使用proxy,proxy是当前对象,引用会出现死循环问题
                    afterMethod(proxy, method, args);
                    return object;
                });
        return myProxy;
    }

    /**
     * 代理前执行方法
     * @param proxy
     * @param method
     * @param args
     */
    private static void beforeMethod(Object proxy, Method method, Object[] args) {
        System.out.println("目标方法执行前");
    }

    /**
     * 代理前执后方法
     * @param proxy
     * @param method
     * @param args
     */
    private static void afterMethod(Object proxy, Method method, Object[] args) {
        System.out.println("目标方法执行后");
    }

}

适配器模式(Adaptor)

  • 定义:将一个类的接口,转换成为客户期望的另一个接口,是配置让原本接口不兼容的类可以合作无间
  • 类图

桥接模式(Bridge)

  • 定义:将抽象部分和实现部分,分离解耦,使得二者独立变化。桥接模式通过将实现和抽象放在两个不同的层次中,而是它们可以单独变化。
  • 优点:
  • 将实现和抽象解耦,抽象和实现都可以独立扩展,不会影响到对方;
  • 对于“具体抽象类中”所做的改变,不会影响到客户;
  • 缺点:
  • 增加了系统复杂度
  • 用途
  • 适合用于跨越多个平台的图形和窗口系统上;
  • 当需要用不同方式改变接口和实现时,桥接方式很好用
  • 类图

装饰者模式(Decorator)

  • 定义:动态的将责任附加到对象上。若要拓展功能,装饰者提供了比继承更具弹性的替代方案。
  • 解决问题:咖啡店之前有四种咖啡,分别是:HouseBlend、DarkRoast、Espresso和Decaf,每个咖啡都有自己的cost()售价方法。但是,购买咖啡时,很多客户要求向其中加入各种调料,例如:Milk、Mocha、Soy等。那么总价就会存在多种组合方式,如果针对每种组合都实现具体类来提供总价cost(),那肯定就会造成类爆炸
  • 类图

门面模式(Facade)

  • 定义:提供一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更易用。
  • 解决问题:例如:入职办理。如果需要自己办理公积金转移、自己采购电脑并安装软件、申请工位、自己添加工资卡等等,这显然是不存在的。通常都是HR小姐姐帮我们把各种资料信息都已经准备好了,我们只需要签协议就可以了。在这里HR就扮演的是门面
  • 类图:

享元模式(Flyweight)

  • 定义:使用共享对象可以有效地支持大量的细粒度对象。享元模式是池技术的重要实现方式。
  • 问题:若存在一个业务场景,2000万人需要填报自己的个人信息用户核酸检测数据采集。刺激信息包括:姓名、居住地址、核酸检测点(医院)、核酸检测品牌等等。针对这些信息,如果每一个有一份这样的信息,将会存在大量的空间浪费,因为医院、核酸检测品牌等是固定信息。我们可以将这些固定通用实例池化,然后直接到池中获取即可。
  • 类图:

组合模式(Composite)

  • 定义:将对象组合成树形结构来表现“整体/部分”层次结构。组合能让可和以一直的方式处理个别对象以及对象组合
  • 类图

3、行为型设计模式

  • 行为型设计模式 用于描述程序在运行时复杂的流程控制,即:描述多个类或对象之间怎样相互协作共完成单个对象都无法单独完成的任务,它设计算法与对象间职责的分配。
  • 行为型设计模式分为类行为模式(采用继承机制来在类间分派行为)和对象行为模式(采用组合或聚合在对象间分配行为)。由于组合关系或聚合关系比继承关系耦合程度低,满足合成复用原则,所以对象行为模式比类行为模式具有更大的灵活性。
  • 行为型设计模式是GOF设计模式中最为庞大的一类,它包含11种模式:模板方法、策略模式、命令模式、责任链模式、状态模式、观察者模式、中介者模式、迭代器模式、访问者模式、备忘录模式、解释器模式

模板方法(TemplateMethod)

-定义 在一个方法中顶一个算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤

  • 类图

策略模式(Strategy)

  • 定义:定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
  • 类图

命令模式(Command)

  • 定义:将请求封装成为命令对象,以便使用不同的请求、队列或日志来参数化其他对象
  • 类图

责任链模式(Chain Of Responsibility)

  • 定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到没有对象处理它为止。
  • 场景:公司收到了很多电子邮件,其中大致分为四类:1>公司粉丝发来的邮件——由CEO查阅处理;2>诽谤公司产品的邮件——有法律部门处理;3>要求业务合作的邮件——由业务部门处理;4>其他垃圾邮件——直接丢弃或删除。如何将这些邮件按类去归纳处理呢?可以为邮件添加邮件类型(emailType),通过if…else进行判断和归类处理。但是如果后续又增加\删除了其他邮件类型,那么就需要侵入代码修改。所以在此种场景之下可以考虑采用责任链模式
  • 责任链处理流程图
  • 类图

状态模式(State)

  • 定义:允许对象在内部状态改变时改变它的行为,独享看起来好像修改了它的类。
  • 场景:糖果售卖机
  • 场景描述:若以一个动作作为一个方法去处理,比如:投入铅笔的动作,那么我们就需要按照如下方式去实现方法中的的逻辑,那么其他动作,如:转动曲柄、退回钱币、弹出糖果等,也都需要按照以上的写法实现逻辑。显然,通过if-else是很不优雅的,而且若糖果机需要增加功能,如增加会员功能,则需要侵入代码。针对以上问题,我们可以使用状态模式来解决。
  • 类图

观察者模式(Observe)

  • 定义:定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
  • 问题场景
  • 类图

中介者模式(Mediator)

  • 定义:使用中介者模式来集中相关对象之间复杂的沟通和控制方式。
  • 场景:在一个线上售卖商城系统中,存在三个功能:采购功能、销售功能和库存功能。
  • 采购功能:需要从销售功能获取到销售情况,并且确定是否需要继续采购增加库存量;
  • 销售功能:需要获取库存信息,当销售量大于库存量时,需要通知采购功能快速采购商品。每当销售了产品,需要扣减相应库存;
  • 库存功能:如果库存压力大了,要提示销售功能区折价促销, 尽快消耗库存,并且通知采购功能暂时不要采购该商品。
  • 通过上面的描述,我们可以了解到三个功能模块是相互依赖的,如下图所示:
  • 类图

迭代器模式(Iterator)

  • 定义:提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。通常情况下,在JDK中可能会使用到迭代器的容器,JDK底层都给予了实现。
  • 类图

访问者模式(Visitor)

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

备忘录模式(Memento)

  • 定义:在不破坏分装性的前提下,铺货一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。当你需要让该对象返回之前的状态时,(例如:你的用户请求“撤销”),就需要使用备忘录模式。
  • 类图

三、总结

由于设计模式的引入,必然会给我们的系统带来相应程度的复杂度。所以在日常开发中依然需要回归业务,不能因为“设计模式所以设计模式”。毕竟符合业务场景的才是最好的