⏰⏰⏰⏰⏰⏰设计模式

1 六项设计原则

  • 1、开闭原则(Open Close Principle)
    开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

  • 2、里氏代换原则(Liskov Substitution Principle)
    里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

  • 3、依赖倒转原则(Dependence Inversion Principle)
    这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。

  • 4、接口隔离原则(Interface Segregation Principle)
    这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。

  • 5、迪米特法则(最少知道原则)(Demeter Principle)
    为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

  • 6、合成复用原则(Composite Reuse Principle)
    原则是尽量使用合成/聚合的方式,而不是使用继承

2 设计模式

菜鸟详解

1. 创建型模式

这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

  • 工厂模式(Factory Pattern):一个对象接口(汽车工厂提车)
  • 抽象工厂模式(Abstract Factory Pattern):一系列对象接口(产品族中包含产品族)
  • 单例模式(Singleton Pattern):仅有一个实例(多线程操作同一个文件、singleton实现)
  • 建造者模式(Builder Pattern):一步一步按顺序构造最终的对象(“肯德基套餐”、StringBuilder)
  • 原型模式(Prototype Pattern):通过拷贝一个现有对象生成新对象(细胞分裂、Object clone()方法)

2. 结构型模式

这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

  • 适配器模式(Adapter Pattern):两个不兼容的接口之间的桥梁(电压转换、 LINUX 运行 WINDOWS 程序、jdbc)
  • 桥接模式(Bridge Pattern):抽象化与实现化解耦(灵魂与肉体、开关与功能)
  • 过滤器模式(Filter、Criteria Pattern):过滤特定对象(字符集过滤器)
  • 组合模式(Composite Pattern):一组相似的对象当作一个单一的对象(算术表达式=操作数+操作符+另一个操作数(操作数+操作符+另一个操作数))
  • 装饰器模式(Decorator Pattern):现有的对象添加新的功能,不改变其结构(画+玻璃+画框)
  • 外观模式(Facade Pattern):隐藏系统的复杂性,提供可访问系统的接口(JAVA 的三层开发模式)
  • 享元模式(Flyweight Pattern):共享技术减少创建对象的数量(常量池、数据库池)
  • 代理模式(Proxy Pattern):一个类代表另一个类的功能(Spring的AOP、快捷方式)

3. 行为型模式

这些设计模式特别关注对象之间的通信。

  • 责任链模式(Chain of Responsibility Pattern):为请求创建了一个接收者对象的链(WEB中encoding处理、拦截器、过滤器)
  • 命令模式(Command Pattern):以命令的形式包裹在对象中(GUI 中每一个按钮都是一条命令、模拟 CMD、封装sql)
  • 解释器模式(Interpreter Pattern):评估语言的语法或表达式的方式(编译器、运算表达式计算)
  • 迭代器模式(Iterator Pattern):顺序访问一个聚合对象中各个元素(iterator)
  • 中介者模式(Mediator Pattern):用一个中介对象来封装一系列的对象交互(MVC中的C)
  • 备忘录模式(Memento Pattern):不破坏对象封装的前提下,捕获内部状态,并在该对象之外保存(存档、ctri + z、后退、事务管理)
  • 观察者模式(Observer Pattern):一种一对多的依赖关系中一个对象的状态发生改变时,所有依赖于它的对象被通知和自动更新(拍卖师观察最高标价,然后通知给其他竞价者竞价)
  • 状态模式(State Pattern):类的行为是基于它的状态改变的(运动员状态与行为、不同身份不同行为)
  • 空对象模式(Null Object Pattern):一个空对象取代 NULL 对象实例的检查
  • 策略模式(Strategy Pattern):一个类的行为或其算法可以在运行时更改(不同出行方式、锦囊妙计)
  • 模板模式(Template Pattern):一个抽象类公开定义了执行它的方法的方式/模板(抽象子类的公用方法、Spring中事务Session中的方法)
  • 访问者模式(Visitor Pattern):对一个对象结构中的对象进行很多不同的并且不相关的操作,避免这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类(UI、拦截器与过滤器)

4. J2EE 模式

这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。

  • MVC 模式(MVC Pattern): Model-View-Controller(模型-视图-控制器) 模式。用于应用程序的分层开发。

  • 业务代表模式(Business Delegate Pattern):减少通信或对表示层代码中的业务层代码的远程查询功能。(客户端(Client)、业务代表(Business Delegate)、查询服务(LookUp Service)、业务服务(Business Service))

  • 组合实体模式(Composite Entity Pattern):一个组合实体是一个 EJB 实体 bean,代表了对象的图解(组合实体(Composite Entity)、粗粒度对象(Coarse-Grained Object)、依赖对象(Dependent Object)、策略(Strategies))

  • 数据访问对象模式(Data Access Object Pattern):数据访问对象模式(Data Access Object Pattern)或 DAO 模式用于把低级的数据访问 API 从高级的业务服务中分离出来(数据访问对象接口(Data Access Object Interface)、数据访问对象实体类(Data Access Object concrete class)、模型对象/数值对象(Model Object/Value Object))

  • 前端控制器模式(Front Controller Pattern):集中的请求处理机制,所有的请求都将由单一的处理程序处理。(前端控制器(Front Controller)、调度器(Dispatcher)、视图(View) )

  • 拦截过滤器模式(Intercepting Filter Pattern):对应用程序的请求或响应做一些预处理/后处理(过滤器(Filter)、过滤器链(Filter Chain)、Target、过滤管理器(Filter Manager)、客户端(Client))

  • 服务定位器模式(Service Locator Pattern):JNDI 查询定位各种服务(服务(Service)、Context / 初始的 Context 、服务定位器(Service Locator)、缓存(Cache) 、客户端(Client))

  • 传输对象模式(Transfer Object Pattern):从客户端向服务器一次性传递带有多个属性的数据(业务对象(Business Object)、传输对象(Transfer Object)、客户端(Client))

3 Spring中涉及的设计模式:

详情

  • 简单工厂(非23种设计模式中的一种):Spring中的BeanFactory
  • 工厂方法:实现了FactoryBean接口的bean是一类叫做factory的bean
  • 单例模式:Spring中依赖注入的Bean实例默认是单例的。
  • 适配器模式:SpringMVC中的适配器HandlerAdatper。
  • 装饰器模式:Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。
  • 代理模式:AOP底层,就是动态代理模式的实现。
  • 观察者模式:listener的实现。具体实现:事件源、事件、事件监听器。
  • 策略模式:Spring框架的资源访问Resource接口。
  • 模版方法模式:是模板方法模式和回调模式的结合,父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现。

4 JDK涉及的设计模式

  • Singleton(单例):保证类只有一个实例;提供一个全局访问点
    (1)Runtime
    (2)NumberFormat

  • Factory(静态工厂):(1)代替构造函数创建对象(2)方法名比构造函数清晰
    (1)Integer.valueOf
    (2)Class.forName

  • Factory Method(工厂方法):子类决定哪一个类实例化
    Collection.iterator方法

  • Abstract Factory(抽象工厂):创建某一种类的对象
    (1)java.sql包
    (2)UIManager(swing外观)

  • Builder(构造者):(1)将构造逻辑提到单独的类中(2)分离类的构造逻辑和表现
    DocumentBuilder(org.w3c.dom)
    类图:

  • Prototype(原型):(1)复制对象(2)浅复制、深复制
    Object.clone;Cloneable

  • Adapter(适配器):使不兼容的接口相容
    (1)java.io.InputStreamReader(InputStream)
    (2)java.io.OutputStreamWriter(OutputStream)

  • Bridge(桥接):将抽象部分与其实现部分分离,使它们都可以独立地变化
    java.util.logging中的Handler和Formatter

  • Composite(组合):一致地对待组合对象和独立对象
    (1)org.w3c.dom
    (2)javax.swing.JComponent#add(Component)

  • Builder(装饰器):为类添加新的功能;防止类继承带来的爆炸式增长
    (1)java.io包
    (2)java.util.Collections#synchronizedList(List)

  • Façade(外观):(1)封装一组交互类,一致地对外提供接口(2)封装子系统,简化子系统调用
    java.util.logging包

  • Flyweight(享元):共享对象,节省内存
    (1)Integer.valueOf(int i);Character.valueOf(char c)
    (2)String常量池

  • Proxy(代理):(1)透明调用被代理对象,无须知道复杂实现细节(2)增加被代理类的功能
    动态代理;RMI

  • Iterator(迭代器):将集合的迭代和集合本身分离
    Iterator、Enumeration接口

  • Observer(观察者):通知对象状态改变
    (1)java.util.Observer,Observable
    (2)Swing中的Listener

  • Mediator(协调者):用于协调多个类的操作
    Swing的ButtonGroup

  • Template method(模板方法):定义算法的结构,子类只实现不同的部分
    ThreadPoolExecutor.Worker

  • Strategy(策略):提供不同的算法
    ThreadPoolExecutor中的四种拒绝策略

  • Chain of Responsibility(责任链):请求会被链上的对象处理,但是客户端不知道请求会被哪些对象处理
    (1)java.util.logging.Logger会将log委托给parent logger
    (2)ClassLoader的委托模型

  • Command(命令):(1)封装操作,使接口一致(2)将调用者和接收者在空间和时间上解耦合
    Runnable;Callable;ThreadPoolExecutor

  • Null Object(空对象):不需每次判空,对待空值,如同对待一个相同接口的对象
    Collections.EMPTY_LIST

  • State(状态):将主对象和其状态分离,状态对象负责主对象的状态转换,使主对象类功能减轻
    未发现

  • Visitor(访问者):异构的类间添加聚合操作;搜集聚合数据
    未发现

  • Interpreter(解释器):用一组类代表某一规则
    java.util.regex.Pattern

  • Memento(备忘录):保持对象状态,需要时可恢复
    未发现

5 各种工厂模式

  • 简单工厂模式

    1. 虽然不是真正的设计模式,但仍不失为一个简单的方法,可以将客户端程序从具体类解耦。
    2. 工厂类中实现方法。
    3. 每次要新增创建的对象时必须去修改工厂类本身,这意味着这个工厂类很容易膨胀,同时这违反了开放封闭原则
  • 静态工厂模式

    1. 静态方法定义一个“简单工厂”。
  • 工厂方法模式

    1. 工厂模式,把对象的创建委托给子类,子类实现工厂方法来创建对象。
    2. 实例化的过程下沉到具体子工厂,需要新增创建的对象时,就新增一个对应的工厂。
  • 抽象工厂模式

    1. 抽象工厂模式提供一个接口(Interface), 用于创建具体或依赖对象的家族, 而不需要明确指定具体类。
    2. 从抽象工厂中派生出具体工厂, 这些工厂生产相同的产品, 但是产品的实现不同。
    3. 抽象工厂的方法经常以工厂方法的方式实现。

6 JAVA单例实现的方式

一般来说,单例模式有五种写法:懒汉、饿汉、双重检验锁、静态内部类、枚举。上述所说都是线程安全的实现,文章的第一种方法不算正确的写法。

1懒汉式,线程不安全(static )
当被问到要实现一个单例模式时。

public class Singleton {
    private static Singleton instance;
    private Singleton (){}

    public static Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
    }
}

懒加载模式,问题:当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。

2懒汉式,线程安全(static +synchronized方法)

多个实例问题
——》》将整个 getInstance() 方法设为同步(synchronized)。

public static synchronized Singleton getInstance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}

问题:它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时(其他调用用第一次创建的实例就行)。
——》》这就引出了双重检验锁

3双重检验锁(两次检查null中间同步块加锁)

双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。

public static Singleton getSingleton() {
    if (instance == null) {                         //Single Checked
        synchronized (Singleton.class) {
            if (instance == null) {                 //Double Checked
                instance = new Singleton();
            }
        }
    }
    return instance ;
}

问题:在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

  1. 给 instance 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量
  3. 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(有实例,但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

——》》将 instance 变量声明成 volatile 就可以了。

public class Singleton {
    private volatile static Singleton instance; //声明成 volatile
    private Singleton (){}

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

不是因为 volatile 的可见性。使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。

但是特别注意在 Java 5 以前的版本使用了 volatile 的双检锁还是有问题的。其原因是 Java 5 以前的 JMM (Java
内存模型)是存在缺陷的,即时将变量声明成 volatile 也不能完全避免重排序,主要是 volatile变量前后的代码仍然存在重排序问题。这个 volatile 屏蔽重排序的问题在 Java 5 中才得以修复,所以在这之后才可以放心使用volatile。

——》》更好的实现线程安全的单例模式的办法。

4饿汉式 static final field

这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

public class Singleton{
    //类加载时就初始化
    private static final Singleton instance = new Singleton();
    
    private Singleton(){}

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

问题:它不是一种懒加载模式(lazy initialization),单例会在加载类一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

——》》更好的写法

5静态内部类(private static class)⭐

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

——》》更好写法

6枚举 Enum⭐
用枚举写单例实在太简单了!这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法。

public enum EasySingleton{
    INSTANCE;
}

我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,可能是因为不太熟悉吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zkFun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值