设计模式(上)

设计模式基础

概述:

 

  1. 观察者模式

定义:观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变时,它的所有依赖者都会收到通知并自动更新。

 

适用场景:存在订阅关系,一个主题被许多观察者订阅,当主题中受观察者关心的数据有变动时,需要立马通知观察者。这里的通知分为推送和拉取2种方式。

    1. 类图

 

    1. 精要

 

    1. 典型应用

 

 

主题接口定义了一些方法:

registerObserver();

removeObserver();

notifyObservers();

 

观察者接口定义了一个方法:

update();

 

实现参考:

ImplementsSubject{

成员变量部分:

MemberDatas;

List of Observers;//可以使用ArrayList

 

方法部分:

Construct{创建List of Observers}

registerObserver(Observer o){将观察者加入到List of Observers}

notifyObservers(){调用每个Observer的update()}

……

}

 

ImplementsObserver{

成员变量部分:

MemberDatas;

Subject;

 

方法部分:

Construct(Subject s){s.registerObserver}

Update(params){给成员变量赋值}

 

个人初步理解与疑问

适用场景:从另一个模块或调服务等取数据,而内部在多个地方依赖这个数据,那么主题用来从别的模块或调服务等取数据,观察者才是真正需要用数据的地方,如此就把取数据和使用数据分开了,对数据的依赖就解耦了,后续在数据产生变化时,在取数据的地方修改,使用的地方改动不大。

 

JDK提供了对观察者模式的支持,在java.util包下有Observer接口和Observable类,如果使用JDK的支持,可用主题类继承Observable类,而需要的业务观察者类实现Observer接口并实现相关业务

 

案例:

JDK中的典型实现

      1. Spring中的应用

Spring中观察者模式主要应用于Listener的实现,如ApplicationListener

  1. 单例模式

定义:

适用场景:

    1. 类图

 

    1. 精要

 

    1. 典型应用
      1. MyBatis中的单例模式

(1)ErrorContext

用在每个线程范围内的单例,用于记录该线程的执行环境错误信息

LogFactory

提供给整个MyBatis使用的日志工厂,用于获得针对项目配置好的日志对象

LogFactory产生的实例是Constructor<? Extends Log>类型,即产生的不只是一个产品,而是具有Log公共接口的一系列产品,如Log4jImpl、Slf4jImpl等具体日志系统。

    1. 几种实现

 

(1)懒汉模式要点:

构造函数为private的

声明一个private static的对象变量

定义一个public static的获取对象的方法,在其中调用私有构造方法,先判空

所谓的懒指的是在需要的时候才创建,而不是立马(区别于饿汉)

缺陷:线程不安全,即多线程无法保证只产生一个实例

 

(2)synchronized的懒汉

即获取对象的方法加了synchronized,保证不会有2个线程同时访问这个方法

缺陷:只在第一次访问是需要同步锁,第二次是多余的,因此降低了性能,尤其是频繁调用这个方法的场景。

 

(3)饿汉模式

要点:饿汉模式的单例,实例化是在静态中创建的,通过私有构造方法返回给静态变量,或在静态代码块中返回。获取对象的方法不需要判空,因为静态变量或代码块只会加载1次。所谓的饿指的是在JVM加载类时创建(静态初始化了),即在访问获取对象的方法前已经创建了。

 

(4)双检锁模式,线程安全,推荐使用

声明一个private static volatile的对象变量

判空后synchronized锁类,再判空

缺陷:可能被多个类加载器加载,解决办法,指定类加载器

public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {}

 

    public static Singleton getInstance() {

        if (singleton == null) {

            synchronized (Singleton.class) {

                if (singleton == null) {

                    singleton = new Singleton();

                }

            }

        }

        return singleton;

    }

问题1:双检锁模式的volatile修饰是否是必须的?为什么?

答:是必须的,

双检锁单例的解析:https://blog.****.net/hnd978142833/article/details/81633730

 

(5)静态内部类,线程安全,主调时才实例化,延迟加载效率高,推荐使用

public class Singleton {  

    private static class SingletonHolder {  

              private static final Singleton INSTANCE = new Singleton();  

    }  

    private Singleton (){}  

    public static final Singleton getInstance() {  

              return SingletonHolder.INSTANCE;  

    }  

}

(6)枚举,线程安全,避免反序列化,使用较少

public enum Singleton {  

    INSTANCE;  

    public void whateverMethod() {  

    }  

}

 

 

案例

JDK中的典型实现

java.lang.Runtime;便是单例模式的实现,并且是饿汉模式

private static Runtime currentRuntime = new Runtime();

 

    public static Runtime getRuntime() {

        return currentRuntime;

    }

 

private Runtime() {}

      1. 疑问

 

  1. 工厂模式

工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

    1. 类图

 

    1. 精要

工厂方法和创建者并不总是抽象的,可以提供默认的工厂方法

简单工厂将全部事情在一个地方处理完,而工厂方法创建一个框架,在子类决定如何实现。

      1. 涉及的设计原则

依赖抽象,不依赖具体。(依赖倒置)

    1. 典型应用
      1. Mybatis中的工厂模式

(1)SqlSessionFactory

SqlSessionFactory是简单工厂/静态工厂方法模式,用于创建其它类的实例。

ObjectFactory

 

MapperProxyFactory

 

TransactionFactory

 

LogFactory

 

DefaultSqlSessionFactory

DefaultSqlSessionFactory类中重载了多个创建SqlSession的openSession方法,首先获取环境配置,然后通过TransactionFactory获得一个Transaction对象,再通过Transaction对象获取Executor对象,最后才通过Configuration、Executor、是否autoCommit这三个参数来创建SqlSession。实际上,SqlSession的执行是委托给对应的Executor进行的。

    1. 比较

 

    1. 简单工厂

 

      1. 类图

设计模式(上)

 

  1. 抽象工厂模式

定义:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类

    1. 类图

 

  1. 命令模式

定义:

适用场景:

    1. 类图

 

    1. 精要

 

    1. 典型应用

 

 

命令模式有一个命令接口,每个命令有一个实现类,实现了命令接口,并持有厂商类的实例。eg:遥控器有一个开灯的按钮和一个关灯的按钮,则有一个命令接口,一个onLightCommand命令类,一个offLightCommand命令类,2个类均有Light这个类的实例引用,在2个命令类的execute()实现中分别调Light的on()和off()。RemoteControl类则对应了遥控器,其构造方法中初始化了各个按钮,即事实上遥控器定了的时候,各个按钮的功能已经定了,即其对应的命令类已经定了。若遥控器需要扩展,即命令类需要扩展。

 

 

  1. 装饰者模式

定义:

适用场景:

 

    1. 类图:设计模式(上)

 

    1. 精要

包装类与被包装类直接或间接继承自相同的抽象类,可能继承树中间还有其它抽象或非抽象类,包装类中定义了

      1. 设计原则

 

      1. 适用场景

个人理解:小功能较多,变化较频繁,经常需要添加小功能的场景。对原来的功能进行扩展和增强,但不改变本质,则适合使用装饰者模式。

    1. 典型应用:

I/O中的BufferedReader和BufferedWriter分别是Reader和Writer的装饰类

 

 

装饰模式,装饰模式没有通过继承原有类来扩展功能,但却达到了一样的目的,而且比继承更加灵活,所以可以说装饰模式是继承关系的一种替代方案。

 

GoF设计模式中,结构型模式有:

1.适配器模式 Adapter

  适配器模式是将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

  两个成熟的类需要通信,但是接口不同,由于开闭原则,我们不能去修改这两个类的接口,所以就需要一个适配器来完成衔接过程。

2.桥接模式 Bridge

  桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。它很好的支持了开闭原则和组合锯和复用原则。实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这些多角度分离出来让他们独立变化,减少他们之间的耦合。

3.组合模式 Composite

  组合模式将对象组合成树形结构以表示部分-整体的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。

4.装饰模式 Decorator

装饰模式动态地给一个对象添加一些额外的职责,就增加功能来说,它比生成子类更灵活。也可以这样说,装饰模式把复杂类中的核心职责和装饰功能区分开了,这样既简化了复杂类,有去除了相关类中重复的装饰逻辑。 装饰模式没有通过继承原有类来扩展功能,但却达到了一样的目的,而且比继承更加灵活,所以可以说装饰模式是继承关系的一种替代方案。

5.外观模式 Facade

 外观模式为子系统中的一组接口提供了同意的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

外观模式中,客户对各个具体的子系统是不了解的,所以对这些子系统进行了封装,对外只提供了用户所明白的单一而简单的接口,用户直接使用这个接口就可以完成操作,而不用去理睬具体的过程,而且子系统的变化不会影响到用户,这样就做到了信息隐蔽。

6.享元模式 Flyweight

 享元模式为运用共享技术有效的支持大量细粒度的对象。因为它可以通过共享大幅度地减少单个实例的数目,避免了大量非常相似类的开销。.

      享元模式是一个类别的多个对象共享这个类别的一个对象,而不是各自再实例化各自的对象。这样就达到了节省内存的目的。

7.代理模式 Proxy   

为其他对象提供一种代理,并由代理对象控制对原对象的引用,以间接控制对原对象的访问。

      1. MyBatis中的装饰者模式

org.apache.ibatis.cache.Cache作为接口,被PerpetualCache实现,而PerpetualCache有一系列装饰者,全在org.apache.ibatis.cache.decorators中

1.FifoCache:先进先出算法,缓存回收策略

2.LoggingCache :输出缓存命中的日志信息

3.LruCache:最近最少使用算法,缓存回收策略

4.ScheduledCache:调度缓存,负责定时清空缓存

5.SerializedCache:缓存序列化和反序列化存储

6.SoftCache:基于软引用实现的缓存管理策略

7.SynchronizedCache:同步的缓存装饰器,用于防止多线程并发访问

8.WeakCache:基于弱引用实现的缓存管理策略

9.TransactionalCache:事务性的缓存

一级缓存:即本地缓存,PerpetualCache类型的缓存,保存在BaseExecutor中,而执行器在SelSession(DefaultSqlSession)中,因此一级缓存的生命周期与SqlSession一致。

二级缓存:即自定义缓存,实现了Cache接口的类都可以作为二级缓存,可配置如encache等的第三方缓存。二级缓存以namespace为唯一标识,保存在Configuration核心配置对象中。二级缓存对象默认类型为PerpetualCache,若配置的默认类型,MyBatis根据配置自动追加装饰器。

Cache的引用顺序SynchronizedCache->LoggingCache->SerializedCache->ScheduledCache->LruCache->PerpetualCache

    1. 比较

装饰者模式和代理模式都是对功能进行增强,有什么区别或者说各自的适用场景呢?

个人理解:装饰者模式往往是添加小功能,可以添加多个小功能,但是代理模式就不适合增强多个功能,如果通过多层代理的话,将会变得很复杂。

  1. 适配器模式

定义:

适用场景:

 

对象适配器和类适配器适用2种不同的适配方法(分别是组合与继承)。类适配器适合允许多重继承的语言。

    1. 类图

 

    1. 精要

 

    1. 典型应用

在老的应用中使用到的枚举,在新的设计中使用Iterator接口代替,但是Iterator接口设计了remove方法,而枚举没有这个方法,所以在Iterator接口的remove()实现是抛出一个UnsupportedOperationException。用适配器模式来实现枚举适配Iterator,也可以这么设计。

MyBatis中的适配器模式

Mybatis提供了Log接口,同时提供了许多日志框架的实现

设计模式(上)

 

如Log4jImpl的实现,持有了org.apache.log4j.Logger的实例,然后所有的日志方法,均委托该实例来实现

    1. 比较

装饰者目的:不改变接口,但加入责任,即扩展被包装对象的行为或责任

适配器目的:将一个接口转成另一个接口

外观目的:让接口更简单