设计模式之禅《二 - 上卷》
23种设计模式完美演绎
一:单例模式
Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
public class singleton {
private static final Singleton singleton = new Singleton ();
//限制产生多个对象
private Singleton (){
}
//通过该方法获得实例对象
public static Singleton getSingleton (){
//此方法前加sychronized称为懒汉式单例,不加是饿汉式单例
return singleton;
}
//类中其他方法,尽量是static
public static void doSomething (){
}
}
优势:
● 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
● 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM垃圾回收机制)。
● 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
● 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
缺点:
● 单例模式对测试是不利的。
● 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
● 单例模式与单一职责原则有冲突。
禅:单例模式是23个模式中比较简单的模式,应用也非常广泛,如在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理这些Bean的生命期,决定什么时候创建出来,什么时候销毁,销毁的时候要如何处理。
二:工厂方法模式
Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.(定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。)
(1)工厂方法模式
下面是通用代码是一个比较实用、易扩展的框架。
//抽象产品类
public abstract class Product {
//产品类的公共方法
public void method1 ( ) {
//业务逻辑处理
}
//抽象方法
public abstract void method2 ( );
}
//=======================================
//具体产品类
public class ConcreteProduct1 extends Product {
public void method2 ( ){
//业务逻辑处理
}
}
public class ConcreteProduct2 extends Product {
public void method2( ){
//业务逻辑处理
}
}
//========================================
//抽象工厂类
public abstract class Creator{
/*
*创建一个产品对象,其输入参数类型可以自行设置*通常为string 、Enum、class等,当然也可以为空
*/
public abstract <T extends Product> T createProduct(Class<T> c);
}
//========================================
//具体工厂类
public class ConcreteCreator extends Creator {
public <Textends Product> T createProduct (Class<T> c){
Product product=null;
try {
product = (Product)Class.forName (c.getName ( ) ) .newInstance ( ) ;
} catch(Exception e){
//异常处理
return (T)product ;
}
}
//========================================
//业务场景实现
public class client {
public static void main (string [ ] args ){
Creator creator = new ConcreteCreator () ;
Product product = creator.createProduct (ConcreteProduct1.class) ;
/*继续业务处理*/
}
}
工厂模式优点:良好的封装性、代码结构清晰、降低模块间的耦合、扩展性非常优秀、屏蔽产品类。
特点:工厂方法模式是典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,我不需要的就不要去交流;也符合依赖倒置原则:只依赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类,没问题!
适合的场景:需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。
1、简单工厂模式:去掉继承抽象类,增加了static关键字。也称为静态工厂模式,不符合开闭原则,但是它仍然是一个非常实用的设计。
2、多工厂模式:每个创建者都独立负责创建对应的产品对象,非常符合单一职责原则。抽象方法中已经不再需要传递相关参数了,因为每一个具体的工厂都已经非常明确自己的职责:创建自己负责的产品类对象。当然,在复杂的应用中一般采用多工厂的方法,然后再增加一个协调类,避免调用者与 各个子工厂交流,协调类的作用是封装子工厂类,对高层模块提供统一的访问接口。
3、替代单例模式:通过获得类构造器,然后设置访问权限,生成一个对象,然后提供外部访问,保证内存中的对象唯一。通过反射的方式建立一个单例对象。
4、延迟初始化: 一个对象被消费完毕后,并不立刻释放,工厂类 保持其初始状态,等待再次被使用。延迟初始化是工厂方法模式的一个扩展应用
禅:工厂方法模式在项目中使用得非常频繁,以至于很多代码中都包含工厂方法模式。该模式几乎尽人皆知,但不是每个人都能用得好。熟能生巧,熟练掌握该模式,多思考工厂方法如何应用,而且工厂方法模式还可以与其他模式混合使用(例如模板方法模式、单例模式、原型模式等)
(2)抽象工厂方法模式
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.(为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。)抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。
优势:封装性 ,产品族内的约束为非公开状态。
缺点:抽象工厂模式的最大缺点就是产品族扩展非常困难。
场景限制:抽象工厂模式的使用场景定义非常简单:一个对象族(或是一组没有任何关系的对象)都有相同的约束,则可以使用抽象工厂模式。
禅:抽象工厂模式是一个简单的模式,使用的场景非常多,大家在软件产品开发过程中,涉及不同操作系统的时候, 都可以考虑使用抽象工厂模式。
三:模板方法模式
Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.(定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。)
模板方法模式非常简单,仅仅使用了Java的继承机制,但它是一个应用非常广泛的模式。AbstractClass叫做抽象模板,它的方法分为两类:
● 基本方法: 基本方法也叫做基本操作,是由子类实现的方法,并且在模板方法被调用。
● 模板方法: 可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。
注意: 抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限。
优势:
● 封装不变部分,扩展可变部分
● 提取公共部分代码,便于维护
● 行为由父类控制,子类实现
缺点:
抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成具体的事物属性和方法。抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,这在复杂的项目中,会带来代码阅读的难度,而且也会让新手产生不适感。
使用场景:
● 多个子类有公有的方法,并且逻辑基本相同时。
● 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
● 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通 过钩子函数(见“模板方法模式的扩展”)约束其行为。
扩展:
外界条件改变,影响到模板方法的执行,该方法就叫做钩子方法(Hook Method)。
提问:
初级程序员在写程序的时候经常会问高手“父类怎么调用子类的方法”?
● 把子类传递到父类的有参构造中,然后调用。
● 使用反射的方式调用,你使用了反射还有谁不能调用的?!
● 父类调用子类的静态方法。
禅:模板方法在一些开源框架中应用非常多,它提供了一个抽象类,然后开源框架写了一堆子类。如果你需要扩展功能,可以继承这个抽象类,然后覆写protected方法,再然后就是调用一个类似execute方法,就完成你的扩展开发,非常容易扩展的一种模式。
四:建造者模式
经常会有一个共识:需求是无底洞,是无理性的
如果你要调用类中的成员变量或方法,需要在前面加上this关键字,不加也能正常地跑起来,但是不清晰,加上this关键字,我就是要调用本类中的成员变量或方法,而不是本方法中的一个变量。还有super方法也是一样,是调用父类的成员变量或者方法,那就加上这个关键字,不要省略,这要靠约束,还有就是程序员的自觉性。
ArrayList和HashMap如果定义成类的成员变量,那你在方法中的调用一定要做一个clear的动作,以防止数据混乱
建造者模式(Builder Pattern)也叫做生成器模式,其定义如下:Separate the construction of a complex object from its representation so that the sameconstruction process can create different representations.(将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。)
● Product产品类
通常是实现了模板方法模式,也就是有模板方法和基本方法。
● Builder抽象建造者
规范产品的组建,一般是由子类实现。例子中的CarBuilder就属于抽象建造者。
● ConcreteBuilder
具体建造者实现抽象类定义的所有方法,并且返回一个组建好的对象。例子中的BenzBuilder和BMWBuilder就属于具体建造者。
● Director导演类
负责安排已有模块的顺序,然后告诉Builder开始建造。
//产品类
public class Product {
public void doSomething()
{
//独立业务处理
}
}
//抽象建造者
public abstract class Builder {
//设置产品的不同部分,以获得不同的产品
public abstract void setPart();
//建造产品
public abstract Product buildProduct();
}
//具体建造者
public class ConcreteProduct extends Builder {
private Product product = new Product();
//设置产品零件
public void setPart(){
/** 产品类内的逻辑处理 */
}
//组建一个产品
public Product buildProduct() {
return product;
}
}
//导演类
public class Director {
private Builder builder = new ConcreteProduct();
//构建不同的产品
public Product getAProduct(){
builder.setPart();
/** 设置不同的零件,产生不同的产品 */
return builder.buildProduct();
}
}
优势:
● 封装性:使用建造者模式可以使客户端不必知道产品内部组成的细节
● 建造者独立,容易扩展
● 便于控制细节风险:由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。
使用场景:
● 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
● 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式。
● 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适。
● 在对象创建过程中会使用到系统中的一些其他对象,这些对象在产品对象的创建过程中不易得到时,也可以采用建造者模式封装该对象的创建过程。
建造者模式和工厂模式:
建造者模式最主要的功能是基本方法的调用顺序安排,也就是这些基本方法已经实现了,通俗地说就是零件的装配,顺序不同产生的对象也不同;而工厂方法则重点是创建,创建零件是它的主要职责,组装顺序则不是它关心的。
禅:使用建造者模式的时候可以考虑融合模板方法模式,别孤立地思考一个模式。
五、代理模式
Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问。)
● Subject抽象主题角色
抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
● RealSubject具体主题角色
也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。
● Proxy代理主题角色
也叫做委托类、代理类。
public class Proxy implements Subject {
//要代理哪个实现类
private Subject subject = null;
//默认被代理者
public Proxy(){
this.subject = new Proxy();
}//通过构造函数传递代理者
public Proxy(Object...objects ){
}//实现接口中定义的方法
public void request() {
this.before();
this.subject.request();
this.after(); }
//预处理
private void before(){
//do something
}//善后处理
private void after(){
//do something
}
}
优势:
● 职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
● 高扩展性
具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。
● 智能化
把表单元素映射到对象上的。
使用场景:
减轻负担,Spring AOP,这是一个非常典型的动态代理。
(1)普通代理
客户端只能访问代理角色,而不能访问真实角色在该模式下,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要你实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合。
普通代理模式的约束问题,尽量通过团队内的编程规范类约束,因为每一个主题类是可被重用的和可维护的,使用技术约束的方式对系统维护是一种非常不利的因素。
(2)强制代理
强制代理却是要“强制”,你必须通过真实角色查找到代理角色,否则你不能访问。甭管你是通过代理类还是通过直接new一个主题角色类,都不能访问,只有通过真实角色指定的代理类才可以访问,也就是说由真实角色管理代理角色。
强制代理的概念:就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要产生一个代理出来,代理的管理已经由真实角色自己完成。
代理类可以为真实角色预处理消息、过滤消息、消息转发、事后处理消息等功能。当然一个代理类,可以代理多个真实角色,并且真实角色之间可以有耦合关系。
(3)动态代理
动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制。
类的动态代理类是这样的一个类,由InvocationHandler的实现类实现所有的方法,由其invoke方法接管所有方法的实现,其动态调用过程
动态代理的主要意图就是解决我们常说的“审计”问题,也就是横切面编程,在不改变我们已有代码结构的情况下增强或控制对象的行为、要实现动态代理的首要条件是:被代理类必须实现一个接口,
禅:**切面(Aspect)、切入点 (JoinPoint)、通知(Advice)、织入(Weave)**弄清所有名词的含义,并且了解整个切面编程的过程逻辑。
六、原型模式
Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.(用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。)
public class PrototypeClass implements Cloneable{
//覆写父类Object方法
@Override
public PrototypeClass clone(){
PrototypeClass prototypeClass = null;
try { prototypeClass = (PrototypeClass)super.clone();
} catch (CloneNotSupportedException e) {
//异常处理
}
return prototypeClass;
}
}
优点:
● 性能优良
● 逃避构造函数的约束
使用场景 :
● 资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
● 性能和安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
● 一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
(1)构造函数不会被执行
对象拷贝时构造函数确实没有被执行,这点从原理来讲也是可以讲得通的,Object类的clone方法的原理是从内存中(具体地说就是堆内存)以二进制流的方式进 行拷贝,重新分配一个内存块,那构造函数没有被执行也是非常正常的了。
(2)浅拷贝和深拷贝
是因为Java做了一个偷懒的拷贝动作,Object类提供的方法clone只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝。
//浅拷贝
public class Client {
public static void main(String[] args) {
//产生一个对象
Thing thing = new Thing();
//设置一个值
thing.setValue("张三");
//拷贝一个对象
Thing cloneThing = thing.clone();
cloneThing.setValue("李四");
System.out.println(thing.getValue());
}
}
//深拷贝
public class Thing implements Cloneable{
//定义一个私有变量
private ArrayList<String> arrayList = new ArrayList<String>();
@Override public Thing clone(){
Thing thing=null;
try {
thing = (Thing)super.clone();
thing.arrayList = (ArrayList<String>)this.arrayList.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}return thing;
}
}
(3)clone与final
要使用clone方法,类的成员变量上不要增加final关键字。
禅:原型模式先产生出一个包含大量共有信息的类,然后可以拷贝出副本,修正细节信息,建立了一个完整的个性对象。
七、中介者模式
Define an object that encapsulates how a set of objects interact.Mediator promotes loose coupling by keeping objects from referring to each other explicitly,and it lets you vary their interaction independently.(用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。)
在多个对象依赖的情况下,通过加入中介者角色,取消了多个对象的关联或依赖关系,减少了对象的耦合性。
● Mediator 抽象中介者
角色抽象中介者角色定义统一的接口,用于各同事角色之间的通信。
● Concrete Mediator 具体中介者
角色具体中介者角色通过协调各同事角色实现协作行为,因此它必须依赖于各个同事角色。
● Colleague 同事角色
每一个同事角色都知道中介者角色,而且与其他的同事角色通信的时候,一定要通过中介者角色协作。每个同事类的行为分为两种:一种是同事本身的行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为(Self-Method),与其他的同事类或中介者没有任何的依赖;第二种是必须依赖中介者才能完成的行为,叫做依赖方法(Dep- Method)。
优点:
中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合。
缺点:
中介者模式的缺点就是中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。
使用场景:
中介者模式
用于多个对象之间紧密耦合的情况,紧密耦合的标准是:在类图中出现了蜘蛛网状结构。在这种情况下一定要考虑使用中介者模式,这有利于把蜘蛛网梳理为星型结构,使原本复杂混乱的关系变得清晰简单。
应用:
● 机场调度中心
● MVC框架
MVC框架,其中的C(Controller)就是一个中介者,叫做前端控制器(Front Controller),它的作用就是把M(Model,业务逻辑)和V(View,视图)隔离开,协调M和V协同工作,把M运行的结果和V代表的视图融合成一个前端可以展示的页面,减少M和V的依赖关系。MVC框架已经成为一个非常流行、成熟的开发框架,这也是中介者模式的优点的一个体现。
● 媒体网关
● 中介服务
禅:
● N个对象之间产生了相互的依赖关系(N>2)。
● 多个对象有依赖关系,但是依赖的行为尚不确定或者有发生改变的可能。
● 产品开发。一个明显的例子就是MVC框架,把中介者模式应用到产品中。
八、命令模式
命令模式是一个高内聚的模式,其定义为:Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.(将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。)
● Receive接收者角色
● Command命令角色
● Invoker调用者角色
优点:
● 类间解耦
● 可扩展性
● 命令模式结合其他模式会更优秀
缺点:
Command的子类,会随着命令的增长而变的非常膨胀。
扩展:客户只需要发布命令,至于如何执行这个命令,是协调一个对象,还是两个对象,都不需要关心,命令模式做了一层非常好的封装。
反悔问题:
一、是结合备忘录模式还原最后状态,该方法适合接收者为状态的 变更情况,而不适合事件处理;
二、是通过增加一个新的命令,实现事件的回滚。
禅:通过有意义的类名或命令名处理命令角色和接收者角色的耦合关系(这就是约定),减少高层模块(Client类)对低层模块(Receiver角色类)的依赖关系,提高系统整体的稳定性
九、责任链模式
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.Chain the receiving objects and pass the request along the chain until an object handles it.(使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。)
责任链模式的重点是在“链”上,由一条链去处理相似的请求在链中决定谁来处理这个请求,并返回相应的结果
反悔问题:
一、是结合备忘录模式还原最后状态,该方法适合接收者为状态的 变更情况,而不适合事件处理;
二、是通过增加一个新的命令,实现事件的回滚。
禅:通过有意义的类名或命令名处理命令角色和接收者角色的耦合关系(这就是约定),减少高层模块(Client类)对低层模块(Receiver角色类)的依赖关系,提高系统整体的稳定性