您的位置:首页 > 其它

设计模式之创建性模式

2012-12-04 21:12 183 查看
抽象工厂模式:提供一个创建并返回一族相关示例的接口,而不需要指明它们具体的类。

生成器模式:将一个复杂对象的构建与呈现分开,以便相同的构造过程能够根据不同需要创建不同的形式。

工厂方法模式:提供一个创建一个示例的接口,但是允许子类决定实例化哪个类,即不同的子类可以实例化不同的对象。

原型模式:先实例化一个类,然后克隆或者拷贝该类来构建新的实例。可以用共有方法进一步修改这些实例。

单件模式:某个类只能有一个实例。提供一个全局访问点。(可拓展到有限个实例)


简单工厂

意图:提供一个类,由它负责根据一定的条件创建某一具体类的实例



考虑我们开了一家披萨店,客户可以通过调用orderPizza方法来订餐。

public class PizzaStore {
SimplePizzaFactory factory;

public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}

public Pizza orderPizza(String type) {
Pizza pizza;

pizza = factory.createPizza(type);

pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();

return pizza;
}

}


我们使用一个工厂来制作各种披萨,而无需关心是哪种具体的批塞,因为工厂可以根据我们传进去的参数自己判断制造何种披萨。

public class SimplePizzaFactory {

public Pizza createPizza(String type) {
Pizza pizza = null;

if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
return pizza;
}
}


抽象类Pizza的定义如下:

public abstract class Pizza {
String name;
String dough;
String sauce;
ArrayList toppings = new ArrayList();

void prepare() {
System.out.println("Preparing " + name);
System.out.println("Tossing dough...");
System.out.println("Adding sauce...");
System.out.println("Adding toppings: ");
for (int i = 0; i < toppings.size(); i++) {
System.out.println("   " + toppings.get(i));
}
}

void bake() {
System.out.println("Bake for 25 minutes at 350");
}

void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}

void box() {
System.out.println("Place pizza in official PizzaStore box");
}
}




简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。

角色及其职责:

工厂(Creator)角色:如上图中的SimplePizzaFactory类。简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
抽象(Product)角色:如上图中的Pizza抽象类。简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
具体产品(Concrete Product)角色:如上图中的CheesePizza类、VeggiePizza类和ClamPizza类。简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。一般来讲它是抽象产品类的子类,实现了抽象产品类中定义的所有接口方法,也可以覆盖父类中的方法。

模式的特点:

优点:简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。在这个模式中,工厂类是整个模式的关键所在。它包含必要的判断逻辑,能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。用户在使用时可以直接根据工厂类去创建所需的实例,而无需了解这些对象是如何创建以及如何组织的。有利于整个软件体系结构的优化。
缺点:体现在其工厂类上,由于工厂类集中了所有实例的创建逻辑,所以"高内聚"方面做的并不好。另外,当系统中的具体产品类不断增多时,可能会出现要求工厂类也要做相应的修改,扩展性并不很好。


工厂方法模式

意图:定义一个用户创建对象的接口,让子类决定实例化哪一个类,工厂方法模式使一个类的实例化延迟到其子类。这里的“决定”并不是指模式允许子类在运行时刻做决定,而是指在编写创建者类(Creator)时(如编写AnOperation()方法时),不需要知道实际创建的产品是哪一个。选择了使用哪个子类,自然就决定了实际创建的产品是什么。

依然考虑我们的披萨店。现在生意很好,我们有资本去开分店了!比如我们希望去北京、杭州、济南开不同的分店,但是由于存在地区差异,每家分店可能想要提供不同风味的批塞。如果使用简单工厂,我们可以写出三种不同的工厂,分别是BeijingPizzaFactory、HangzhouPizzaFactory、JinanPizzaFactory,那就会变成下面这个样子。

SimplePizzaFactory bjFactory = new BeijingPizzaFactory();
PizzaStore bjStore = new PizzaStore(bjFactory);

SimplePizzaFactory hzFactory = new HangzhouPizzaFactory();
PizzaStore hzStore = new PizzaStore(hzFactory);


然而,分店提出来他们想要采用自创的流程:烘烤的方法有些差异、不要切片、使用其他厂商的盒子等等。如果还是采用上面的方法,就会发现,因为大家都是采用同一个“客户”(PizzaStore),所以所有的流程必然都是相同的(都是PizzaStore中实现的orderPizza方法),这样做肯定是不行了。但是我们又希望大家使用同一个框架,同时又保持一定的弹性。这时工厂方法就登场了!

这个框架就是我们定义的一个PizzaStore的抽象类,它定义了所有分店的基本流程(框架),但允许子类继承并决定具体是怎样实现的(有弹性)。

public abstract class PizzaStore {

abstract Pizza createPizza(String item);

public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.getName() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}


而每个分店只需继承这个类,实现自己的工厂方法,createPizza,即可。

public class BJPizzaStore extends PizzaStore {

Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new NYStyleCheesePizza();
} else if (item.equals("veggie")) {
return new NYStyleVeggiePizza();
} else if (item.equals("clam")) {
return new NYStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new NYStylePepperoniPizza();
} else return null;
}
}


而且,每个分店还可以覆盖父类中的orderPizza方法,实现自己的创新!

除此之外,我们的披萨的种类也更多了,因为不同地区又研发出了不同口味的披萨,而且制作方法也可能发生了改变。

public class ChicagoStylePepperoniPizza extends Pizza {
public ChicagoStylePepperoniPizza() {
name = "Chicago Style Pepperoni Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";

toppings.add("Shredded Mozzarella Cheese");
toppings.add("Black Olives");
toppings.add("Spinach");
toppings.add("Eggplant");
toppings.add("Sliced Pepperoni");
}

void cut() {
System.out.println("Cutting the pizza into square slices");
}
}
与简单工厂相比,它们之间的差异如下:

虽然每个具体的商店(BJPizzaStore)看起来很像一个SimplePizzaFacoty,但是这里的商店是扩展自一个类,这个类中有一个抽象方法createPizza(),每个店实现自己的方法。而在简单工厂中,这个抽象方法是一个PizzaStore使用的对象。也就是说,简单工厂使用的是对象,而工厂方法使用的是方法。
简单工厂把全部事情在同一个地方做完,然后工厂方法却是建立了一个框架,剩下的让子类自己搞去吧。比如orderPizza()方法提供了一般框架,以便创建披萨,orderPizza()依赖工厂方法创建具体的类。



总结一下工厂方法。

Creator是一个抽象类,它实现了所有操纵产品的方法,但不实现工厂方法,即FactoryMethod()方法。
Creator所有的子类都必须实现这个抽象的FactoryMethod()方法。
ConcreteCreator是Creator的一个子类,它实现了FactoryMethod(),以实际制造出产品。
ConcreteCreator负责创建一个或多个具体产品,即ConcreteProduct,只有ConcreteCreator类知道如何创建这些产品。

所有的产品必须实现一个共同的接口:Product,这样一来,使用这些产品的类就可以引用这个接口,而不是具体的类。

优点:遵守了依赖倒置原则:要依赖抽象,不要依赖具体的类。为了避免违反依赖倒置原则,我们可以在设计中尽量遵守以下规定:1)变量不可以持有具体类的引用。如果使用new,就会持有具体类的引用,可以使用工厂来避免;2)不要让类派生自具体类。如果派生自具体的类,就会依赖具体类。请派生自一个抽象(接口或抽象类);3)不要覆盖基类中已经实现的方法。如果覆盖基类已实现的方法,那么说明基类并不是一个真正适合被继承的抽象。

这里还体现了一个观点:平行的类层次。



实现要点:

Factory Method模式的两种情况:一是Creator类是一个抽象类且它不提供它所声明的工厂方法的实现;二是Creator是一个具体的类且它提供一个工厂方法的缺省实现,即创建者和工厂方法不再是抽象的。
工厂方法是可以带参数的。
工厂的作用并不仅仅只是创建一个对象,它还可以做对象的初始化,参数的设置等。

效果:

用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活。
Factory Method模式通过面向对象的手法,将所要创建的具体对象的创建工作延迟到了子类,从而提供了一种扩展的策略,较好的解决了这种紧耦合的关系。

适用性:

当一个类不知道它所必须创建的对象的类的时候。
当一个类希望由它的子类来指定它所创建的对象的时候。
当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

附注

基类是一个抽象类,模式必须返回一个完整的可工作的类
基类包含默认方法,除非这些默认方法不能胜任才调用子类方法
可以讲参数传递给工厂,告诉工厂返回哪一个类。


抽象工厂模式

意图:提供一个接口,用于创建一系列相关或相互依赖对象的家族,而无需明确指定具体的类。与工厂方法类似,但此处返回的一系列相关产品。实现过程同样推延到子系列类去实现。与工厂方法的区别在于他们的层次模型。工厂方法的抽象基类只有儿子,而抽象工厂模式却是有孙子,而且每个儿子的儿子们之间有相互关联依赖关系。

好吧,我们还是得说我们的披萨店。现在分店已经开好了,但是新的问题又出现了:原料问题。我们总不能让一家原料工厂供应所有的分店!因为虽然大家都使用那么几种原料:面团,酱料,芝士,蔬菜,肉等,但是原料的制作方式会根据区域的不同而有差异(比如四川人喜欢吃辣的,可能我们的酱料就要使用辣酱,而上海人不吃辣的,所以就使用一般的酱料)。

这里就有了一个新的概念:原料家族。北京分店使用一组原料,而杭州使用另一组原料。这些原料品种相同(每个家族都包含了一种面团,一种酱料,一种芝士等等),但实现方法不同而已。每个区域对应了一套完整的原料家族。于是我们考虑在原料方面使用“抽象工厂”!

首先为工厂定义一个接口,负责创建所有的原料:

public interface PizzaIngredientFactory {

public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();

}


然后每个分店实现自己的原料工厂:

public class BJPizzaIngredientFactory implements PizzaIngredientFactory {

public Dough createDough() {
return new ThinCrustDough();
}

public Sauce createSauce() {
return new MarinaraSauce();
}

public Cheese createCheese() {
return new ReggianoCheese();
}

public Veggies[] createVeggies() {
Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
return veggies;
}

public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}

public Clams createClam() {
return new FreshClams();
}
}


现在工厂已经准备就绪,但是我们需要重做披萨,好让它可以使用工厂生产出来的原料(原先我们是在Pizza类的子类的构造函数中中直接添加原料,没有使用工厂)。



具体的披萨就继承上述抽象类。注意,这里使用了原料工厂来创建各种原料,实际上就是一种简单工厂的应用

public class ClamPizza extends Pizza {
PizzaIngredientFactory ingredientFactory;

public ClamPizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}

void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
clam = ingredientFactory.createClam();
}
}


现在我们只需告诉每个分店使用哪里的原料工厂就可以了。而分店依然是使用工厂方法来实现的

public class BJPizzaStore extends PizzaStore {

protected Pizza createPizza(String item) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory =
new BJPizzaIngredientFactory();

if (item.equals("cheese")) {

pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");

} else if (item.equals("veggie")) {

pizza = new VeggiePizza(ingredientFactory);
pizza.setName("New York Style Veggie Pizza");

} else if (item.equals("clam")) {

pizza = new ClamPizza(ingredientFactory);
pizza.setName("New York Style Clam Pizza");

} else if (item.equals("pepperoni")) {

pizza = new PepperoniPizza(ingredientFactory);
pizza.setName("New York Style Pepperoni Pizza");

}
return pizza;
}
}


现在为止,虽然我们做了很多改变(加了原料工厂),但是客户订购披萨时的流程依然完全一样,这也是我们要使用设计模式的原因。

总结一下抽象工厂模式。





角色及职责

抽象工厂(Abstract Factory):定义一个接口,声明生成一系列抽象产品的方法
具体工厂(Concrete Factory):执行生成一系列抽象产品的方法,生成一系列具体的产品
抽象产品(Abstract Product):为这一系列的某一种产品声明接口
具体产品(Product):定义具体工厂生成的具体产品的对象,实现产品接口
客户(Client):我们的应用程序客户端(不要理解成人),使用抽象产品和抽象工厂生成对象。

附注:简单工厂、工厂方法、抽象工厂比较

简单工厂,工厂方法,抽象工厂都属于设计模式中的创建型模式。其主要功能都是帮助我们把对象的实例化部分抽取了出来,优化了系统的架构,并且增强了系统的扩展性。

简单工厂:用来生产同一等级结构中的任意产品。简单工厂模式的工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的对象实例。不修改代码的话,是无法扩展的。
工厂方法:用来生产同一等级结构中的固定产品。工厂方法是针对每一种产品提供一个工厂类。通过继承的方法,使用类来创建对象。在同一等级结构中,支持增加任意产品(只需要增加更多的子类就可以了)。实际上,工厂方法只不过就是通过子类来创建对象,它只负责将客户从具体类型中解耦。它只不过创建一个产品,因此只需要一个方法就可以了。它作用于类模式,编程简单,但不太灵活。
抽象工厂:用来生产不同产品族的全部产品。抽象工厂是应对产品族概念的,它通过对象的组合(例如在PizzaStore中持有一个PizzaIngredientFactory的引用),使用对象来创建对象。它提供一个用来创建一个产品家族的抽象类型,这个类型的子类定义了产品具体被产生的方法,而具体工厂经常使用工厂方法来创建他们的产品。要使用这个工厂,必须先实例化它,然后将它传给一些针对这个抽象类型所写的代码中。比如说,每个汽车公司可能要同时生产轿车,货车,客车,那么每一个工厂都要有创建轿车,货车和客车的方法。应对产品族概念而生,增加新的产品线很容易,但是无法增加新的产品。因为万一要扩展这组相关产品,就不得不改变接口(如PizzaIngredientFactory),而改变接口意味着必须深入改变每个子类的接口!

小结

工厂模式中,重要的是工厂类,而不是产品类。产品类可以是多种形式,多层继承或者是单个类都是可以的。但要明确的,工厂模式的接口只会返回一种类型的实例,这是在设计产品类的时候需要注意的,最好是有父类或者共同实现的接口。使用工厂模式,返回的实例一定是工厂创建的,而不是从其他对象中获取的。工厂模式返回的实例可以不是新创建的,返回由工厂创建好的实例也是可以的。

以上三种工厂方法在等级结构和产品族这两个方向上的支持程度不同。所以要根据情况考虑应该使用哪种方法。


单件模式

意图:单件模式保证应用只有一个全局惟一的实例,并且提供一个访问它的全局访问点。

单件模式讲得就是如何实例化“唯一的对象”!但这有什么用呢?有些时候我们只需要一个,比如说:缓存、对话框、注册表的对象、日志对象等。的确,就算不用单件模式,我们也可以通过静态类变量、静态方法和适当的访问修饰符来实现相同的功能,但了解单件的运作方式仍然很有用。



结构:包括防止其他对象创建实例的私有构造函数、保存惟一实例的私有变量和全局访问接口等。

效果:单件提供了全局惟一的访问入口,因此易于控制可能发生的冲突。单件是对类静态函数的一种改进,首先它避免了全局变量对系统的污染。如果将对象赋给一个全局变量,那么必须在程序一开始就创建好对象,若这个对象非常消耗资源,而程序在这次执行过程中又一直没有用到它,就形成了浪费;其次正常类可以有子类,可以定义虚函数,具有多态性。而类中的静态方法是不能定义为虚函数的,因此不具有多态性。单件模式可以扩展为多件,即允许有受控的多个实例存在。

适用场合:当类只能有一个实例存在,并且可以在全局访问时。这个惟一的实例应该可以通过子类实现扩展,并且用户无须更改代码即可使用。我们前面介绍的工厂类经常被实例化为全局惟一的单件,可能的单件还有管理日志的对象、关键字生成对象和外部设备接口对象等。

实现

私有构造函数防止在外部实例化。
保存惟一实例的静态的私有变量。
初始化并获得惟一实例的静态方法。

正如上所述,单件没有公开的构造器,那它究竟是怎么被实例化的呢?外人为了取得它的实例,必须“请求”得到一个实例,而不是自行实例化。单件包含一个静态方法,叫做getInstance(),调用这个静态方法,它就立刻现身,随时可以工作。事实上,它可能是在这次调用的时候被创建出来的,也可能是以前早就被创建出来了。
经典的单件模式实现:
public class Singleton {
private static Singleton uniqueInstance;

// other useful instance variables here

private Singleton() {}

public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}

// other useful methods here
}
但是,这种实现只在单线程的情况下是安全的。如果涉及到多线程,就有可能得到两个实例了。考虑如下一种情况,我们的巧克力工厂需要唯一一个锅炉,但是如果存在两个线程都要请求这个锅炉变量(并且是第一次请求,这意味着之前并没有实例化巧克力锅炉),就可能会造成返回两个巧克力锅炉实例:



处理多线程
只要把getInstance()变成同步(synchronized)方法,多线程灾难几乎就可以轻易解决了:
public class Singleton {
private static Singleton uniqueInstance;

// other useful instance variables here

private Singleton() {}

public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}

// other useful methods here
}


通过增加synchronized关键字到getInstance()方法中,我们迫使每个线程在进入这个方法之前,要等候别的线程先离开该方法,也就是说不会有两个线程可以同时进入这个方法。但是这么做有一点不好,就是同步会降低性能(因为线程需要等候别的线程)。而且我们需要注意:只有第一次执行此方法时才需要同步。换句话说,一旦设置好uniqueInstance变量,就不再需要同步这个方法了。因为这个问题,我们需要重新衡量一下:

如果getInstance()的性能对应用程序不是很关键,就不要管它了。同步getInstance()方法既简单又有效。但是你必须意识到,同步一个方法可能造成程序执行效率下降100倍!因此,如果你的程序需要频繁使用getInstance()方法,你就得重新考虑了。
使用“急切”创建实例,而不用延迟实例化的方法。如果应用程序总是创建并使用单件实例,你可能想要急切地创建此单件,如下所示:
public class Singleton {
private static Singleton uniqueInstance = new Singleton();

private Singleton() {}

public static Singleton getInstance() {
return uniqueInstance;
}
}
利用这个做法,我们在加载这个类时马上创建唯一的单件实例,就确保了任何线程访问uniqueInstance静态变量之前,一定先创建此实例。
用“双重检查加锁”,在getInstance()中减少使用同步。使用双重检查加锁(double-checked locking),首先检查是否已经创建了一个实例,如果尚未创建,才进行同步。这样一来,只有第一次会同步。代码如下:
public class Singleton {
private volatile static Singleton uniqueInstance;

private Singleton() {}

public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
它的原理是:第一次检查实例,如果不存在,就进入同步区块(只有第一次才执行到这里)。进入区块后,再检查一次。如果仍是null,才创建实例。volatile关键词确保:当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量。但是要注意,1.4及更早版本的Java中部支持volatile关键字!

比较:

与全局变量的比较:单件模式维护自身的实例化,在使用时是安全的。一个全局对象无法自行维护,也就无法避免重复创建多个实例,系统资源会被大量占用。更糟糕的是在很多情况下会出现逻辑问题,当这些对象访问相同的资源(例如串口时)时,会发生访问冲突。除此之外,单件还可以延迟实例化,即只有在使用的时候才会实例化。
与实用类静态方法的比较:实用类提供系统公用的静态方法,并且也经常采用私有化的构造函数。与单件不同,它没有实例,其中的方法全部是静态方法。

(1)实用类不保存状态,仅提供功能。

(2)实用类不具有多态性,而单件可以有子类。

(3)单件是对象,实用类只是方法的集合。

实现要点:

Singleton模式是限制而不是改进类的创建。
Singleton类中的实例构造器可以设置为Protected以允许子类派生。
Singleton模式一般不要支持Icloneable接口,因为这可能导致多个对象实例,与Singleton模式的初衷违背。
Singleton模式一般不要支持序列化,这也有可能导致多个对象实例,这也与Singleton模式的初衷违背。
Singleton只考虑了对象创建的管理,没有考虑到销毁的管理,就支持垃圾回收的平台和对象的开销来讲,我们一般没必要对其销毁进行特殊的管理。
理解和扩展Singleton模式的核心是"如何控制用户使用new对一个类的构造器的任意调用"。
可以很简单的修改一个Singleton,使它有少数几个实例,这样做是允许的而且是有意义的。

优点 :

实例控制:Singleton 会阻止其他对象实例化其自己的 Singleton 对象的副本,从而确保所有对象都访问唯一实例
灵活性:因为类控制了实例化过程,所以类可以更加灵活修改实例化过程

缺点 :

开销:虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题,上面的五种实现方式中已经说过了。
可能的开发混淆:使用 singleton 对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用 new 关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
对象的生存期:Singleton 不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于 .NET Framework 的语言),只有 Singleton 类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致 Singleton 类中出现悬浮引用。

适用性 :

当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。


生成器模式

在软件设计中,有时候面临着一个非常复杂的对象的创建工作。这个复杂的对象通常可以分成几个较小的部分,由各个子对象组合出这个复杂对象的过程相对来说比较稳定,但是子对象的创建过程各不相同并且可能面临变化。根据OOD中的OCP(开闭原则),我们自然应该对这些子对象的创建过程进行变化封装。这就是生成器模式的思路。定义一个抽象的建造者的角色(Builder),规定所有具体的建造者都应该具有的功能——这些功能就是如何创建复杂对象的某个特定部分(子对象),而具体如何创建子对象有具体的创建者实现。再定义一个指导者的角色,它把创建者作为工具,知道如何使用这个工具来创建复杂对象。这样,客户在需要创建这个复杂对象的时候,只需要给指导者一个具体的创建者就可以了。至于具体创建者如何创建子对象的细节以及这些子对象之间的差异,不是指导者,也不是客户关心的。

意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示(GoF)。



协同图:



角色及指责

Builder:抽象生成器角色,这是一个抽象接口,定义了创建负责对象的各个组成部分的公共接口。这个接口应该足够普遍,以便具体的生成器能够通过实现这个抽象接口的不同方式和不同部分生成不同的产品。
ConcreateBuilderA:具体生成器角色,继承并实现抽象生成器的接口,完成具体产品部件的建造。除此以外,具体生成器还需要额外提供一个检索产品的方法,以便指导者能在产品生成后获得它。这个方法通常包含的是复杂产品的装配逻辑。
Director:指导者角色,指导具体生成器来一步一步完成产品的创建。需要特别指出的是,指导者并不需要指导具体生成器生成的产品,它只需要了解如何使用生成器制造产品,制造出的产品的细节,只有具体生成器才知道。
Product:产品,被创建出来的复杂的产品。由ConcreteBuilder创建这个复杂产品的内部表示并定义它的装配过程。通常,Product定义了它的组成部件的类,以及将这些部件装配成最终产品的接口。一个系统中可能还有多个Product类,而且这些类之间不一定会有共同的接口,可以是完全互不相关的。

现在考虑我们有一个度假村,假期到了有很多游客要来我们的度假村独家,而你负责给顾客提出一个旅游规划,例如第一天找宾馆,第二天买票去游乐园等等。但是所有人的旅游规划流程是一样的:先决定哪一天开始,然后确定一个旅店,再买票,只是每个人开始的时间可能不一样,定的旅店不一样,要参观的景点不一样。这时,我们就可以创建一个接口类AbstractBuilder,再让客户继承并创建具体的生成器。



实现:

一般首先定义一个抽象接口,为每个部件的创建定义一个方法,以便指导者Director来调用。
一个具体的生成器负责实现这些方法。

适用范围:生成器模式用于分步骤构建一个复杂的对象,如何划分步骤是一个稳定的算法,而复杂对象的各个部分的创建过程则经常变化。通过把各部件的创建过程封装到生成器中,使得复杂对象的创建过程和最终表象实现分分离。使用生成器模式,隐藏了具体产品的表示方法、内部结构和装配过程。通过定义一个新的生成器,就可以改变产品的内部表示。生成器模式中的指导者角色控制着生成器生成部件的过程。因此,通过指导者,可以实现对复杂产品生成步骤进行精细的控制——这一点在复杂产品部件的生成必须遵循一定的次序时显得十分方便。

当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
当构造过程必须允许被构造的对象有不同的表示时。

优点:

将一个复杂对象的创建过程封装起来。
允许对象通过多个步骤来创建,并且可以改变过程(这和只有一个步骤的工厂模式不同)。
向客户隐藏内部的实现。
产品的实现可以被替换,因为客户只看到一个抽象的接口。因此可以轻松地改变产品的内部实现。

缺点:

经常被用来创建组合结构。
与工厂模式相比,采用生成器模式创建对象的客户,需要具备更多的领域知识。因为不同生成器产生的对象可能不属于同一类型,因此使用生成器的客户必须知道产品的具体类型。这意味着生成器经常不能互换,不同的生成器针对的客户程序也不相同。

附注:与抽象工厂模式的比较

生成器模式关注于将构造对象的过程和构造的各个部分分开,而抽象工厂关注于构建一个产品系列。实际上,最大的区别是生成器模式创建的产品不一定有共同的父类,只要有类似的构造过程即可。实际上我们常见到的文件资源管理器的实现完全可以使用生成器模式。

生成器模式和工厂模式很相象,但是它们有很明显的区别。那就是工厂模式只是根据给的参数不同,工厂"生产"并返回不同的对象。生成器模式除了根据不同参数"生产"不同对象外,这些不同的对象还包含着不同的数据。生成器模式比工厂模式复杂就复杂在多"数据"这一部分。

注意问题

装配和构造接口 :生成器逐步的构造它们的产品。因此Builder类接口必须足够普遍,以便为各种类型的具体生成器构造产品。
没有抽象类 :通常情况下,由具体生成器生成的产品,它们的表示相差是如此之大以至于给不同的产品以公共父类没有太大意思。 (与抽象工厂的明显区别)
在Builder中却省的方法为空:定义为空方法可以使客户只重定义他们所感兴趣的操作。


原型模式

定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。



——————————————————————————————————————



意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

角色及职责:

客户(Client)角色:客户类提出创建对象的请求。

抽象原型(Prototype)角色:这是一个抽象角色,通常由一个C#接口或抽象类实现。此角色给出所有的具体原型类所需的接口。在C#中,抽象原型角色通常实现了ICloneable接口。

具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象原型角色所要求的接口

原型管理器(Prototype Manager)角色:创建具体原型类的对象,并记录每一个被创建的对象

优点:引入Prototype模式后不再需要一个与具体产品等级结构平行的工厂方法类,减少了类的构造,同时客户程序可以在运行时刻建立和删除原型(自定义界面时,此点尤其重要)。

要实现深拷贝,可以通过序列化的方式。抽象类及具体类都必须标注为可序列化的[Serializable]

现在考虑我们要开一个Party,party上要分发给宾客每人一把勺子和叉子,但是有的人要汤勺(喝汤)和吃沙拉的叉子,有的人不用汤勺只用吃沙拉的勺子和叉子。这时我们可以创建两个原型管理器来分别创建这两对对象。

我们的原型管理器(Prototype Manager)如下:

public class PrototypeFactory {
AbstractSpoon prototypeSpoon;
AbstractFork prototypeFork;
public PrototypeFactory(AbstractSpoon spoon, AbstractFork fork) {
prototypeSpoon = spoon;
prototypeFork = fork;
}
public AbstractSpoon makeSpoon() {
return (AbstractSpoon)prototypeSpoon.clone();
}
public AbstractFork makeFork() {
return (AbstractFork)prototypeFork.clone();
}
}
两种抽象原型(Prototype),即勺子和叉子分别如下:

public abstract class AbstractSpoon implements Cloneable {
String spoonName;
public void setSpoonName(String spoonName) {
this.spoonName = spoonName;
}
public String getSpoonName() {
return this.spoonName;
}
public Object clone() {
Object object = null;
try {
object = super.clone();
}catch (CloneNotSupportedException exception){ 	System.err.println("AbstractSpoon is not Cloneable"); }
return object;
}
}


public abstract class AbstractFork implements Cloneable {
String forkName;
public void setForkName(String forkName) {
this.forkName = forkName;
}
public String getForkName() {
return this.forkName;
}
public Object clone() {
Object object = null;
try {
object = super.clone();
}catch (CloneNotSupportedException exception)
{ System.err.println("AbstractFork is not Cloneable"); }
return object;
}
}
每种抽象原型都对应了一个或多个具体原型(Concrete Prototype):

public class SoupSpoon extends AbstractSpoon {
public SoupSpoon() { setSpoonName("Soup Spoon"); }
}
public class SaladSpoon extends AbstractSpoon {
public SaladSpoon() { setSpoonName("Salad Spoon"); }
}
public class SaladFork extends AbstractFork {
public SaladFork() { setForkName("Salad Fork"); }
}
最后是我们的客户(Client)角色,它提出创建对象的请求:

class TestPrototype {
public static void main(String[] args) {
System.out.println( "Creating a Prototype Factory with a SoupSpoon and a SaladFork");
PrototypeFactory prototypeFactory = new PrototypeFactory(new SoupSpoon(), new SaladFork());
AbstractSpoon spoon = prototypeFactory.makeSpoon();
AbstractFork fork = prototypeFactory.makeFork();

System.out.println( "Creating a Prototype Factory with a SaladSpoon and a SaladFork");
prototypeFactory = new PrototypeFactory(new SaladSpoon(), new SaladFork());
spoon = prototypeFactory.makeSpoon();
fork = prototypeFactory.makeFork();
}
}


实现要点:

使用原型管理器,体现在一个系统中原型数目不固定时,可以动态的创建和销毁。
实现克隆操作,在.NET中可以使用Object类的MemberwiseClone()方法来实现对象的浅表拷贝或通过序列化的方式来实现深拷贝。
Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。

效果:

它对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目。
Prototype模式允许客户只通过注册原型实例就可以将一个具体产品类并入到系统中,客户可以在运行时刻建立和删除原型。
减少了子类构造,Prototype模式是克隆一个原型而不是请求工厂方法创建一个,所以它不需要一个与具体产品类平行的Creater类层次。
Portotype模式具有给一个应用软件动态加载新功能的能力。由于Prototype的独立性较高,可以很容易动态加载新功能而不影响老系统。
产品类不需要非得有任何事先确定的等级结构,因为Prototype模式适用于任何的等级结构
Prototype模式的最主要缺点就是每一个类必须配备一个克隆方法。而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事。

适用性:

当一个系统应该独立于它的产品创建,构成和表示时;
当要实例化的类是在运行时刻指定时,例如,通过动态装载;
为了避免创建一个与产品类层次平行的工厂类层次时;
当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

附注

Prototype模式同工厂模式,同样对客户隐藏了对象的创建工作,但是,与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的,达到了"隔离类对象的使用者和具体类型(易变类)之间的耦合关系"的目的。

此处引入的知识点式浅拷贝与深拷贝的问题:

概念:

a.浅拷贝(Shallow Copy影子克隆):只复制对象的基本类型,对象类型,仍属于原来的引用。

b.深拷贝(Deep Copy 深度克隆):不紧复制对象的基本类,同时也复制原对象中的对象.完全产生新对象。

实现机制:

2.深拷贝与浅拷贝实现机制:

对于值类型:

a.浅拷贝: 通过赋值等操作直接实现,将对象中的值类型的字段拷贝到新的对象中。

b.深拷贝:通过赋值等操作直接实现,将对象中的值类型的字段拷贝到新的对象中。 和浅拷贝相同

对于引用类型:

a.浅拷贝: MemberwiseClone 方法创建一个浅副本,方法是创建一个新对象,如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用原始对象,与原对象引用同一对象。

b.深拷贝:拷贝对象应用,也拷贝对象实际内容,也就是创建了一个新的改变新对象 不会影响到原始对象的内容

这种情况需要为其实现ICloneable接口中提供的Clone方法。

差别就是在对于引用类型的实现深拷贝和浅拷贝的时候的机制不同,前者是MemberwiseClone 方法实现,后者是通过继承实现ICloneable接口中提供的Clone方法,实现对象的深拷贝。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: