常用设计模式与相关原则
仅个人笔记
设计原则
1. SRP原则 - 单一职责原则(Single Responsibility Principle)
应该有且只有一个原因引起类的变化。(这里的类不光指类,也适用于方法和接口,比如常说的一个方法实现一个功能)
2. LSP法则 - 里氏替换原则(Liskov Substitution Principle)
把抽象接口和实现分离,子类必须能替换掉父类,这个原则通常由语言保证。(子类必须完全实现父类的方法,如果子类无法完全实现父类的方法,则建议断开父子继承关系,采用依赖,聚集,组合等关系来代替)
3. DIP法则 - 依赖倒置原则(Dependence Inversion Principle)
- 抽象不应当依赖于细节细节应当依赖于抽象
- 要针对接口编程,不针对实现编程。具体讲就是要依赖于抽象,不要依赖于具体。
- 高层不直接依赖底层,而是高层定义自己需要底层提供什么样的接口,底层负责实现,这样就可以随意切换底层的具体实现而不用影响高层,但底层反而要依赖高层公布的接口,所以称为“依赖倒置”。
4. ISP原则 - 接口分离原则 (Interface Segregation Principle)
每一个接口应该是一种角色,不多不少,不干不该干的事,该干的事都要干。这类似编码原则中的最小权限法则。(当然,接口的设计粒度越小,系统越灵活,这是肯定的,但灵活的同时带来的问题是结构
复杂化,开发难度增加,可维护性降低,从而提高成本)
5. LKP原则 - 最少知识原则(Least Knowledge Principle)
又称迪米特法则。原则的核心思想即一个对象应对其他对象有尽可能少的了解。(目的是为了降低各个组件间的耦合)
6. OCP法则 - 开放-关闭原则(Open Close Principle)
一个类应当对扩展开放,对修改关闭。即当有新的需求时,不是修改已有的类,而是对已有的类进行扩展。(实现开闭原则的关键在于 分离不变和变化的部分,并对变化的部分进行合理的高层抽象,并让不变的部分依赖该高层抽象)
7. CARP原则 - 组合/聚合复用原则(Composition/Aggregation Reuse Principle)
尽量使用组合和聚合少使用继承的关系来达到复用的原则。
常用设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
设计模式分为三种类型,共23种。
-
创建型模式:对类的实例化过程的抽象。一些系统在创建对象时,需要动态地决定怎样创建对象,创建哪些对象,以及如何组合和表示这些对象。创建模式描述了怎样构造和封装这些动态的决定。包含类的创建模式和对象的创建模式。
-
结构型模式:描述如何将类或对象结合在一起形成更大的结构。分为类的结构模式和对象的结构模式。类的结构模式使用继承把类,接口等组合在一起,以形成更大的结构。类的结构模式是静态的。对象的结构模式描述怎样把各种不同类型的对象组合在一起,以实现新的功能的方法。对象的结构模式是动态的。
-
行为型模式:对在不同的对象之间划分责任和算法的抽象化。不仅仅是关于类和对象的,并是关于他们之间的相互作用。类的行为模式使用继承关系在几个类之间分配行为。对象的行为模式则使用对象的聚合来分配行为。
其中分别是:
- 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
- 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
- 行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式、访问者模式。
一、创建型模式
Factory method 工厂方法(工厂模式)
思想:一个类需要一个产品来完成某项工作,但它不能确定,也不关心具体拿到什么产品,因此它定义一个工厂方法,将具体产品的生产延迟到子类决定。
-
简单工厂模式(Simple Factory Pattern)
工厂模式的目的在于程序的可扩展性。而对于简单工厂模式来说,它是为了让程序有一个更好地封装,降低程序模块之间的耦合程度。
对于简单的工厂模式,其实也可以将其理解成为一个创建对象的工具类。
//例子 public class SimpleFactory { public Object create(Class<?> clazz) { if (clazz.getName().equals(Plane.class.getName())) { return createPlane(); } else if (clazz.getName().equals(Broom.class.getName())) { return createBroom(); } return null; } private Broom createBroom() { return new Broom(); } private Plane createPlane() { return new Plane(); } }
优点:简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。
缺点:很明显工厂类集中了所有实例的创建逻辑,容易违反责任分配原则 -
工厂方法模式(Factory Method Pattern)
可以一定程度上解耦,消费者和产品实现类隔离开,只依赖产品接口(抽象产品),产品实现类如何改动与消费者完全无关。
可以一定程度增加扩展性,若增加一个产品实现,只需要实现产品接口,修改工厂创建产品的方法,消费者可以无感知(若消费者不关心具体产品是什么的情况)。
可以一定程度增加代码的封装性、可读性。清楚的代码结构,对于消费者来说很少的代码量就可以完成很多工作。
工厂方法模式中把生成产品类的时间延迟,就是通过对应的工厂类来生成对应的产品类,在这里可以实现“开放-封闭”原则,无论加多少产品类,都不用修改原来类中的代码,而是通过增加工厂类来实现。
但是这还是有缺点的,如果产品类过多,就要生成很多的工厂类。
-
抽象工厂模式(Abstract Factory Pattern)
-
抽象工厂(Creator)角色
抽象工厂模式的核心,包含对多个产品结构的声明,任何工厂类都必须实现这个接口。
-
具体工厂( Concrete Creator)角色
具体工厂类是抽象工厂的一个实现,负责实例化某个产品族中的产品对象。
-
抽象(Product)角色
抽象模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
-
具体产品(Concrete Product)角色
抽象模式所创建的具体实例对象。
-
-
区别: 工厂方法模式: 一个抽象产品类,可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类只能创建一个具体产品类的实例。 抽象工厂模式: 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品。 区别: 工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。 工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。 工厂方法创建 "一种" 产品,他的着重点在于"怎么创建",也就是说如果你开发,你的大量代码很可能围绕着这种产品的构造,初始化这些细节上面。也因为如此,类似的产品之间有很多可以复用的特征,所以会和模版方法相随。 抽象工厂需要创建一些列产品,着重点在于"创建哪些"产品上,也就是说,如果你开发,你的主要任务是划分不同差异的产品线,并且尽量保持每条产品线接口一致,从而可以从同一个抽象工厂继承。
单例模式(Singleton)
单例模式是一种对象创建型模式,使用单例模式, 可以保证为一个类只生成唯一的实例对象。也就是说, 在整个程序空间中,该类只存在一个实例对象。
-
在应用系统开发中,我们常常有以下需求:
- 在多个线程之间,比如servlet环境,共享同一个资源或者操作同一个对象在整个程序空间使用全局变量,共享资源
- 大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。
-
这样的模式有几个好处:
- 某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
- 省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
- 有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
public class Singleton { /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */ private static Singleton instance = null; /* 私有构造方法,防止被实例化 */ private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ public Object readResolve() { return instance; } } //这样子看起来不错,但JVM的优化机制可能会提前分配空间 /*instance = new Singleton()其实可以分为下面的步骤: 1.申请一块内存空间; 2.在这块空间里实例化对象; 3.instance的引用指向这块空间地址; 在JVM的即时编译器中存在指令重排序的优化,也就是说上面的第二步和第三步的顺序是不能保证的。*/ //可以给instance添加volatile关键字来禁止指令重排 private static volatile SingletonDemo2 instance;
根据上述情况可以有下方几种写法: public class Singleton { private static volatile Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } public Object readResolve() { return instance; } } //-------------------------------------------- public class Singleton { private Singleton() {} /* 此处使用一个内部类来维护单例 */ private static class SingletonFactory { private static Singleton instance = new Singleton(); } /* 获取实例 */ public static Singleton getInstance() { return SingletonFactory.instance; } public Object readResolve() { return getInstance(); } } //-------------------------------------------- public class SingletonTest { private static SingletonTest instance = null; private SingletonTest() {} //将在创建类的时候进行同步,将创建和getInstance()分开,单独为创建加synchronized关键字,防止虚拟机的优化机制 private static synchronized void syncInit() { if (instance == null) { instance = new SingletonTest(); } } public static SingletonTest getInstance() { if (instance == null) { syncInit(); } return instance; } }
二、结构型模式
代理模式(Proxy)
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式。
即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。
-
静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类或者是继承相同父类。
优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展。
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。同时,一旦接口增加方法,目标对象与代理对象都要维护。后者可以使用继承的方式避免。 -
动态代理
代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。(需要我们指定创建代理对象/目标对象实现的接口的类型)
动态代理也叫做:JDK代理,接口代理。(JDK中生成代理对象的API代理类所在包:java.lang.reflect.Proxy)
//JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是: static Object newProxyInstance( ClassLoader loader, Class<?>[] interfaces, InvocationHandler h ) 注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为: ClassLoader loader,:指定当前目标对象使用类加载器, 获取加载器的方法是固定的 Class<?>[] interfaces,:目标对象实现的接口的类型, 使用泛型方式确认类型 InvocationHandler h:事件处理,执行目标对象的方法时, 会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
(代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理)
-
Cglib代理
上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
Cglib子类代理实现方法:
-
需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可。
-
引入功能包后,就可以在内存中动态构建子类
-
代理的类不能为final,否则报错
-
目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
//目标对象,没有实现任何接口 public class UserDao { public void save() { System.out.println("----已经保存数据!----"); } } //----------------------------------------------- //对UserDao在内存中动态构建一个子类对象 public class ProxyFactory implements MethodInterceptor{ //维护目标对象 private Object target; public ProxyFactory(Object target) { this.target = target; } //给目标对象创建一个代理对象 public Object getProxyInstance(){ //1.工具类 Enhancer en = new Enhancer(); //2.设置父类 en.setSuperclass(target.getClass()); //3.设置回调函数 en.setCallback(this); //4.创建子类(代理对象) return en.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("开始事务..."); //执行目标对象的方法 Object returnValue = method.invoke(target, args); System.out.println("提交事务..."); return returnValue; } } //----------------------------------------------- public class App { @Test public void test(){ //目标对象 UserDao target = new UserDao(); //代理对象 UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance(); //执行代理对象的方法 proxy.save(); } }
-
三、行为型模式
(后续有时间再补充了=_=)