第二部分:23种设计模式(上)
1、单例模式
单例模式定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式通用类图如下:
Singleton类称为单例类,通过使用private的构造函数确保了在一个应用中只产生一个实例,并且是自行实例化的(在Singleton中自己使用new Singleton())。
单例模式的通用源代码如下所示:
public class Singleton{
private static final Singleton singleton = new Singleton();
//限制产生多个对象
private Singleton(){
}
//通过该方法获得实例对象
public static Singleton getSingleton(){
return singleton;
}
//类中其它方法,尽量是static
public static void doSomething(){
}
}
单例模式的优点:
1)由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的有时就非常明显;
2)由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要较多的资源时,如读取配置、产生其它依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在Java EE中采用单例模式时注意JVM垃圾回收机制);
3)单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作;
4)单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
单例模式的缺点:
1)单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现;
2)单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象;
3)单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关系它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。
单例模式的使用场景:
1)要求生成唯一序列号的环境;
2)在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用吧每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全地;
3)创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
4)需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。
注:单例模式是23个模式中比较简单的模式,应用也非常广泛,如在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理这些Bean的生命期,决定什么时候创建出来,什么时候销毁,销毁的时候要如何处理,等等。 如果采用非单例模式(Prototype类型),则Bean初始化后的管理交由J2EE容器,Spring容器不再跟踪管理Bean的生命周期。
2、工厂方法模式
工厂方法模式定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法模式的通用类图如下所示:
在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。通用源码如下:
代码清单:抽象产品类
public abstract class Product{
//产品类的公共方法
public void method1(){
//业务逻辑处理
}
//抽象方法
public abstract void method2();
}
具体产品类可以有多个,都继承于抽象产品类
代码清单:具体产品类
public class ConcreteProduct1 extends Product{
public void method1(){
//业务逻辑处理
}
}
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 <T extends 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)良好的封装性,代码结构清晰;
2)工厂方法模式的扩展性非常优秀;
在增加产品类的情况下,只要适当地修改具体工厂类或扩展一个工厂类,就可以完成“拥抱变化”。
3)屏蔽产品类;
产品类的实现如何变化,调用者都不需要关系,它只需要关系产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的。
注:工厂方法模式是典型的解耦框架。高层模块只需要知道产品的抽象类,其他的实现类都不用关系,符合迪米特法则,我不需要的就不去交流;也符合依赖倒置原则,职以来产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类,没问题!
工厂方法模式的使用场景:
1)工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但要慎重考虑是否增加一个工厂类进行管理,增加代码复杂度;
2)需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式;
3)可以用在异构项目中;
4)可以使用在测试驱动开发的框架下。例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。目前由于JMock和EasyMock的诞生,该使用场景已弱化。
工厂方法模式的扩展:
1)缩小为简单工厂模式
2)升级为多个工厂类
3)替代单例模式
4)延迟初始化
3、抽象工厂模式
抽象工厂模式的定义:为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。
抽象工厂模式的通用类图如下:
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。我们来看看抽象工厂的通用源代码,首先有两个互相影响的产品线(也叫产品族),例如制造汽车的左侧门和右侧门,这两个应该是数量相等的——连个对象之间的约束,每个型号的车门都是不一样的,这是产品等级结构约束的,我们先看看两个产品族的类图
抽象工厂莫斯的通用源码类图如下:
两个抽象的产品类可以有关系,例如共同继承或实现一个抽象类或接口
代码清单:抽象产品类
public abstract class AbstractProductA{
//每个产品共有的方法
public void shareMethod(){
}
//每个产品相同方法,不同实现
public abstract void doSomething();
}
代码清单:产品A1的实现类
public class ProductA1 extends AbstractProductA{
public void doSomething(){
System.out.println("产品A1的实现方法");
}
}
代码清单:产品A2的实现类
public class ProductA2 extends AbstractProductA{
public void doSomething(){
System.out.println("产品A2的实现方法");
}
}
抽象工厂类AbstractCreator的职责是定义每个工厂要实现的功能
代码清单:抽象工厂类
public abstract class AbstractCreator{
//创建A产品家族
public abstract AbstractProductA createProductA();
//创建B产品家族
public abstract AbstractProductB createProdectB();
}
注:有N个产品族,在抽象工厂类中就应该有N个创建方法
代码清单:产品等级1的实现类
public class Creator1 extends AbstractCreator{
//只生产产品等级为1的A产品
public AbstractProductA createProductA(){
return new ProductA1();
}
//只生产产品等级为1的B产品
public AbstractProductB createProductB(){
return new ProductB1();
}
}
代码清单:产品等级2的实现类
public class Creator2 extends AbstractCreator{
//只生产产品等级为2的A产品
public AbstractProductA createProductA(){
return new ProductA2();
}
//只生产产品等级为2的B产品
public AbstractProductB createProductB(){
return new ProductB2();
}
}
注:有M个产品等级就应该有M个实现工厂类,在每个实现工厂中,实现不同产品族的生产任务
代码清单:场景类
public class Client{
public static void main(String[] args){
//定义出两个工厂
AbstractCreator creator1 = new Creator1();
AbstractCreator creator2 = new Creator2();
//产生A1对象
AbstractProductA a1 = creator1.createProductA();
//产生A2对象
AbstractProductA a2 = creator2.createProductA();
//产生B1对象
AbstractProductB b1 = creator1.createProductB();
//产生B2对象
AbstractProductB b2 = creator2.createProductB();
/*
*然后在这里就可以为所欲为了...
*/
}
}
注:在场景类中,没有一个方法与实现类有关系,对于一个产品来说,我们只要知道它的工厂方法就可以直接产生一个产品对象,无需关心它的实现类。
抽象工厂模式的优点:
1)封装性
每个产品的实现类不是高层模块要关心的,它要关心的是什么?是接口,是抽象,它不关心对象是如何创建出来,这由谁负责呢?工厂类,只要知道工厂类是谁,就能创建出一个需要的对象。
2)产品族内的约束为非公开状态
具体产品族内的约束是在工厂内实现的。
抽象工厂模式的缺点:
1)产品族扩展非常困难(但是产品等级非常容易扩展)
抽象工厂模式的使用场景:
一个对象族(或是一组没有任何关系的对象)都有相同的约束,则可以使用抽象工厂模式。
4、模板方法模式
模板方法模式的定义:定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法模式的通用类图如下所示:
模板方法模式确实非常简单,仅仅使用了Java的继承机制。AbstractClass叫做抽象模板,它的方法分为两类
1)基本方法
基本方法也叫基本操作,是由子类实现的方法,并且在模板方法中被调用
2)模板方法
可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。
注:为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。
5、建造者模式
建造者模式定义:建造者模式(Builder Pattern)也叫做生成器模式,定义是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式的通用类图如下所示:
在建造者模式中,有如下4个角色
1)Product产品类
通常是实现了模板方法模式,也就是有模板方法和基本方法
2)Builder抽象建造者
规范产品的组件,一般是有子类实现。
3)ConcreteBuilder具体建造者
实现抽象类定义的所有方法,并且返回一个组建好的对象。
4)Director导演类
负责安排已有模块的顺序,然后告诉Builder开始建造
建造者模式通用源代码如下:
代码清单:产品类
public class Product{
public void doSomething(){
//独立业务处理
}
}
代码清单:抽象建造者
public abstract class Builder{
//设置产品的不同部分,以获得不同的产品
public abstract void setPart();
//建造产品
public abstract Product buildProduct();
}
注:setPart方法是零件的配置,什么是零件?其他的对象,获得一个不同零件,或者不同的装配顺序就可能产生不同的产品。
代码清单:具体建造者
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();
}
}
注:导演类起到封装的作用,避免高层模块深入到建造者内部的实现类。当然,在建造者模式比较庞大时,导演类可以有多个
建造者模式的优点:
1)封装性
使用建造者模式可以使客户端不必知道产品内部组成的细节
2)建造者独立,容易扩展
3)便于控制细节风险
由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。
建造者模式的使用场景:
1)相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式;
2)多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式;
3)产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适;
4)在对象创建过程种会使用到系统中的一些其他对象,这些对象在产品对象的创建过程中不易得到时,也可以采用建造者模式封装该对象的创建过程。这种场景只能是一个补偿方法,因为一个对象不容易获得,而在设计阶段竟然没有发觉,而要通过创建者模式柔化创建过程,本身已经违反设计的最初目标。
注:建造者模式最主要的功能是基本方法的调用顺序安排,也就是这些基本方法已经实现了,通俗地说就是零件的装配,顺序不同产生的对象也不同;而工厂方法则重点是创建,创建零件是它的主要职责,组装顺序则不是它关心的。
6、代理模式
代理模式的定义:为其它对象提供一种代理以控制对这个对象的访问。
代理模式的通用类图如下所示:
代理模式也叫委托模式,先看一下类图中三个角色定义:
1)Subject抽象主题角色
抽象主题类可以是抽象类也可以是借口,是一个最普通的业务类型定义,无特殊要求。
2)RealSubject具体主题角色
也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。
3)Proxy代理主题角色
也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
代理模式通用源代码如下:
代码清单:抽象主题类
public interface Subject{
//定义一个方法
public void request();
}
代码清单:真实主题类
public class RealSubject implements Subject{
//实现方法
public void request(){
//业务逻辑处理
}
}
注:RealSubject是一个正常的业务实现类,代理模式的核心就在代理类上。
代码清单:代理类
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
}
}
代码清单:代理的构造函数
public Proxy(Subject _subject){
this.subject = _subject;
}
注:你要代理谁就产生该代理的实例,然后把被代理者传递进来。
代理模式的优点:
1)职责清晰
真实的角色就是实现实际的业务逻辑,不用关系其他非本职责的事物,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
2)高扩展性
具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。
3)智能化
注:代理模式应用得非常广泛,达到一个系统框架、企业平台,小到代码片段、事务处理,稍不留意就用到代理模式。
7、原型模式
原型模式的定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式的通用类图如下:
原型模式的核心是一个clone方法,通过该方法进行对象的拷贝
原型模式的通用源码如下:
public class PrototypeClass implements Cloneable{
//覆写父类Object方法
@Override
public PrototypeClass clone(){
PrototypeClass prototypeClass = null;
try{
prototypeClass = (PrototypeClass)super.clone();
}catch(CloneNotSupportedException e){
//异常处理
}
return prototypeClass;
}
}
实现一个接口,然后重写clone方法,就完成了原型模式!
原型模式的优点:
1)性能优良
原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
2)逃避构造函数的约束
这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。优点是减少了约束,缺点也是减少了约束,需要大家在实际应用中考虑。
原型模式的使用场景:
1)资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
2)性能和安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
3)一个对象多个修改者的场景
一个对象需要提供给其它对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
原型模式注意事项:
1)构造函数不会被执行
2)浅拷贝和深拷贝
Object类提供的方法clone只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是只想原生对象的内部元素地址,这种拷贝就叫做浅拷贝。
注:对象的clone与对象内的final关键字是有冲突的,要使用clone方法,类的成员变量上不要增加final关键字。
注:
上一篇:6大设计原则
下一篇:23种设计模式(中)
推荐阅读:秦小波著《设计模式之禅》