设计模式之美学习笔记二-部分demo

这部分总结了23种设计模式的定义,适用场景,部分设计模式写了小demo来演示。总体来说设计模式一定要注意他的适用场景和实现方式这两点,只关注实现方式,就会觉得好几种设计模式类图差不多,但实际上这几种的应用场景完全不同,如果只关注适用场景,具体写代码还是不会套用,那也是纸上谈兵,总之设计模式常看常新,每次都有不同的体会。有问题可以在本博客下留言或者个人博客上留言。

文章目录

  • 构建型设计模式
    • 单例模式
      • 饿汉式:
      • 懒汉式
      • 双重检测(double check)模式
      • 静态内部类
      • 枚举
      • 单例模式存在的问题
      • 如何替代单例模式
    • 工厂模式
    • 建造者模式
    • 原型模式
    • 小结
  • 结构型设计模式
    • 代理模式(Proxy Design Pattern)
      • 代理模式的应用场景:
    • 桥接模式(Bridge Design Pattern)
    • 装饰器模式
    • 适配器模式(Adapter Design Pattern)
      • 代理、桥接、装饰器、适配器四种设计模式的区别
    • 门面模式(Facade Design Pattern)
    • 组合模式(Composite Design Pattern)
    • 享元模式(Flyweight Design Pattern)
  • 行为型设计模式
    • 观察者模式(Observer Design Pattern)
    • 模板模式(Template Method Design Pattern)
      • 模板方法模式的作用
    • 策略模式(Strategy Design Pattern)
    • 责任链模式(Chain of Responsibility Design Pattern)
    • 状态模式
    • 备忘录模式(Memento Design Pattern)
    • 迭代器模式(Iterator Design Pattern)
    • 访问者模式(Visitor Design Pattern)
    • 命令模式(Command Design Pattern)
    • 解释器模式(Interpreter Design Pattern)
    • 中介模式(Mediator Design Pattern)

构建型设计模式

单例模式

如何实现一个单例:

  • 构造函数需要是private访问权限,避免外部直接new对象
  • 考虑对象创建时的线程安全问题
  • 考虑是否支持懒加载
  • 考虑getInstance()性能是否高(是否加锁)

饿汉式:

public class Singleton{ 
     private static Singleton instance = new Singleton(); 
     private Singleton(){} 
     public static Singleton getInstance(){  
          return instance;  
      } 
}

优点:线程 安全,调用效率高

缺点:一上来就实例化了单例,如果该单例从头到尾都没有用到,或者初始化的耗时较长,是一种资源的浪费,不支持懒加载

不过专栏作者的意见是如果确实初始化耗时较长,反而不如通过饿汉式在项目启动初期就加载好,以免在后面实际使用的时候才加载(懒加载)造成性能损耗,或者用户体验下降

懒汉式

public class Singleton {
     
    //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
    private static Singleton instance;
     
    //构造器私有化
    private Singleton(){}
     
    //方法同步,调用效率低
    public static synchronized Singleton getInstance(){
        if(instance==null){
            instance=new Singleton();
        }
        return instance;
    }
}

优点:线程安全,支持懒加载(即真正使用的时候再去加载)

缺点:获取单例的方法加锁,性能不高

双重检测(double check)模式

public class Singleton {
        private volatile static Singleton singleton;

        private Singleton() {
        }

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

优点:即支持懒加载模式,又避免了懒汉式每次调用均加锁造成的性能损耗(只要第一次创建成功后,后续再次调用就不会进入同步方法)。

缺点:注意singleton成员变量前一定要加volatile关键字,防止指令重排。这么做的目的是避免因为指令重排导致实例被new出来之后(实例化)赋值给singleton变量后还没来得及初始化,就被另外一个线程使用了。目前高版本的java已经在JDK内部解决了这个问题(通过将new对象和初始化操作设计为原子性操作,即可避免指令重排带来的问题)

静态内部类

public class Singleton {

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

    private Singleton(){}

    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }

}

优点:线程安全,效率高,支持懒加载。由于SingletonHolder是一个静态内部类,当外部类Singleton被加载的时候,并不会创建SingletonHolder实例对象,只有当调用getInstance()方法时,SingletonHolder才会被加载,这个时候才创建instance实例,实例的唯一性,创建过程的线程安全性,都由JVM保证

枚举

public enum Singleton {

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

    //添加自己需要的操作
    public void singletonOperation(){
    }
}

优点:通过枚举本身的特点(枚举实例本身就是单例的)保证了创建实例的线程安全性和实例的唯一性。线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用

单例模式存在的问题

大部分情况下,我们使用单例来表示一些全局唯一类,比如配置信息类,连接池类,ID生成器类。

单例对OOP特性的支持不友好

由于单例的使用方式基于实现类而非基于接口,将来想要更换一种生成单例的方式,需要变更代码的各个地方,此外单例对继承,多态的支持也不好(不是不能继承,只是这样做比较怪异)

单例会隐藏类之间的依赖关系

由于单例的调用是直接调用其对外提供的公共方法,内部去生成实例对象,单例类不需要显式创建,不需要依赖参数传递,在函数中直接调用就可以了,如果代码比较复杂,这种调用关系就会非常隐蔽

单例对代码的拓展性不友好

由于单例类只能有一个对象实例,如果将来由于需求变更需要支持多实例,就需要改动较多代码。

单例对代码的可测试性不友好

单例不支持有参数的构造函数

参数的传递需要放在getInstance()中

如何替代单例模式

为了保证全局唯一,除了使用单例,还可以使用静态方法来实现。但是他比单例更加不灵活,比如无法支持懒加载。实际上类对象的全局唯一性可以通过多种方式保证,除了单例模式,还有工厂模式,IOC容器保证 ,或者程序员代码中保证。

工厂模式

一般情况下工厂模式被分为简单工厂(静态工厂方法模式)、工厂方法和抽象工厂三类。简单工厂和工厂方法原理简单,项目中常用,抽象工厂原理稍微复杂,在项目中不经常用。

工厂方法相比简单工厂方法,需要更多的类去实现工厂方法接口类,当对象的创建逻辑比较复杂,不只是简单new一下的时候推荐使用工厂模式,将复杂的创建逻辑拆分到各个工厂类中,让每个工厂类不至于过于复杂。

总结下就是简单工厂将对象创建的不同分支用if、else写在一个工厂类中,工厂方法是将这些分支拆分成不同的工厂类。

当创建逻辑比较复杂,是一个大工程的时候,就需要考虑使用工厂模式,封装对象的创建,将对象的创建与使用分离开来。

工厂模式的作用无外乎下面四个,也是我们判断要不要使用工厂模式的标准:

  • 封装变化:创建逻辑有可能发生变化,封装成工厂类之后,创建逻辑的变更对调用者透明
  • 代码复用:创建代码抽离到独立的工厂类之后可以复用
  • 隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象
  • 控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁

以DI容器讲解工厂模式:

DI容器的核心功能一般有三点:配置解析,对象创建和对象生命周期管理,详情可以看spring源码解析

下面是简单工厂模式和工厂模式的示例代码:

//手机类
public interface IMobile {
    void call();
}
public class Huawei implements IMobile{
    @Override
    public void call() {
        System.out.println("我是华为手机");
    }
}
public class Xiaomi implements IMobile {
    @Override
    public void call() {
        System.out.println("我是小米手机");
    }
}
public class Oppo implements IMobile{
    @Override
    public void call() {
        System.out.println("我是oppo手机");
    }
}
//简单工厂类
/**
 * 简单工厂模式
 * 特点,各种类型的类创建并不复杂,把类的创建写在一个工厂类中,
 * 可以通过if,else,或者map获取具体的类
 */
public class SimpleFactory {

    public static IMobile createMobile(String brand){
        if (brand == null){
            return null;
        }
        if (brand.equalsIgnoreCase("huawei")){
            return new Huawei();
        }else if (brand.equalsIgnoreCase("xiaomi")){
            return new Xiaomi();
        }else if (brand.equalsIgnoreCase("oppo")){
            return new Oppo();
        }else {
            return null;
        }
    }

    public static void main(String[] args) {
        IMobile mobile = SimpleFactory.createMobile("huawei");
        if (mobile != null) {
            mobile.call();
        }
    }
}


//工厂方法模式
public interface IMobileFactory {
    IMobile getMobile();
}
public class HuaweiFcatory implements IMobileFactory {
    @Override
    public IMobile getMobile() {
        //经过一系列复杂的判断,前置条件校验,准备
        return new Huawei();
    }
}
public class XiaomiFactory implements IMobileFactory{
    @Override
    public IMobile getMobile() {
        //经过一系列复杂的判断,前置条件校验,准备
        return new Xiaomi();
    }
}
public class OppoFactory implements IMobileFactory{
    @Override
    public IMobile getMobile() {
        //经过一系列复杂的判断,前置条件校验,准备
        return new Oppo();
    }
}
public class Client {
    public static void main(String[] args) {
        //实际中可以根据传入的参数获取具体的工厂类
        IMobileFactory factory = new HuaweiFcatory();
        //创建手机的逻辑比较复杂,拆分为各种工厂类(示例中比较简单,忽略)
        IMobile mobile = factory.getMobile();
        mobile.call();
    }
}

建造者模式

建造者模式与工厂模式的不同

工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的参数,"定制化"地创建不同的对象。

建造者模式的使用场景:

  • 当类的构造函数必填属性很多,通过set设置,没有办法校验必填属性
  • 如果类的属性之间具有一定的依赖关系,构造函数配合set方式,无法进行依赖关系和约束条件校验
  • 需要创建不可变对象,不能暴露set方法

实现方式:把构造函数定义为private,定义public static class Builder内部类,通过Builer类的set方法设置属性,调用build方法创建对象。下面是一个简单示例:

/**
*假设数据库连接池参数配置有很多,现在我们通过建造者模式来完成创建DataBaseConnectPoolConfig
*/
@Data
public class DataBaseConnectPoolConfig {
    /**
     * # 数据库的连接地址。
     */
    private String url;
    private String userName;
    private String password;
    /**
     * 初始化时建立的物理连接数。初始化发生在显式调用init方法,或者第一次getConnection时.
     */
    private Integer initialSize;
    /**
     * 连接池最大物理连接数量。
     */
    private Integer maxActive;

    public DataBaseConnectPoolConfig(Builder builder) {
        this.url = builder.url;
        this.userName = builder.userName;
        this.password = builder.password;
        this.initialSize = builder.initialSize;
        this.maxActive = builder.maxActive;
    }

    /**
     * 这里的builder设置为一个静态内部类,也可以单独拿出去作为一个类
     */
    @Data
    public static class Builder{
        private static final String URLSTRING = "myUrl";
        private static final String USERNAME = "username";
        private static final String PASSWORD = "password";


        private String url = URLSTRING;
        private String userName = USERNAME;
        private String password = PASSWORD;
        private Integer initialSize;
        private Integer maxActive;
        public DataBaseConnectPoolConfig build(){
            if (initialSize == 5){
                System.out.println("初始化的连接数为5");
            }
            //经过一系列的判断等等操作(对set方法之后的数据校验),返回创建好的连接池配置对象
            return new DataBaseConnectPoolConfig(this);
        }

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

        public Builder setUserName(String userName) {
            this.userName = userName;
            return this;
        }

        public Builder setPassword(String password) {
            this.password = password;
            return this;
        }

        public Builder setInitialSize(Integer initialSize) {
            this.initialSize = initialSize;
            return this;
        }

        public Builder setMaxActive(Integer maxActive) {
            this.maxActive = maxActive;
            return this;
        }
    }
    //模拟客户端通过建造者模式创建连接池配置对象
    public static void main(String[] args) {
        DataBaseConnectPoolConfig config = new Builder().setUrl("http://abc")
                .setUserName("zhangsan").setPassword("abc").setInitialSize(5)
                .setMaxActive(5).build();
        System.out.println(config.toString());
    }

}

原型模式

什么是原型模式

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(拷贝)的方式来创建新的对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式叫做原型模式

原型模式的两种实现方式

深拷贝和浅拷贝。浅拷贝只复制对象基本数据类型数据和引用对象的内存地址,不会递归的复制引用对象,以及引用对象的引用对象,而深拷贝得到的是一份完全独立的对象,所以深拷贝比浅拷贝更耗时间。

如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没有问题的,但是对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能会出现数据被修改的风险。

小结

单例模式用来创建全局唯一的对象。工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建复杂对象,可以通过设置不同的可选参数,定制化的创建不同的对象。原型模式针对创建成本比较大的对象,利用对已有对象进行复制的方式进行创建,以节省创建时间。

结构型设计模式

结构型模式包括:代理模式,桥接模式,装饰器模式,适配器模式,门面模式,组合模式,享元模式

代理模式(Proxy Design Pattern)

代理模式的应用场景:

业务系统的非功能性需求开发

比如:监控,统计,鉴权,限流,事务,幂等,日志等(AOP就是基于动态代理)

在RPC/缓存中的使用

RPC框架也可以看做一种代理模式

桥接模式(Bridge Design Pattern)

对于桥接模式的两种不同的理解:在GOF的《设计模式》中,定义为:“将抽象和实现解耦,让它们可以独立变化”,在其他资料书中,定义为:一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行拓展

对于第一种GOF的定义,理解其中的"抽象"和"实现"两个概念,是理解它们的关键。这里的抽象指的并不是抽象类或者接口,而是被抽象出来的一套"类库",只包含骨架代码,真正的业务逻辑需要委派给定义中的"实现"来完成。而这里的实现,也并非"接口的实现类",而是一套独立的类库,抽象和实现独立开发,通过对象之间的组合关系,组装在一起

对于第二种理解方式,类似于"组合优于继承"的设计原则,通过组合关系来替代继承关系,避免继承层次过多。

桥接模式的应用,在JDBC中,针对不同的数据库有不同的Driver类,可以按需切换,JDBC只定义接口,具体的实现委托给具体的数据库Driver

下面以不同线上系统不同紧急程度下消息发送的小例子来展示桥接模式

//消息发送接口类
public interface MsgSender {
    void send(String message);
}

public class TelephoneMsgSender implements MsgSender{
    private List<String> telephones;
    public TelephoneMsgSender(List<String> telephones) {
        this.telephones = telephones;
    }
    @Override
    public void send(String message) {
        //具体的发送短信的逻辑
    }
}

public class EmailMsgSender implements MsgSender{
    @Override
    public void send(String message) {
        //类似于TelephoneMsgSender
    }
}

public class WechatMsgSender implements MsgSender {
    @Override
    public void send(String message) {
        //类似于TelephoneMsgSender
    }
}

/**
 * Notification相当于抽象,MsgSender相当于实现,
 * 两者可以独立开发,通过组合关系(也就是桥梁)任意组合在一起。所谓的任意组合
 * 的意思就是,不同紧急程度的消息和发送渠道之间的对应关系,不是在代码中固定写死,
 * 我们可以动态地去指定(比如通过读取配置来获取对应关系)
 */
public abstract class Notification {
    protected MsgSender msgSender;
    public Notification(MsgSender msgSender) {
        this.msgSender = msgSender;
    }
    public abstract void notify(String message);
}

/**
 *严重情况下消息发送类
 */
public class SevereNotification extends Notification {
    public SevereNotification(MsgSender msgSender) {
        super(msgSender);
    }
    @Override
    public void notify(String message) {
        msgSender.send(message);
    }
}
/**
紧急情况下消息发送类(逻辑类似SevereNotification)
*/
public class UrgencyNotification extends Notification {
    public UrgencyNotification(MsgSender msgSender) {
        super(msgSender);
    }
    @Override
    public void notify(String message) {

    }
}
/**
普通情况下消息发送类(逻辑类似SevereNotification)
*/
public class NormalNotification extends Notification{
    public NormalNotification(MsgSender msgSender) {
        super(msgSender);
    }
    @Override
    public void notify(String message) {

    }
}

装饰器模式

装饰器模式并非简单的利用组合替代继承,它有两个比较特殊的地方:

  • 装饰类和原始类继承同样的父类,这样我们可以对原始类"嵌套"多个装饰器类
  • 装饰类是对功能的增强,这也是装饰器模式应用场景的一个重要特点

装饰器模式与代理模式的不同点:

代理类增加的是与原始类没有关系的功能,而装饰类增加的是原始类功能的增强

下面是一个简单示例:

public interface BaseInterface {
    void say();
}
public class BaseClass implements BaseInterface{
    @Override
    public void say() {
        System.out.println("我是被装饰者,我要开演唱会");
    }
}
public class Decorator implements BaseInterface{
    private BaseInterface baseInterface;

    public Decorator(BaseInterface baseInterface) {
        this.baseInterface = baseInterface;
    }
    @Override
    public void say() {
        System.out.println("演唱会前我先好好锻炼身体,记歌词");
        this.baseInterface.say();
        System.out.println("演唱会后我要好好复盘");
    }
    public static void main(String[] args) {
        BaseClass base = new BaseClass();
        Decorator decorator = new Decorator(base);
        decorator.say();
    }
}

适配器模式(Adapter Design Pattern)

适配器模式是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。举个形象的例子,USB转接头就是生活中的一种适配器

适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系实现,对象适配器使用组合关系来实现。

适配器模式适用场景:

一般来说,适配器模式可以看做一种"补偿模式",用来补救设计上的缺陷。如果在设计初期,我们就能协调规避接口不兼容的问题,那这种模式就没有应用的机会了。适配器模式适用于以下场景:

  • 封装有缺陷的接口设计

    假设我们依赖的外部系统在接口设计方面有缺陷,引入之后会影响到我们自身代码的可测试性。为了隔离设计上的缺陷,我们希望对外部系统提供的接口进行二次封装,抽象出更好的接口设计,这个时候就可以使用适配器模式了。

  • 统一多个类的接口设计

    某个功能的实现依赖多个外部系统(或者说类),通过适配器模式,将它们的接口适配为统一的接口定义,然后我们就可以使用多态的特性来复用代码逻辑

  • 替换依赖的外部系统

    当我们把项目中依赖的一个外部系统替换为另一个外部系统的时候,依靠适配器模式,可以减少对代码的改动

  • 兼容老版本接口

    在做版本升级的时候,对于一些要废弃的接口,我们不直接将其删除,而是暂时保留,并且标注为deprecated,并将内部实现逻辑委托为新的接口实现。这样做的好处是,使用它的项目有个过渡期,而不是强制进行代码修改

  • 适配不同格式的数据

下面是适配器模式的简单示例:

/**
 * 需要将其他类适配成该目标类
 */
public interface ITarget {
    void f1();
    void f2();
    void fc();
}
/**
 * 源类,需要将该类适配成ITarget接口类
 */
public class Adaptee {
    public void fa(){};
    public void fb(){};
    public void fc(){};
}
/**
 * 类适配器,基于继承
 */
public class ClassAdaptor extends Adaptee implements ITarget{

    @Override
    public void f1() {
        super.fa();
    }

    @Override
    public void f2() {
        //重新实现f2
    }
    //这里的fc不需要实现,直接继承自Adaptee,这是和对象适配器最大的不同
}
/**
 *对象适配器基于组合
 */
public class InstanceAdaptor implements ITarget {

    private Adaptee adaptee;

    public InstanceAdaptor(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void f1() {
        adaptee.fa();//委托给Adaptee
    }

    @Override
    public void f2() {
        //重新实现f2
    }

    @Override
    public void fc() {
        adaptee.fc();
    }
}

代理、桥接、装饰器、适配器四种设计模式的区别

代理,桥接,装饰器,适配器,这四种模式是比较常用的结构型设计模式。它们的代码结构非常相似,笼统来说,都可以成为Wrapper模式,也就是通过Wrapper类二次封装原始类。他们之间的区别如下:

**代理模式:**代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同

**桥接模式:**桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、相对独立的加以改变

**装饰器模式:**装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰类的嵌套使用

**适配器模式:**适配器模式是一种时候的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口

门面模式(Facade Design Pattern)

门面模式主要用于接口设计方面,GOF的《设计模式》中定义如下:门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用,这里的子系统可以有多种理解方式,既可以是一个完整的系统,也可以是更细粒度的类或者模块,门面模式的应用场景如下:

  • 解决易用性问题

  • 解决性能问题

  • 解决分布式事务问题

组合模式(Composite Design Pattern)

组合模式在GOF的《设计模式》中定义如下:将一组对象组织(Compose)成树形结构,以表示一种"部分-整体"的层次结构,组合让客户端(代指代码的使用者)可以统一单个对象和组合对象的处理逻辑

享元模式(Flyweight Design Pattern)

享元,顾名思义就是被共享的单元,享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。不可变对象指的是,一旦通过构造函数初始化完成之后,它的状态(对象的成员变量或者属性)就不会再被修改了。所以不可变对象不能暴露任何set()等修改内部状态的方法。

Integer类型的缓冲池中保存的-127-128就是一种享元模式的应用,String中常量池也是一种享元模式

行为型设计模式

创建型设计模式主要解决"对象的创建"问题,结构型设计模式主要解决"类或对象的组合或组装"问题,行为型设计模式主要解决的就是"类或对象之间的交互"问题。行为型设计模式有11种,包括:观察者模式,模板模式,策略模式,责任链模式,状态模式,迭代器模式,访问者模式,备忘录模式,命令模式,解释器模式,中介模式。实际上设计模式要干的事情就是解耦,创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦。

观察者模式(Observer Design Pattern)

观察者模式也被称为发布订阅模式,在GOF《设计模式》中定义为:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

一个典型的观察者模式应用是spring中的事件监听和谷歌提供的event bus,这里就不举例子了

模板模式(Template Method Design Pattern)

模板模式,全称是模板方法设计模式,在GOF《设计模式》一书中定义如下:模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。下面是模板方法模式的一个示例:

/**
 * 定义模板方法的父类
 */
public abstract class AbstractBaseClass {

    public void testMethod(){
        //定义模板方法流程(业务逻辑顺序),具体方法由子类实现
        method1();
        method2();
    }

    protected abstract void method2();

    protected abstract void method1();

    public static void main(String[] args) {
        AbstractBaseClass baseClassA = new SubClassA();
        baseClassA.testMethod();
        AbstractBaseClass baseClassB = new SubClassB();
        baseClassB.testMethod();
    }
}
public class SubClassA extends AbstractBaseClass{

    @Override
    protected void method2() {
        System.out.println("我是子类A的方法二");
    }

    @Override
    protected void method1() {
        System.out.println("我是子类A的方法一");
    }

}
public class SubClassB extends AbstractBaseClass{

    @Override
    protected void method2() {
        System.out.println("我是子类B的方法二");
    }

    @Override
    protected void method1() {
        System.out.println("我是子类B的方法一");
    }

}

模板方法模式的作用

  • 复用

    模板方法模式把业务逻辑中不变的流程抽象到父类的模板方法中,将可变的部分留给子类实现,所有的子类都可以复用父类中的模板方法定义的流程代码。

  • 扩展

    这里说的扩展,并非代码的扩展性,而是指框架的扩展性。模板模式常用在框架的开发中,让框架用户可以在不修改框架源码的情况下,定制化框架的功能。

模板模式和回调的区别:模板模式基于继承来实现,回调基于组合来实现

策略模式(Strategy Design Pattern)

在GOF的《设计模式》一书中,策略模式是这样定义的:定义一族算法类,将每个算法分别封装起来,让它们可以相互替换。策略你是可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)

下面是策略模式的一个小例子:

public interface BaseStrategy {
    void handle();
}
public class StrategyA implements BaseStrategy{
    @Override
    public void handle() {
        System.out.println("现在执行策略A的代码");
    }
}
public class StrategyB implements BaseStrategy{
    @Override
    public void handle() {
        System.out.println("现在执行策略B的代码");
    }
}
public class StrategyC implements BaseStrategy{
    @Override
    public void handle() {
        System.out.println("现在执行策略C的代码");
    }
}

@Getter
public enum StrategyTypeEnums {
    /**
     * 策略枚举类
     */
    STRATEGYA("A",new StrategyA()),
    STRATEGYB("B",new StrategyB()),
    STRATEGYC("C",new StrategyC()),
    ;

    private String type;
    private BaseStrategy baseStrategy;

    StrategyTypeEnums(String type, BaseStrategy baseStrategy) {
        this.type = type;
        this.baseStrategy = baseStrategy;
    }
    public static BaseStrategy getStrategyByType(String type){
        if (type == null) {
            return null;
        }
        for (StrategyTypeEnums value : values()) {
            if (value.type.equals(type)){
                return value.baseStrategy;
            }
        }
        return null;
    }
}
//客户端
public class StrategyClient {
    public static void main(String[] args) {
        String type = "A";
        BaseStrategy strategyByType = StrategyTypeEnums.getStrategyByType(type);
        if (strategyByType != null) {
            strategyByType.handle();
        }
    }
}

责任链模式(Chain of Responsibility Design Pattern)

责任链模式在GOF的《设计模式》中定义为:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接受对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接受对象能够处理它为止。

下面是责任链模式的一个小例子:

public interface BaseHandler {
    void handle();
}
@Component
public class FirstHandler implements BaseHandler {
    @Override
    public void handle() {
        System.out.println("经过第一个处理器");
    }
}
@Component
public class SecondHandler implements BaseHandler {
    @Override
    public void handle() {
        System.out.println("经过第二个处理器");
    }
}
@Component
public class ThirdHandler implements BaseHandler {
    @Override
    public void handle() {
        System.out.println("经过第三个处理器");
    }
}
@Configuration
public class BaseConfig implements InitializingBean {

   public static List<BaseHandler> handlers = new ArrayList<>();
   @Autowired
   private FirstHandler firstHandler;
   @Autowired
   private SecondHandler secondHandler;
   @Autowired
   private ThirdHandler thirdHandler;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("开始加入配置");
        handlers.add(firstHandler);
        handlers.add(secondHandler);
        handlers.add(thirdHandler);
        System.out.println("配置加入完毕");
    }
}
@Controller
@RequestMapping("/design")
public class DesignPatternClient {
    //两种方式都可以拿到责任链中的处理器
    @Autowired
    //private List baseHandlers = BaseConfig.handlers;
    Map<String,BaseHandler> map;

    @RequestMapping("/testHandler")
    public void testHandler(){
        /*baseHandlers.forEach(h->{
            h.handle();
        });*/
        for(Map.Entry<String, BaseHandler> entry :map.entrySet()){
            entry.getValue().handle();
        }
    }
}

状态模式

状态模式一般用来实现有限状态机(Finite State Machine),缩写为FSM。状态机有三个组成部分:状态(state)、事件(event)、动作(Action),其中事件也称为转移条件,事件触发状态的转移及动作的执行。例如订单的状态可以会有各种不同的流转,通过状态模式可以将不同状态可以进行的动作拆分开来。

状态模式比较简单,此处不再举例。

备忘录模式(Memento Design Pattern)

在GOF的《设计模式》一书中,备忘录模式的定义为:在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。

迭代器模式(Iterator Design Pattern)

迭代器模式也叫游标模式,是用来遍历集合对象的。相比于for循环,迭代器模式有如下优点:

  • 迭代器模式封装集合内容部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可
  • 迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一
  • 迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。此外,迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。

访问者模式(Visitor Design Pattern)

在GOF的《设计模式》一书中,访问者模式的定义为:允许一个或多个操作应用到一组对象上,解耦操作和对象本身。

命令模式(Command Design Pattern)

在GOF的《设计模式》一书中,命令模式的定义为:将请求(命令)封装为一个对象,这样可以使用不同的请求参数化对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能。

解释器模式(Interpreter Design Pattern)

在GOF的《设计模式》一书中,解释器模式的定义为:解释器模式为某个语言定义它的语法(或叫文法)表示,并定义一个解释器用来处理这个语法。

spel表达式类似解释器模式,规则引擎类似解释器模式。

中介模式(Mediator Design Pattern)

在GOF的《设计模式》一书中,中介模式的定义为:中介模式定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象,来避免对象之间的直接交互。

中介模式的设计思想与中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟n个对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。

观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。而在中介模式的引用场景中,参与者之间的关系错综复杂,既可以是消息的发送者,也可以同时是消息的接收者。

你可能感兴趣的:(学习笔记,java,设计模式)