[JAVA设计模式]第二部分:创建模式
创建模式
创建模式是对类的实例化过程的抽象。可以动态决定如何创建对象。
简单工厂(Simple Factory)
简单工厂模式又叫静态工厂方法模式,由一个工厂类根据传入的参数决定创建出哪一种产品类的实例,并且工厂方法是静态的。
模式结构
public interface Product{//产品接口}
public class ConcreteProduct implements Product{//具体产品
public ConcreteProduct(){}
}
public class Creator{//工厂
public static Product factory(){//静态方法
return new ConcreteProduct();
}
}
特点
优点:允许客户端相对独立于产品创建的过程,并且在系统引入新产品的时候无需修改客户端,也就是说,它在某种程度上支持“开-闭”原则。
缺点:由于一个工厂类负责所有产品的创建,增加新的产品必将导致工厂类的修改,将必要的逻辑加入到工厂类中。这个模式的缺点是对“开—闭”原则的支持不够。
静态工厂方法中含有对象创建的逻辑,但如果结合反射机制,客户端在调用工厂方法时只需传递一个要创建类的全限定名,可以避免在增加新的产品时修改工厂方法。
工厂方法(Factory Method)
工厂类不再负责所有的产品的创建,而是将具体要求创建的工作交给子类去做,这个核心类则抽取成抽象类。它可用来允许系统在不修改工厂抽象类的情况下引进新的产品,这是简单工厂方法做不到的。
模式结构
public interface Creator{//工厂接口
//工厂方法
public Product factory();
}
public interface Product{}//产品接口
public class ConcreteCreator1 implements Creator{//具体工厂类
public Product factory(){//实现工厂方法
return new ConcreteProduct1();
}
}
public class ConcreteProduct1 implements Product{//具体产品类}
public class Client{//场景
private static Creator creator1, creator2;
private static Product prod1, prod2;
public static void main(String[] args){
creator1 = new ConcreteCreator1();
prod1 = creator1.factory();
creator2 = new ConcreteCreator2();
prod2 = creator2.factory();
}
}
工厂与产品等级结构平行
工厂方式模式并没有限制产品等级结构的层数。一般的书籍中都以两个层次为例,第一层是抽象产品层,第二层是具体产品层。但是在实际的系统中,产品常常有更为复杂的层次,这时工厂一般应与产品等级结构对应。
特点
工厂方法模式的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。
如果系统需要加入一个新的产品,那么所需要的就是向系统中加入一个这个产品类以及它所对应的工厂类。不需要修改客户端,也没有必要修改抽象工厂角色或者其他已有的具体工厂角色。对于增加新的产品类而言,这个系统完全支持“开一闭”原则。
抽象工厂(Abstract Factory)
抽象工厂是对工厂方法模式的进一步推广。假设一个系统需要一些产品对象,而这些产品对象又属于一个以上的产品等级结构。那么为了将消费产品对象的责任和创建这些产品对象的责任分开,可以引进抽象工厂模式。
产品等级结构、产品族的概念
产品等级结构:属于同一抽象产品下的所有产品组成了一个产品等级结构,比如像下图中的Button、Text就是二个产品等级结构。
所谓产品族,是指位于不同产品等级结构中,功能相关联的产品组成的家族,比如下图中的UnixButton、UnixText就是一个产品族,而WinButton、WinText又是另一个产品族。看下图会更清楚一点:
比如Button与Text就是不同的两个产品级结构,而Uinx下Button与Text就组成了Unix下的一个产品族,相似地Windows属于另一产品族。
模式结构
public interface Creator{//工厂接口
//产品等级结构A的工厂方法
public ProductA factoryA();
//产品等级结构B的工厂方法
public ProductB factoryB();
}
public class ConcreteCreator1 implements Creator{//具体工厂
public ProductA factoryA(){
return new ProductA1();
}
public ProductB factoryB(){
return new ProductB1();
}
}
public interface ProductA{//抽象产品A
}
public class ProductA1 implements ProductA{//具体产品A1
}
应用场景
系统的产品多于一个产品族,而系统只消费其中某一族的产品(这是抽象工厂模式的原始用意)。
同属于同一个产品话的产品在一起使用,这一约束必须在系统的设计中体现出来。
与工厂方式模式区别
工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构。
另外,抽象工厂模式中的工厂等级结构应该像工厂方法模式中的工厂等级结构那样,与产品等级结构是平行的。
特点
一般而言,有多少个产品等级结构,就会在工厂角色中发现多少个工厂方法。每个产品等级结构中有多少具体产品,就有多少个产品族,相应地在工厂等级结构中就会有多少具体工厂。
在真实的系统中,产品等级结构的数目与每个产品等级结构中产品族数组一般不相等。
在现代的应用中,抽象工厂模式的使用范围已经大大扩大了,不再要求系统只能消费某一个产品族了,因此,可以不用理会前面所提到的原始用意。
系统中的产品族增加时,抽象工厂模式是支持“开—闭”原则的(如向系统中增加一套苹果操作系统产品族组件时,只需添加相应苹果工厂类即可);但新增一个产品等级结构时,抽象工厂模式不支持“开—闭”原则。
单例(Singleton)模式
单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。可以分成以下三个特点:
单例类只能有一个实例。
单例类必须有自己创建自己的惟一的实例。
单便类必须给所有其他对象提供这个实例。
饿汉式单例类
public class EagerSingleton {
private static final EagerSingleton m_instance = new EagerSingleton();
private EagerSingleton() { }//私有构造器
//静态实例获取方法
public static EagerSingleton getInstance() {
return m_instance;
}
}
饿汉式单例类可以在Java语言内实现,但不易在C++内实现,因为静态初始化在C++里没有固定的顺序,m_instance变量的初始化与类的加载顺序没有保证,可能会出问题。这就是为什么GoF在提出单例类的概念时,举的例子是懒汉式的。实质上饿汉式单例类更符全Java语言本身的特点。
懒汉式单例类
public class LazySingleton{
private static LazySingleton m_instance = null;
private LazySingleton() {}
synchronized public static LazySingleton getInstance(){
if (m_instance == null){
m_instance = new LazySingleton();
}
return m_instance;
}
}
特点
要求生成唯一序列号的环境。
在整个项目中需一个共享访问或共享数据,如Web页面上的计数器。
创建一个对象需要消耗很多的资源的重量级对象,比如Hibnernate中的SessionFactory就是一个单例类,整个系统中只有一个实例。
一般工具性类是没有状态的,可以考虑单例,但是工具类里的成员一般都是静态的,在不需要实例化时就可以使用,所以不需要向外界提供一个getInstance的方法。因此最好将工具类的构造器设计成private后,还需要在禁止在该工具类的内部进行实例化,比如可以通过在构造器中抛出异常来禁止。
一个常用的例子,如读取配置文件的资源读取器,在一个系统里就只需要一个就可以了。
单例类可以是有状态的,也可以是没有状态的。但如果是有状态的话,有状态的单例一定要考虑线程安全的问题。
好处之:减少内存与资源消耗、减少系统的性能开销、避免对资源的多重占用、共享资源访问。
分布式系统中的单例
EJB容器有能力将一个EJB的实例跨过几个JVM调用。由于单例对象不是EJB,因此,单例类局限于某一个JVM中,换言这,如果EJB在跨过JVM后仍然需要引用同一个单类的话,这个单例类就会在多个JVM中被实例化。还就有是一个J2EE应该系统可能分布在多个JVM中,这时候不一定需要EJB就能造成多个单例类的实例出现在不同的JVM中的情况。如果这个单例类是没有状态的,那么就没有问题,但如果是有状态的,那就会有问题了,它不向EJB那样可以跨JVM而状态不会有问题。所以在任何使用了EJB、RMI和JINI技术的分布式系统中,应该避免使用有状态的单例模式。
多个类加载器
在同一JVM中,单例类一定要让同一类加载器来加载,否则会出现多个实例。另外不同类加载器加载的对象是不能相互访问的,所以如果此时单例是有状态的,会有危险。
双重检测
请参考XXXXXXXXXXX
建造(Builder)模式
建造模式可以将一个产品的内部表象与产品的构建过程分开,从而可以使一个建造接口生成具有不同的内部表象的产品对象。
GoF:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造模式实际上是将一个对象的性质(属性)建造过程外部化到独立的建造者对象中,并通过一个导演者角色对这些外部化的性质(属性)赋值的过程进行协调。
概念
产品的内部表象
一个产品常有不同的组成成分作为产品的零件,这些零件有可能是对象,也有可能不是对象,它们通常又叫做产品的内部表象。不同的产品(即不同的类)可以有不同的内部表象(零件个数不同或零件类型不同),即不同的零件。使用建造模式可以使用客户端不需要知道所创建的产品对象有哪些零件,每个产品的对应零件彼此有何不同,是怎么建造出来的,以及怎样组成产品。
对象性质(属性)的建造
有些情况下,一个对象会有些重要的性质,在它们没有恰当的值之前,对象不能作为一个完整的产品使用。比如,一个电子邮件有发件人地址、收件人地址、主题、内容、附录等部分,而在最起码的收件人地址得到照值之前,这个电子邮件不能发出。
有些情况下,一个对象的一些性质必须按照某个赋序值才有意义,在某个性质没有赋值之前,另一个性质则无法赋值。这些情况使得性质本身的建造涉及到复杂的商业逻辑。
这时候,此对象相当于一个有待建造的产品,而对象的这些性质相当于产品的零件。建造产品的过程是建造零件的过程。由于建造零件的过程很复杂,因此,这些零件的建造过程往往被“外部化”到另外一个称做建造者的对象里,建造者对象返还给客户端的是一个全部零件都建造完毕的产品对象。
模式结构
单一产品
假定这个是复杂产品,它由两个零件组成。一般来说,一个系统中会有多于一个的产品类,而且这些产品类并不一定有共同的癌,而完全可以是不相关联的,这会使用Builder中的返回产品的retrieveResult不可用,解决法请后面。
public class Product{
private Object part1;
private Object part2;
public Product() {}
}
建造接口,规范产品对象的各个组成成分的建造。创建者角色一般有两种方法,一种是零件建造方法,一般与产品中的零件数目相等,有多少零件,就有多少相应的建造方法。第二种是返回建造好的产品方法。
abstract public class Builder {
public abstract void buildPart1();
public abstract void buildPart2();
public abstract Product retrieveResult();
}
一般来说,每有个产品类都有一个相应的具体建造者类。
public class ConcreteBuilder extends Builder{//具体建造者
private Product product = new Product() ;
public void buildPart1(){//产品零件1构造方法
//build the first part of the product
}
public void buildPart2(){//产品零件2构造方法
//build the second part of the product
}
public Product retrieveResult(){//产品返还
return product;
}
}
导演者角色是与客户端打交道的角色。导演者角色将客户端创建产品的请求划分为对各个零件的建造请求,再将这些请求“委派”给具体建造者角色。导演者角色并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者角色。
public class Director{//导演者
private Builder builder;
public Director(Builder builder){//可以动态地更换建造器
this.builder = builder;
}
//产品构造方法,负责协调调用各个零件的建造方法
public void construct(){
builder = new ConcreteBuilder();
builder.buildPart1();//构造零件1
builder.buildPart2();//构造零件2
builder.retrieveResult();//产品返还
//continue with other code
}
}
虽然客户端依赖于某个具体建造者,但是操纵具体建造者的任务却是属于导演者对象的。把创建具体建造者对象的任务交给客户端而不是导演者对象,是为了将导演者对象与具体建造者对象的耦合变成动态的,从而使导演者对象可以操纵多个具体建造者对象中的任何一个。
public class Client {//场景
public static void main() {
Builder builder = new ConcreteBuilder();//建造者
Director director = new Director(builder);//导演者
director.construct();//向导演者发出建造产品命令
}
}
示意性类图中的建造模式只是一个最简草的例子,它包括了一个具体建造者类和一个产品类。在实际应用中,一般会有一个以上的具体建造者类和相应的一个以上的产品类。
多个产品
上面只有一个产品的情况,如果有两个产口类的话,就应当有两个具体建造者类。
产品类Product1和Product2各有两个零件,但它们有不同的内部表象。在这里建造模式提供了一个对建造过程的封装,使得客户端不需要知道产品类的建造细节,便可以使用一个建造接口生成具有不同的内部表象的Product1和Product2对象。
如果Product1和Product2不具有相同接口时,则Builder接口中的retrieveResult就需要返还不同的类型,解决办法有两种:
第一种:强迫Product1和Product2实现同一标示性接口,就是上面所示。
第二种:如果产品类是第三提供的,正不能修改,那么就只好将retrieveResult从抽象建造者中移到具体建造者中,但如果Direcotr对象需要调用这个方法,则需要先将Builder向下转型。
必须指出的是,这种没有抽象产品类或者公共产品接口的情况更为普遍。
在多产品的建造者模式中,如果有一些产品有较多的零件,而有些产品有较少的零件,建造模式还可以使用吗?回答是肯定可以使用的。这些产品并不一定要有同样数目的零件才可以使用建造模式。如果一个产品有较少的零件,可以使用空的零件建造方法,忽略没有的零件。当然可以先为Builder设计一个缺少适配器,再让具体构造都从它继承。
变体
省略抽象建造者
如果肯定系统只需要一个具体建造者的话,可以省略掉抽象建造者,因为抽象建造者角色存在的目的是规范具体建造者角色的行为。
省略导演者
在个体建造者只有一个的情况下,如果抽象建造角色已经被省略掉,那么还可以进一步活力掉导演者角色。此时具体构建者自己扮演了导演者和建造者双重角色。此时需要将原导演者里的相关代码移到建造者中。
如果只省略掉导演者,而没有省略掉抽象建造者时,此时的抽象建造者相当于模板方法:
public class Builder {
private Product product = new Product();
public void buildPart1() {
// Write your code here
}
public void buildPart2() {
// Write your code here
}
public Product retrieveResult() {
return product;
}
//从导演者中移过来,此时相当于模板方法
public void construct() {
buildPart1();//具体怎么构造由子类去实现
buildPart2();
Product product = retrieveResult();
}
}
合并建造者角色和产品角色
当建造模式失去抽象建造者与导演者之后,还可以进一步退化——去掉具体建造者。此时具体建造者与产品合并,从而使得产品自己就是自己的建造者。显然,这样做混淆了对象的创建者与对象本身。但是有时候一个产品对象有着固定的几个零件,而且永远只有这几个零件,此时将产品类与建造类合并是很好的,这样可以简化系统的设计,此时这些产品的性质(属性)就变成了零件。比如JavaMail库中的Message类就是一个退化的建造模式,它的性质如from、recipient、subject、text等,都可看做是Message的“零件”。
应用场景
需要生成的产品对象有复杂的内部结构。
需要生成的产品对象的属性相互依赖。
原型(Prototype)模式
通过给出的原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。这就是原型模式的用意。
不通过new关键字来产生一个对象,百是通过对象复制来实现的模式就叫原型模式。
GoF:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
Java语言直接支持原型模式。
模式结构
public interface Prototype extends Cloneable {//原型接口
Object clone();
}
public class ConcretePrototype implements Prototype {//具体原型
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
//write your code here
return null;
}
}
}
public class Client {
private Prototype prototype;
public void operation(Prototype example) {
Prototype p = (Prototype) example.clone();
}
}
应用场景
当一个系统应该独立它的产品类结构时,要使用Prototype模式。比如类是在运行期间动态加载的,我们不知道这个类的具体类型,如果再创建它的实例是很难的,这时如果它具有克隆能力(Java语言本身就具有,不过在某些情况一定要重写clone方法,因为防止浅克隆),我们就很容易地创建出这个对象的另一个实例,这时我们完成了不可能通过new方式来完成的操作。
l 当要实例化的类是在运行时刻指定时,例如,通过动态加载;或者
l 为了避免创建一个与产品类层次平行的工厂类层次时;或者
l 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
为了线程安全,给各个线程一个拷贝。
原型模型很少单独出现,一般和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
优点
l 原型模型允许动态地增加或减少产品类。由于创建产品类实例的方法是产品内部具有的,因此,增加新产品对象整个结构没有影响(因为本身系统中没有直接使用这些具体的产品类)。
l 原型模式提供简化的创建结构。工厂方法模式常常需要有一个与产品类等级结构相同的等级结构,而原型模式就不需要这样,它可以自行克隆自己,不必借助于工厂方法,它本身就是一个工厂方法。
l 具有给一个应用软件动态加载新功能的能力。
l 产品类不需要非得有任何事先确定的等级结构,因为原型模式适用于任何的产品等级结构。
原型模型是对内存二进制流的拷贝,要比直接new一个对象性能好很多(而且也比序化方式要快),特别是要在一个循环体内产生大量的对象时。