设计模式之美

353 阅读9分钟

创建型:单例模式、原型模式、工厂模式

结构型:代理模式、装饰器模式、适配器模式、门面模式

行为型:观察者模式、模板模式、策略模式、职责链模式

职责链模式

概念:责任链就是一个记录了链表头、链表尾的链表。记录表头是为了方便消息入队使用,记录表尾是为了方便添加处理器。

作用:复用和扩展,我们可以利用职责链来提供框架的扩展点,让使用者在不修改框架源码的情况下,基于扩展到定制自己需要的功能。它是将消息的发送与接收解耦,让职责链上的各个节点都有机会处理这个消息。

使用方式:

①使用链表,能够就处理请求就直接拦截处理并返回,不能就往下传交给后面的处理器来处理;使用数组,每个请求都会被所有的处理器都处理一遍。

②使用模板方法模式,将消息在职责链上移动和对消息的业务逻辑处理分离开来。将调用下一个职责链节点的逻辑放到抽象父类中,具体的处理器类实现自己的doHandle()方法。 (socket项目运用责任链模式实现了单聊和群聊功能。)

适配器模式

适配器模式就是用来做适配的,它相当于是将原本两个不能兼容的接口转化为一个可以兼容的接口,那这个接口的实现类也可以同时适配多个原本不兼容的接口方法。

应用场景:

①对外部系统提供的接口进行二次封装,增强外部方法实现更好的设计(适配器实现重构接口继承外部接口,适配器相当于实现类,为重构接口提供实现方法)

②每个功能的实现可能需要根据选择不同而实现多个接口,那我们就可以提供一个适配器,为多个的接口提供了统一接口定义,根据不同条件自适配不同实现,本质上就是利用多态来实现代码复用逻辑。兼容老版本接口,避免直接删除同理。

具体场景:

①我的SocketChannelAdapter是为了适配不同的IoProvider

②Spring的Slf4j日志框架。它提供了统一的接口定义和针对不同日志框架的适配器,然后对不同日志框架的接口二次封装成了统一的Slf4j接口适配器。那我们只需要导入对于的SDK,Spring会判断JVM能够加载哪个日记技术的依赖,来判断能够使用哪个日志技术,进而实现切换日志技术。Slf4j还提供了反向适配器,即从Slf4j到其他日志框架的适配,那我们就可以从JCL→Slf4j→logback。(Spring中是优先使用log4j2日志技术,若没有才会使用Slf4j日志框架)

模板方法模式和回调

模板方法模式:把相同的业务逻辑抽离到父类中,对于不同的业务逻辑,通过抽象方法来强迫子类重写,实现了代码复用和基于框架模板的代码扩展(抽离出框架的扩展点供使用者重写)。

回调:A类预先把函数注册到B类中,然后A类调用B类的另一个函数,这个函数触发了A类的回调函数,把执行结果返回给A类,这就是回调。也就是说,回调也是复用B类的代码逻辑来触发定制化的回调函数。

模板方法模式基于继承,回调基于组合。而Java是只支持单继承的,若把所有模板方法都放入父类中,子类用不到的抽象方法也需要重写。所以回调更加灵活方便、 (ConnectorHandlerChain类就是模板方法模式)

单例模式:

单例模式就是保证一个类仅创建一个实例,并提供一个他的全局访问点。

饿汉式单例:在类加载阶段就在堆中开辟一块内存,实例化单例对象并放入内存中,这样可以保证在多线程情况下的实例唯一性。它在初始化时便加载完成,在运行时直接获取,性能非常高;但是这些单例对象从类加载阶段开始就一直占用堆内存,如果一直不用就会浪费空间。

懒汉式单例:它使用懒加载方式,只有当系统使用到该类对象时,才会将实例加载到内存中。如果仅用if判断,不能保证线程安全,所以我们使用synchronized锁来修饰方法。但加上同步锁会产生所竞争,带来系统性能开销,所以我们在没有加同步锁之前就进行一次if判断,避免锁被占用时的锁竞争开销,这样可以减少同步锁的资源竞争。这样的Double-Check双重检测,即使在多线程情况下如果多个线程同时都进入到了第一个if判断条件里,他们也只能创建一个实例。但是,还会有指令重排序问题,如果在给对象分配内存空间并赋给成员变量instance,但对象还没有执行构造函数的代码逻辑,那此时另一个线程刚好分配到CPU,执行判断对象不为空,于是返回使用。但此时成员变量还没有初始化,就可能会导致异常。所以我们需要使用volatile关键字修饰对象,防止局部指令重排序。

    public class Singleton{
        public volatile static A instance = null;
        public A getInstance(){
            if(instance==null){
                synchronized(instance){
                    if(instance==null) instance = new A();
                }
            }
            return instance;
        }
    }
    
    public class LazySingleton{
        private static LazySingleton ls = null;
        public synchronized static LazySingleton getInstance(){
            if(lazySingleton==null){
                lazySingleton = new LazySingleton();
            }
        }
    }
    
    public class ehanshiSingleton{
        private static ESingleton es = new Singleton();
        
        public static ESingleton getES(){
            return es;
        }
    }
    
    可以使用枚举来防止反射

当然了,我们直接使用静态内部类来完成成员变量的初始化,这个静态内部类只会被加载一次,那么我们把单例对象的实例化过程放入静态内部类的静态变量初始化中,便完成了单例唯一。

(补充:保证单例的核心,就是通过外部共享存储区保证。上面所说的都是在单个进程中保证对象唯一,而进程与进程之间独立,各自占用不同的地址空间,继而保证了进程内的对象唯一性。如果想要实现线程中的对象唯一,就要使用线程的外部共享存储器ThreadLocal了。而要保证进程间的对象唯一性,就需要文件、磁盘等外部共享存储区了。)

枚举防止反射破坏单例模式

枚举也是Java类,我们对枚举类反编译后可以看到枚举类实际上是一个抽象类,无法被外部实例化。在反射newInstance创建枚举类的对象时,会检查该类是否被ENUM修饰,若是则会抛出异常

而且枚举类的构造方法是在static块中,就保证了JVM类加载过程中的线程安全。

观察者模式:

也叫发布订阅模式,我们可以在对象之间定义一个一对多的依赖,当这一个对象状态改变时,其他所有的依赖对象都会收到通知,实现了存在依赖关系的对象间的解耦。而如果仅仅是观察者阻塞等待通知,属于同步阻塞的实现方式,效果并不是很好,所以我们可以使用异步非阻塞的实现方式,来减少响应的时间:我们可以把需要通知的事件放入异步线程池中,通过异步线程来通知观察者,那么观察者就无需阻塞等待通知了

策略模式

策略模式实际上把对象的创建和使用解耦。策略模式会包含一组策略,通常是通过类型来判断使用哪个策略,那么我们可以把根据类型创建策略的逻辑抽象出来,放到工厂类或者Map中。在使用时我们无需关注各种策略,只需要传入响应的类型,策略工厂就会自动为我们创建对象(策略Map自动返回对应的函数式接口),执行响应业务逻辑。


装饰器模式

装饰器模式主要是用来给原始类添加增强功能的,它通过组合来代替多重继承,解决了继承关系过于复杂的问题。

工厂模式:

简单工厂模式:将用于创建对象且功能独立的代码块抽离出来封装成函数,然后再剥离到一个独立的类中,让这个类只负责对象的创建,那这个用于创建对象的类就是简单工厂模式类。简单工厂模式就是为了让类的职责更加单一,功能更加清晰。然而对于一个对象的创建可能会牵涉到多个if,如果我们想要把简单工厂中的if去掉的话,就要用到工厂方法模式了。

工厂方法模式:我们在接口中维护一个创建对象的接口方法,然后利用多态的性质,让每个实现类自实现此方法中的对象创建。这就相当于把一个简单工厂中的复杂条件(多个if)拆解到一个个的子类工厂中。但这样显然有问题,因为这样就又造成了创建对象与子类工厂的强耦合,和直接创建对象好像没啥区别,只是把if条件封装起来了。所以我们还可以为这些子类工厂再创建一个简单工厂,也就是用于创建工厂的工厂,这里可以结合策略模式,使用一个Map存储,key为if条件,value为工厂子类。所以我们在扩展工厂时,就直接往简单工厂的Map中添加kv键值对即可。

抽象工厂模式:也就是将工厂方法模式中抽象出的接口进行组合,把部分有关联的对象创建逻辑放在一个接口中,使得一个工厂可以负责创建多个不同类型的对象,避免创建太多工厂类。

Spring中的设计模式

beanFactory工厂模式、HandlerAdapter适配器模式、bean单例模式、AOP代理模式、Template模板方法模式、ApplicationListener观察者模式。

JDK中的设计模式

工厂模式:Integer.valueOf()、Class.forName(gzff/name)、java.sql/Connection可以创建不同的Statement;

原型模式:深拷贝、浅拷贝

适配器模式:InputStreamReader(InputStream)转换流。

装饰器模式:Collections.synchronizedList(list)

代理模式:动态代理

策略模式:ThreadPoolExecutor的四种拒绝策略