是设计模式,我们有救了!!!(一)

204 阅读7分钟

highlight: a11y-dark

别怕,我会一直陪着你的。。。

github.com/YaYiXiBa/Ja… 欢迎去看我的代码哦。。。

分类​​模式名称​​定义​​典型应用场景​
​创建型模式​单例 (Singleton)确保类只有一个实例,并提供全局访问点数据库连接池、日志处理器
工厂方法 (Factory Method)定义创建对象的接口,让子类决定实例化哪个类跨平台UI组件、多数据库驱动
抽象工厂 (Abstract Factory)创建相关或依赖对象的家族,无需指定具体类跨操作系统UI套件
建造者 (Builder)分步骤构建复杂对象,分离构造与表示HTTP请求构造器、复杂配置对象
原型 (Prototype)通过克隆现有对象创建新对象游戏角色复制、高性能对象创建
​结构型模式​适配器 (Adapter)转换接口使不兼容的类能协同工作旧系统接口改造、第三方库适配
桥接 (Bridge)将抽象与实现分离,使二者可独立变化多维度扩展系统(如形状+渲染方式)
组合 (Composite)将对象组合成树形结构以表示"部分-整体"层次文件系统、GUI组件树
装饰器 (Decorator)动态地给对象添加额外职责数据流加密/压缩、中间件栈
外观 (Facade)为子系统提供统一的高层接口简化复杂API调用
享元 (Flyweight)通过共享技术有效支持大量细粒度对象文本编辑器字符对象、棋牌游戏棋子
代理 (Proxy)为其他对象提供代理以控制访问图片懒加载、远程方法调用
​行为型模式​责任链 (Chain of Responsibility)将请求沿处理链传递,直到有对象处理它审批流程、异常处理链
命令 (Command)将请求封装为对象,支持参数化、队列化等操作撤销/重做、宏命令
解释器 (Interpreter)定义语言的文法表示,并解释执行SQL解析、正则表达式引擎
迭代器 (Iterator)提供顺序访问聚合对象元素的方法自定义集合遍历
中介者 (Mediator)通过中介对象封装一组对象交互聊天室消息调度、GUI组件通信
备忘录 (Memento)捕获对象内部状态并在需要时恢复游戏存档、事务回滚
观察者 (Observer)定义对象间一对多的依赖关系,状态变化时自动通知事件通知系统、数据绑定
状态 (State)允许对象在其内部状态改变时改变行为订单状态机、游戏角色行为切换
策略 (Strategy)定义算法家族并使其可互换支付方式选择、排序算法切换
模板方法 (Template Method)定义算法骨架,将某些步骤延迟到子类实现框架钩子设计、标准化流程
访问者 (Visitor)将算法与对象结构分离,在不修改结构的前提下添加新操作抽象语法树处理、复杂对象结构统计

一:单例模式

单例模式其实十分常见,但是单独去学习的时候会发现有好多好多种,不要死记硬背,从最简单的开始,慢慢扩展。

饿汉式懒汉式老搞混?铁汁你记住,懒汉就是懒加载,另外一个就是饿汉。。。

饿汉式

  • ​优点​​:简单、线程安全
  • ​缺点​​:可能造成资源浪费(即使不用也会加载实例)
public class HungrySingleton {
    /**
     * private:私有的属性
     * static:静态属性,作为类对象的成员,在加载式赋值。同时避免了线程安全,因为类只会被加载一次。
     * final:类的确是只会被加载一次,但是会不会被修改呢?final就强制要求不可以再对其修改,保证了单例不变。
     */
    private static final HungrySingleton singleton = new HungrySingleton();

    /**
     * 将构造方法定义为私有,使得类外无法再new出新的单例,只能在类加载的时候使用。
     */
    private HungrySingleton(){

    }

    /**
     * static:想一下我们现在提供的访问入口来自于谁?如果不加static是不是没有办法通过类名来访问?
     */
    public static HungrySingleton getSingle(){
        return singleton;
    }
    
}

如此,使用的时候就可以HungrySingleton.getSingle()了。好好看我写的注释啊。

懒汉式

饿汉式即使不用也会加载?那怎么办?我用的时候再加载不就好了。

class LazySingleton {
    /**
     * static:这次初始化的时候直接将其定义为null
     */
    private static LazySingleton instance; // 初始为null

    public static LazySingleton getInstance() {
        if (instance == null) { // 第一次调用时创建
            instance = new LazySingleton();
        }
        return instance;
    }
}

上述代码其实还有一个问题,如果同一时间段内getInstance被调用。两个线程一起到达了if (instance == null)。但是A线程先执行了instance = new LazySingleton();而B线程等A创建完之后也创建了一次,是不是会导致重复实例化?解决方式其实也很简单,限制一下只能有一个线程进入到 getInstance()方法不就可以了。

// 懒汉式(延迟初始化)
class LazySingleton {
    /**
     * static:这次初始化的时候直接将其定义为null
     */
    private static LazySingleton instance; // 初始为null

    public static synchronized LazySingleton getInstance() {
        if (instance == null) { // 第一次调用时创建
            instance = new LazySingleton();
        }
        return instance;
    }
}

ps:有没有发现懒汉式不加final修饰了,因为是延迟赋值啊,初始化的时候是null,调用getInstance的时候才进行赋值。并且,final的作用不就是为了防止被修改吗,我们的同步方法的getInstance其实已经做了这一判断。

优点:解决了饿汉式的可能造成资源浪费(即使不用也会加载实例)的问题。 缺点​​:每次调用都同步,性能差。

双重校验锁

不想每次调用都走同步方法。行!

synchronized又不是只能写在方法前面。。。

public class DCLSingleton {
    /**
     * volatile:值改变对于其他线程立即可变,替代了final
     */
    private static volatile DCLSingleton singleton;
    private DCLSingleton(){

    }
    public static DCLSingleton getSingleton(){
        if(singleton == null){//第一次判断
            synchronized (DCLSingleton.class){//同步上锁,锁class
                if(singleton == null){//第二次判断
                    singleton = new DCLSingleton();
                }
            }
        }
        return singleton;
    }
}

这样是不是就避免了?只在【if(singleton == null){//第一次判断】的时候走到同步方法里,但是呢,又因为“第一次判断”我们是没有上锁的有可能存在A线程执行完同步代码,同时B线程认为singleton == null的情况,接着走到同步代码里面。所以加上第二次判断。

登记式/静态内部类

这篇关于内部类的文章说的很好,不知道什么是内部类看下www.cnblogs.com/GrimMjx/p/1… 这个其实理解起来有点难度,谁研究的呢。。。

public class InnerClassSingleton {
    /**
     * 静态内部类持有外部类的实例
     */
    private static class Holder{
        /**
         * 
         * 切记:静态内部类只有在首次被主动引用时才会加载和初始化,也就是说在加载外部类的时候不会加载内部类,只有调用getSingle()的时候才触发
         * private static final:饿汉式的代码,类加载的时候直接指定single只不过将new Single改成了new InnerClassSingleton,内部类持有外部类的实例;
         */
        private static final InnerClassSingleton single = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
    }

    /**
     * 首次调用的时候会触发Holder类的加载
     * @return
     */
    public static InnerClassSingleton getSingle(){
        return Holder.single;
    }
}

这里实际上是利用了JVM的类加载机制,来保证Holder只会被加载一次,那么Holder的class对象也就只会存在一个只会有一个被持有的实例。既是线程安全的,又是懒加载,又不需要加额外的锁,利用的是JVM的机制。

枚举

public enum EnumSingle {
    INSTANCE("app-config.properties");

    private Properties config;

    private EnumSingle(String configPath) {
        // 加载配置
        config = new Properties();
        try (InputStream is = getClass().getClassLoader()
                .getResourceAsStream(configPath)) {
            config.load(is);
        } catch (IOException e) {
            throw new RuntimeException("加载配置失败", e);
        }
    }

    public String getProperty(String key) {
        return config.getProperty(key);
    }
}

这个方式其实就是利用了枚举本身就是单例的特性,绝对的线程安全,如果涉及到反序列化的话可以尝试使用。 一般情况下饿汉式就足够,如果要求懒加载再考虑懒汉式,如果又要求线程安全就再加锁。