设计模式总结

前言:

设计模式是用来解决复杂问题的,借助前人经验,把复杂问题变得简单,而不要生搬硬套,为了使用设计模式而使用设计模式

经典的设计模式一共有23种,可分为创建型、结构型和行为型三大类。一些设计模式之间看起来很相似,但是目的有所不同,可以借此来区分。下文主要对一些常见的设计模式进行总结。
具体代码请参考:
https://github.com/925781609/pattern

创建型:

  1. 简单工厂模式

    特点: 一个工厂里所有子类都可以创建,根据传入参数选择创建哪个

    UML图:


    简单工厂模式

​ 代码示例:

//根据type,选择创建HpMouse还是DellMouse, HpMouse和DellMouse时Mouse的子类
public class MouseFactory{
    public static Mouse createMouse(String type) throws IllegalArgumentException{
        switch(type){
            case "Hp":
                return new HpMouse();
            case "Dell":
                return new DellMouse();
        }
         throws new IllegalArgumentException("the type not found");
    }
} 
2. 工厂方法模式

​ 特点:每个子类,由对应的工厂创建创建

​ UML图:


工厂方法模式

代码示例:

//  HpMouse由HpMouseFactory创建, DellMouse由DellMouseFactory创建
public class HpMouseFactory implements MouseFactoy{

    @Override
    public Mouse createMouse(){
        return new HpMouse();
    }
}

public class DellMouseFactory implements MouseFactory{

    @Override
    public Mouse createMouse(){
        return new DellMouse();
    }
}
  1. 抽象工厂模式

    当工厂模式创建多个对象时, 工厂模式即变为抽象工厂模式, 原来只创建mouse, 现在创建mouse和keyboard

    在增加子类对象数量和种类时,只需要增加即可,不需要修改原有的逻辑

    UML图:


    抽象工厂模式
    4.建造者模式

    ​ 建造者模式按照顺序创建复杂对象(把内部的创建过程和细节隐藏起来)

    ​ UML图:

建造者模式

​ 代码示例: 详细的参考https://github.com/925781609/pattern/tree/master/src/main/java/pattern/builder

```source-java
// 具体产品,建造后对象,即为Product
public class Computer{

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

    public void Add(String part){
        parts.add(part);
    }

    public void show() {
        for (String e : parts) {
            System.out.println(e);
        }
    }

}

// 声明具体建造者的公共接口
public interface Builder{

    public void BuildCPU();

    public void BuildMainBoard();

    public void BuildHD();

    public Computer getComputer();

}

// 具体的建造者
public class ConcreteBuilder implements Builder{

    Computer computer=new Computer();

    @Override
    public void BuildCPU(){
        computer.Add("组装CPU");
    }

    @Override
    public void BuildMainBoard(){
        computer.Add("组装主板");
    }

    @Override
    public void BuildHD(){
        computer.Add("组装硬盘");
    }

    @Override
    public Computer getComputer(){
        return computer;
    }
}

// 负责控制产品对象的生产工程
public class Director{

    public void construct(Builder builder){
        builder.BuildMainBoard();
        builder.BuildCPU();
        builder.BuildHD();
    }
}

// 建造者使用示例
public class Client {

  public static void main(String[] args) {
    Director director = new Director();
    Builder builder = new ConcreteBuilder();

    // 开始建造对象
    director.construct(builder);

    // 获得建造的对象
    Computer computer = builder.getComputer();

    computer.show();
  }
}
```
5.单例模式

​ 保证一个类仅有一个实例, 并提供一个访问它的全局访问点

​ 应用场景: Listener本身单例, 日历Calander, IoC容器, 配置信息Config

​ 技术方案:保证运行过程中只有一份

​ 注意事项: 单例模式注意构造函数 定义为private,防止外部调用 ,此外极端情况下还需要考虑克隆、反序列化、反射对单例的破坏,可以参考:https://zhuanlan.zhihu.com/p/28491630

​ 技术实现方案包括: (此外还有枚举法)

  1. 饿汉式:

    在类加载的时候立即初始化, 并且创建单例对象

    优点: 没有任何的锁, 执行效率高, 绝对线程安全(在线程还没有出现之前就已经实例化了)

    缺点: 类架子的时候就初始化, 不管是否使用都占用空间

    代码示例:

    public class Singleton {
    
      private static Singleton singleton = new Singleton();
    
      // 私有构造方法,防止被实例化
      private Singleton() {
      }
    
      public static Singleton getInstance() {
        return singleton;
      }
    }
    
    
  2. 懒汉式: 默认不实例化, 只有使用的时候才实例化, 又可以分为静态内部类、双重锁检查、登记式

    静态内部类: 内部类只有在外部类被调用时,才会被加载,内部类在方法 调用前被初始化

 public class Singleton {

   // 私有构造方法,防止被实例化
   private Singleton() {
   }

   private static class SingletonHolder {
     private static final Singleton INSTANCE = new Singleton();
   }

   private static final Singleton getInstance() {
     return SingletonHolder.INSTANCE;
   }
 }

双重锁检查:

public class Singleton {

  // 对singleton对象使用volatile关键字进行限制,保证其对所有线程的可见性,并且禁止对其进行指令重排序优化。
  private static volatile Singleton singleton = null;

  private Singleton() {
  }

  public static Singleton getSingleton() {
    if (singleton == null) {
      synchronized (Singleton.class) {
        if (singleton == null) {
          singleton = new Singleton();
        }
      }
    }
    return singleton;
  }
}

注册登记式(spring种常用,待补充)

  1. 原型模式

结构型模式:

1.代理模式

技术手段: jdk proxy,cglib(字节码重组框架),AspectJ(spring), asm

代理角色通常会持有被代理角色对象的引用,以便代理角色完成工作之前或之后找到被代理对象,能够通知被代理对象

代理分静态代理和动态代理:

  1. 静态代理:

    有目标类的引用,但是类型固定(为目标类), 否则无法使用目标类的方法

    需要定义接口或者父类,代理对象与被代理对象一起实现相同的接口或是继承相同的父类

  2. 动态代理:(通过字节码重组)

    JDK代理: 目标类要实现某一接口

    ​ 生成一个新的类,新的类实现了被代理类的所有实现。在新生成类的方法中调用目标类原来的方法

    CGLIB代理: 不需要实现相同的接口

    ​ 通过字节码重组生成新的类,继承目标类

UML:

代理模式

代码实现: 关于动态代理的实现详见https://github.com/925781609/pattern/tree/master/src/main/java/pattern/proxy

interface Subject {

  void request();
}

class RealSubject implements Subject {

  public void request() {
    System.out.println("request");
  }
}

class Proxy implements Subject {

  private Subject subject;

  public Proxy(Subject subject) {
    this.subject = subject;
  }

  @Override
  public void request() {
    System.out.println("PreProcess");
    subject.request();
    System.out.println("PostProcess");
  }
}

// client
public class Client {

  public static void main(String[] args) {
    RealSubject subject = new RealSubject();
    Proxy p = new Proxy(subject);
    p.request();
  }
}
2.适配器模式

定义一个包装类, 把适配的类(Adaptee)的api转换成目标类(Target)的api

强调的是兼容, 不改变原来的代码也能实现新的需求, 类似于VGA-HDMI

不想去修改老的比较稳定的系统,但是为了兼容新的需求和标准, 不得不在系统上再去做一些文章,向下兼容,

原来的类如果不再使用, 需要打上Deprecated注解,

  1. UML图:

    1. 类适配器:继承原来的类,并且不覆写原来的方法,增加新的方法
类适配器

​ 2) 对象适配器:将原来的类注入进来,调用原来的方法

对象适配器
  1. 代码示例:

    public interface Target {
    
      //这是源类Adapteee没有的方法 
      public void Request();
    }
    
    // 源类
    public class Adaptee {
    
      public void SpecificRequest() {
        System.out.println("SpecificRequest in Adaptee");
      }
    }
    
    // 适配器类
    //适配器Adapter继承自Adaptee,同时又实现了目标(Target)接口。 
    public class Adapter extends Adaptee implements Target {
    
      //所以适配器只是将SpecificRequest()方法作了一层封装,封装成Target可以调用的Request()而已 
      @Override
      public void Request() {
        this.SpecificRequest();
      }
    }
    
3.装饰器模式

​委派+适配器 为了扩展和增强,要满足is-a关系(同源同宗,也不一定有is-a关系,只要是增强就算)

为了某个实现类在不修改原始类的基础上,进行动态地覆盖或增加方法原来的功能依旧对外开放, 依旧保留,新的功能同样也可以使用 ​

  1. 应用场景: IO流,DataSource是connection的装饰器,Spring中Detector结尾, Wrapper结尾

  2. UML 图: (原始版本比较复杂,实际都是用其简化版


    装饰器模式
  3. 代码示例:

public interface Component {
  void sampleOperation();
}

public class ConcreteComponent implements Component {
  @Override
  public void sampleOperation() {
    System.out.println("sampleOperation method in ConcreteComponent");
  }
}

public class ConcreteDecorator implements Component {

  private Component component;

  public ConcreteDecorator(Component component) {
    this.component = component;
  }

  @Override
  public void sampleOperation() {
    component.sampleOperation();
    // 其他业务逻辑代码
    System.out.println("为了扩展增加的代码");
  }
}

装饰器和适配器模式的区别

装饰器模式 是一种非常特别的适配器模式 装饰者 is-a 被装饰者 注重的是覆盖和扩展
适配器模式 可以使用代理(has-a)或者继承(is-a)实现 注重的是兼容和转换
4. 门面模式
  1. 应用场景
  2. UML图
  3. 代码示例

结构型模式:

1.策略模式

准备一组算法,将每一个算法封装起来,让外部按需调用 , 算法彼此之间可以互换(比如打折),用户对算法实现无需了解,只需要去选择

  1. 应用场景:

    BeanFactory 根据用户配置去选择不同的beanFactory, 通常和抽象工厂模式结合使用

    比如支付方式、登录方法, 比较器(sort 方法传comparator)

  2. UML图

    image.png
  1. 代码示例

    通过在Context种传入具体的策略,避免在内部使用if-else

    public interface Strategy {
    
      double calcPrice(double booksPrice);
    
    }
    
    public class StrategyA implements Strategy {
    
      @Override
      public double calcPrice(double booksPrice) {
        return booksPrice;
      }
    
    }
    
    public class StrategB implements Strategy {
    
      @Override
      public double calcPrice(double booksPrice) {
        return booksPrice * 0.9;
      }
    
    }
    
    // context
    public class Price {
    
      //持有一个具体的策略对象
      private Strategy strategy;
    
      public Price(Strategy strategy) {
        this.strategy = strategy;
      }
    
      //计算价格
      public double quote(double booksPrice) {
        return this.strategy.calcPrice(booksPrice);
      }
    }
    
    // StrategyClinet
    public class StrategyClient {
    
      Strategy strategy = new StrategyA();
      //创建环境
      Price price = new Price(strategy);
      //计算价格
      double quote = price.quote(300);
    }
    

基于枚举的策略模式请参考: https://github.com/925781609/pattern/tree/master/src/main/java/pattern/strategy/enumbased

2.模板方法模式

流程固定,某一环节有差异,将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。

父类中定义模板方法,定义了子类方法的执行过程, 并且父类定义的模板方法不能被修改(final)

  1. 应用场景:jdbcTemplate,工作流,redisTemplate, Spring Template

  2. UML图

模板方法模式
  1. 代码示例:

    public abstract class AbstractTemplate {
    
      // 模板方法, 定义成final, 防止子类overwrite
      final public void templateMethod() {
        // 调用基本方法(由子类实现)
        hookMethod();
        // 调用基本方法(由子类实现)
        abstractMethod();
        // 调用基本方法(已经实现)
        concreteMethod();
      }
    
      // 基本方法的声明(由子类实现,但抽象模板给出了默认实现)
      public void hookMethod() {
        System.out.println("Default hookMethod");
      }
    
      // 基本方法的声明(由子类实现)
      public abstract void abstractMethod();
    
      // 基本方法(已经实现)
      public final void concreteMethod() {
        System.out.println("AbstrateTemplate concreteMethod");
      }
    }
    
    public class ConcreteTemplate extends AbstractTemplate {
    
      @Override
      public void abstractMethod() {
        System.out.println("ConcreteTemplate abstractMethod");
      }
    
      /**
       * 不使用父类默认实现的hookMethod,自己重新定义hookMethod
       */
      @Override
      public void hookMethod() {
        System.out.println("ConcreteTemplate hookMethod");
      }
    
    }
    
    

    策略模式和模板方法模式的区别:

    策略模式只有选择权, 由用户自己选择已有的固定算法

    模板模式: 可以参与某一部分的自定义,但无法改变流程

3.委派模式

不是23种设计模式的一种,但是spring种比较常用: 客户请求时Boss,DispatcherServlet是委派者, Controller是被委派者

代理+ 策略:委派模式是代理模式特殊情况(委派者要持有被委派者的引用),全权代理,通常有策略模式作干预。

代理模式注重过程,而委派模式注重结果

策略模式注重的是可扩展(外部扩展), 委派模式注重内部的灵活和复用

  1. 应用场景:以Delegate/Dispatcher结尾的,NIO的selector就是委派模式

  2. UML图:(这里以Spring的DispatcherServlet为例)

委派模式
  1. 代码示例:

    // 真正干活的员工
    // @RestController
    public class UserController {
    
      // @RequestMapping(.....)
      public Object getUserById(String id) {
        return "Something";
      }
    }
    
    // 类似于项目经历分配任务
    public class DispatcherServlet {
    
      private List<Handler> handlerMapping = new LinkedList<>();
    
      public DispatcherServlet() {
        try {
          // 实际spring项目会在启动之初,扫描所有带RequestMapping注解的类,放到handlerMapping中
          Class<?> userControllerClass = UserController.class;
          handlerMapping.add(new Handler()
              .setController(userControllerClass.newInstance())
              .setMethod(userControllerClass.getMethod("getUserById", new Class[]{String.class}))
              .setUrl("/api/user"));
        } catch (Exception e) {
    
        }
      }
    
    
    public void doService(HttpServletRequest request, HttpServletResponse response) { doDispatch(request, response); }
    
    private void doDispatch(HttpServletRequest request, HttpServletResponse response) {
    
    
    // 1、获取用户请求的URL
    String uri = request.getRequestURI();
    
    //   根据用户请求的URL,去找到这个url对应的某一个java类的方法
    
    // 2、Servlet拿到URL以后,要做权衡(要做判断,要做选择)
    // 3、通过拿到的URL去handlerMapping(可以认为是策略常量)
    Handler handle = null;
    for (Handler h : handlerMapping) {
      if (uri.equals(h.getUrl())) {
        handle = h;
        break;
      }
    }
    
    // 4、将具体的任务分发给Method(通过反射去调用其对应的方法)
    Object object = null;
    try {
      object = handle.getMethod().invoke(handle.getController(), request.getParameter("mid"));
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      e.printStackTrace();
    }
    
    // 5、获取到Method执行的结果,通过Response返回出去
    // response.getWriter().write();
    
    
    
    }
    
    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;
    }
    
    
    
    }
    
    }
    
4.观察者模式

针对目标对象的一举一动, 要得到一个反馈,通常会和代理模式配合使用, 观察者和被观察者之间没有必然的联系,

  1. 应用场景:

事件监听, 日志监听, Observer,Springlistener(通常会结合动态代理), 短信通知, 邮件通知

Event是事件,EventListener, 事件的注册和监听

Mouse观察者, Callback被观察者

  1. UML图


    观察者模式
  1. 代码示例

    public abstract class Subject {
    
      /**
       * 用来保存注册的观察者对象
       */
      private List<Observer> observers = new ArrayList<Observer>();
    
      /**
       * 注册观察者对象
       *
       * @param observer 观察者对象
       */
      public void attach(Observer observer) {
    
        observers.add(observer);
        System.out.println("Attached an observer");
      }
    
      /**
       * 删除观察者对象
       *
       * @param observer 观察者对象
       */
      public void detach(Observer observer) {
    
        observers.remove(observer);
      }
    
      /**
       * 通知所有注册的观察者对象
       */
      public void nodifyObservers(String newState) {
    
        for (Observer observer : observers) {
          observer.update(newState);
        }
      }
    }
    
    public class ConcreteSubject extends Subject {
    
      private String state;
    
      public String getState() {
        return state;
      }
    
      public void change(String newState) {
        state = newState;
        System.out.println("主题状态为:" + state);
        //状态发生改变,通知各个观察者
        this.nodifyObservers(state);
      }
    }
    
    public interface Observer {
    
      /**
       * 更新接口
       *
       * @param state 更新的状态
       */
      public void update(String state);
    }
    
    public class ConcreteObserver implements Observer {
    
      //观察者的状态
      private String observerState;
    
      @Override
      public void update(String state) {
        /**
         * 更新观察者的状态,使其与目标的状态保持一致
         */
        observerState = state;
        System.out.println("状态为:" + observerState);
      }
    
    }
    
    public class Client {
    
      public static void main(String[] args) {
        //创建主题对象
        ConcreteSubject subject = new ConcreteSubject();
        //创建观察者对象
        Observer observer = new ConcreteObserver();
        //将观察者对象登记到主题对象上
        subject.attach(observer);
        //改变主题对象的状态
        subject.change("new state");
      }
    
    }
    
5. 责任链模式

创建多个对象,使这些对象形成一条链,并沿着这条链传递请求,直到链上的某一个对象决定处理此请求。

纯责任链模式: 如果一个类要么承担责任处理请求要么将请求踢给下一个皮球

非纯责任链模式:如果一个类承担了一部分责任,还将请求踢给下一个皮球

  1. 应用场景:

会员等级系统,会员等级之间构成一条链,用户发起一个请求,直到传递到与用户会员匹配的等级

请假或报销,自己能处理则处理,处理不了则往上报

  1. UML图(图片有误,子类覆写的是report方法handleMessage方法)
责任链模式
  1. 代码示例

    public abstract class Handler {
    
      private Handler nextHandler;
      private int level;
    
      public Handler(int level) {
        this.level = level;
      }
    
      // 处理请求传递,注意final,子类不可重写
      public final void handleMessage(Request request) {
        if (level == request.getLevel()) {
          this.report(request);
        } else {
          if (this.nextHandler != null) {
            System.out.println("自己无法处理,传递给下一级");
            this.nextHandler.handleMessage(request);
          } else {
            System.out.println("处理链到达尽头,无法处理请求");
          }
        }
      }
    
      public void setNextHandler(Handler handler) {
        this.nextHandler = handler;
      }
    
      // 抽象方法,子类实现
      public abstract void report(Request request);
    
    }
    
    public class Leader extends Handler {
    
      public Leader() {
        super(1);
      }
    
      @Override
      public void report(Request request) {
        System.out.println("Leader 处理:" + request.getDetail());
      }
    }
    
    public class Boss extends Handler {
    
      public Boss() {
        super(2);
      }
    
      @Override
      public void report(Request request) {
        System.out.println("Boss 处理:" + request.getDetail());
      }
    }
    
    public class Client {
    
      public static void main(String[] args) {
        Request requestLevel2 = new Request(2, "Level 2的请求"); // 请求等级高
    
        Boss boss = new Boss();
        Leader leader = new Leader();
        // 设置下一级, 建立职责链
        leader.setNextHandler(boss);
    
        System.out.println("==============开始处理请求=========");
        leader.handleMessage(requestLevel2);
      }
    }
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,302评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,563评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,433评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,628评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,467评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,354评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,777评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,419评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,725评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,768评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,543评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,387评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,794评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,032评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,305评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,741评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,946评论 2 336

推荐阅读更多精彩内容