很久没有这么大块的时间去重温设计模式了,这正好整理一下;
本篇博文包含基础设计模式的概念,以及相似设计模式之间的区别,以便于理解。
后续会持续更新
(理解是啥就行)
单一职责原则:一个类应该只有一个引起变化的原因、
里氏替换原则:任何使用父类对象地方都可以用其子类对象无缝替换、
依赖倒置原则:依赖于接口,而非实现编程、
接口隔离原则:最小接口原则、
迪米特法则:一个类应该于尽可能少的类打交道,减少系统复杂度、
开闭原则:对扩展开放,对修改封闭
定义算法族(以组合方式依赖于一个接口),分别封装起来(依赖于接口编程),让他们之间可以相互替换(使用set方法替换接口指向的算法对象),让算法的变化独立于使用算法的客户;
封装变化
定义了对象之间一对多依赖关系,当一对象改变时,它所有的依赖者都会接到通知并自动更新。
根据依赖倒置原则,所有依赖者实现共同接口(观察者),接口中定义被观察者改变时的更新操作,被观察者依赖于观察者共同的接口;
被动接受与主动获取:被观察改变时,只通知所有观察它的状态改变,而非直接推送数据,观察者依赖被观察者,可在观察者接口中主动获取被观察者的数据;
例:Spring框架自带监听器
定义一个新的接口实现类,以组合的方式动态扩展原有实现类的功能(二者实现同一接口)。
开闭原则:对扩展开发,对修改关闭,(设计程序是,应该允许行为被扩展,而无需修改原有代码)
java单继承,避免继承是保证java代码更具扩展性的条件之一
与继承相比,组合方式更具有弹性,可以在运行时动态的加入新的行为;
装饰模式使用过程中会有许多小的对象,装载麻烦;
例:java io包;
确保一个类只有一个实例,并提供全局访问点;
主要思路是:构造函数私有化,静态方法获取实例;
饿汉式、懒汉式、同步方法式懒汉(避免多线程问题);
双重检查式:只需要在第一次获取实例时才需要使用同步(需要使用volatile修饰引用,以便多线程环境下每次使用该对象都去内存中取最新的值,用完后也将最新值写会内存(而不是缓存);
静态类部类:在单例类中定义静态类部类(只在第一次调用时才会装载,因此静态类部类中使用饿汉式初始化);
构造方法私有化并不能避免使用反射来初始化一个新的对象;
枚举:既避免反射 又避免反序列化
反射:在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败
反序列化:在反序列化枚举常量时,readObject方法会根据类信息获取枚举常量列表中已经存在的枚举对象
枚举实现代码如下:
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
简单工厂、工厂方法、抽象工厂
封装创建对象的代码,使客户端与具体的实现类解耦,客户端只需面向接口编程,而不知道运行时到底是哪个对象在执行(依赖于接口,而非具体类);
依赖倒置原则:客户端只需要依赖于工厂及产品接口,而无需依赖于众多的产品实现类;
类的实例化是无法避免的,工厂的作用是将所有实例化对象的代码集中在一起,避免重复,也便于维护;
工厂方法模式则将要实例化的类推迟到工厂接口的子类,创建者并不知道要创建的具体产品是什么,在新增产品实现时也无需修改创建者的代码;
抽象工厂模式:提供一个接口,用于创建相关对象的家族,具体创建由其实现完成(与具体产品类耦合);
区别于工厂方法模式的是,抽象工厂的作用是创建并组合一组对象;
例:Spring的 beanFactory
封装复杂产品的构造过程,并允许客户端按步骤构建;对客户端隐藏产品内部实现,经常用于创建组合结构;
与工厂模式的区别是工厂模式只有一个步骤,而生成器模式允许客户端通过多个步骤创建需要对象,并可以自由改变步骤;
例:Spring中ApplicationContext的装载
将请求封装成对象(只暴露出execute方法,execute方法会调用receiver的动作),调用该对象的execute方法执行receiver的方法;
目的:实现请求调用者和请求接收者之间的解耦(无论接收者如何复杂,都由命令对象一个一个封装),请求调用者无需关心该命令怎么执行,只需知道执行命令对象的execute方法就能实现该命令对象代表的动作;
用途:
将一个类的接口,转换为客户期望的另一个接口,让原本无法协作不兼容的类可以合作无间;
比如让火鸡伪装成鸭子;
与装饰模式的区别:
都是对原有的类进行包装,但意图不一样,
装饰模式用于在原来基础上新增功能而不改变原有接口,而适配器模式是将原来的接口改造为另一个接口;
提供一个统一的接口,用来访问子系统中的一群接口,即定义了一个更高层的接口,让子系统更容易使用;
迪米特法则:最少知识原则,不要让太多类耦合在一起,避免修改系统中一部分时影响到其他部分;
需要使用一个现有类而这个类并不完全符合要求时使用适配器模式;
当需要简化一个很大子系统时使用外观模式;
当需要给一个类添加功能而不改变原有接口时使用装饰模式;
2019-08-16更新
定义一个算法骨架,将其中某些步骤延迟到子类实现,使子类可以在不改变算法骨架的情况下,重新定义算法中的某些步骤;
挂钩:抽象父类中定义的默认方法,可以不做任何事或者做默认的事,子类可选择去覆盖它,以改变算法骨架的流程走向;
例:源码中到处都是
为一个对象提供一个替身或者占位符以访问这个对象,结构上类似于装饰模式,但意图不同:装饰模式添加行为,代理模式控制访问;
迭代器模式:提供一种顺序访问集合元素又不暴露其内部结构的方法;
组合模式:允许用户将对象组成树形结构来表示整体与部分的层次,能让客户以一致的方式处理个别对象和对象的组合;
组合对象同样需要实现个别对象的接口,并耦合迭代器模式遍历组合中的对象;
例:java集合
允许一个对象基于内部状态而有不同的行为,该对象将行为委托给当前状态对象,因此状态模式的实现方式为:定义每个状态为一个单独的类,类中实现每个可导致状态变化的方法;
状态模式与策略模式拥有相同的类图,但意图不同,状态模式允许状态的改变来改变行为,因为行为本身就是委托给状态对象的,而策略模式则是用算法来配置对象本身;
当需要让一个以上的对象可以处理同一个请求时,使用责任链;
将请求的发送者和接受者解耦,发送者只需将请求发送到链上,具体由那些人处理发送者并不需要关心;
可以通过改变链内的成员位置,并允许新增和删除成员;
例:Tomcat的容器管道与过滤器
在某种情景下,针对某一类设计问题的通用解决方案;
设计模式并非僵化的教条,只是一种解决问题的指导方案,因此现实中设计模式通常会有许多变(如排序工具类中动态传入的Compare对象,没有使用继承,但它属于模板方法模式,而非策略模式,因为排序的算法模板已经写好了,动态传入的Compare对象只作为比较依据);
设计原则是让程序保持简单,应该自然而然地出现在设计中,而非为了使用而使用;