生成器模式:将一个复杂对象的构建与呈现分开,以便相同的构造过程能够根据不同需要创建不同的形式。
工厂方法模式:提供一个创建一个示例的接口,但是允许子类决定实例化哪个类,即不同的子类可以实例化不同的对象。
原型模式:先实例化一个类,然后克隆或者拷贝该类来构建新的实例。可以用共有方法进一步修改这些实例。
单件模式:某个类只能有一个实例。提供一个全局访问点。(可拓展到有限个实例)
意图:提供一个类,由它负责根据一定的条件创建某一具体类的实例
考虑我们开了一家披萨店,客户可以通过调用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;
}
}
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)时(如编写AnOperation()方法时),不需要知道实际创建的产品是哪一个。选择了使用哪个子类,自然就决定了实际创建的产品是什么。
依然考虑我们的披萨店。现在生意很好,我们有资本去开分店了!比如我们希望去北京、杭州、济南开不同的分店,但是由于存在地区差异,每家分店可能想要提供不同风味的批塞。如果使用简单工厂,我们可以写出三种不同的工厂,分别是BeijingPizzaFactory、HangzhouPizzaFactory、JinanPizzaFactory,那就会变成下面这个样子。
SimplePizzaFactory bjFactory = new BeijingPizzaFactory();
PizzaStore bjStore = new PizzaStore(bjFactory);
SimplePizzaFactory hzFactory = new HangzhouPizzaFactory();
PizzaStore hzStore = new PizzaStore(hzFactory);
这个框架就是我们定义的一个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;
}
}
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;
}
}
除此之外,我们的披萨的种类也更多了,因为不同地区又研发出了不同口味的披萨,而且制作方法也可能发生了改变。
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");
}
}
与简单工厂相比,它们之间的差异如下:
总结一下工厂方法。
优点:遵守了依赖倒置原则:要依赖抽象,不要依赖具体的类。为了避免违反依赖倒置原则,我们可以在设计中尽量遵守以下规定:1)变量不可以持有具体类的引用。如果使用new,就会持有具体类的引用,可以使用工厂来避免;2)不要让类派生自具体类。如果派生自具体的类,就会依赖具体类。请派生自一个抽象(接口或抽象类);3)不要覆盖基类中已经实现的方法。如果覆盖基类已实现的方法,那么说明基类并不是一个真正适合被继承的抽象。
这里还体现了一个观点:平行的类层次。
实现要点:
效果:
适用性:
附注:
意图:提供一个接口,用于创建一系列相关或相互依赖对象的家族,而无需明确指定具体的类。与工厂方法类似,但此处返回的一系列相关产品。实现过程同样推延到子系列类去实现。与工厂方法的区别在于他们的层次模型。工厂方法的抽象基类只有儿子,而抽象工厂模式却是有孙子,而且每个儿子的儿子们之间有相互关联依赖关系。
好吧,我们还是得说我们的披萨店。现在分店已经开好了,但是新的问题又出现了:原料问题。我们总不能让一家原料工厂供应所有的分店!因为虽然大家都使用那么几种原料:面团,酱料,芝士,蔬菜,肉等,但是原料的制作方式会根据区域的不同而有差异(比如四川人喜欢吃辣的,可能我们的酱料就要使用辣酱,而上海人不吃辣的,所以就使用一般的酱料)。
这里就有了一个新的概念:原料家族。北京分店使用一组原料,而杭州使用另一组原料。这些原料品种相同(每个家族都包含了一种面团,一种酱料,一种芝士等等),但实现方法不同而已。每个区域对应了一套完整的原料家族。于是我们考虑在原料方面使用“抽象工厂”!
首先为工厂定义一个接口,负责创建所有的原料:
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();
}
}
具体的披萨就继承上述抽象类。注意,这里使用了原料工厂来创建各种原料,实际上就是一种简单工厂的应用。
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;
}
}
总结一下抽象工厂模式。
角色及职责:
附注:简单工厂、工厂方法、抽象工厂比较
简单工厂,工厂方法,抽象工厂都属于设计模式中的创建型模式。其主要功能都是帮助我们把对象的实例化部分抽取了出来,优化了系统的架构,并且增强了系统的扩展性。
小结:
以上三种工厂方法在等级结构和产品族这两个方向上的支持程度不同。所以要根据情况考虑应该使用哪种方法。
意图:单件模式保证应用只有一个全局惟一的实例,并且提供一个访问它的全局访问点。
单件模式讲得就是如何实例化“唯一的对象”!但这有什么用呢?有些时候我们只需要一个,比如说:缓存、对话框、注册表的对象、日志对象等。的确,就算不用单件模式,我们也可以通过静态类变量、静态方法和适当的访问修饰符来实现相同的功能,但了解单件的运作方式仍然很有用。
结构:包括防止其他对象创建实例的私有构造函数、保存惟一实例的私有变量和全局访问接口等。
效果:单件提供了全局惟一的访问入口,因此易于控制可能发生的冲突。单件是对类静态函数的一种改进,首先它避免了全局变量对系统的污染。如果将对象赋给一个全局变量,那么必须在程序一开始就创建好对象,若这个对象非常消耗资源,而程序在这次执行过程中又一直没有用到它,就形成了浪费;其次正常类可以有子类,可以定义虚函数,具有多态性。而类中的静态方法是不能定义为虚函数的,因此不具有多态性。单件模式可以扩展为多件,即允许有受控的多个实例存在。
适用场合:当类只能有一个实例存在,并且可以在全局访问时。这个惟一的实例应该可以通过子类实现扩展,并且用户无须更改代码即可使用。我们前面介绍的工厂类经常被实例化为全局惟一的单件,可能的单件还有管理日志的对象、关键字生成对象和外部设备接口对象等。
实现:
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
}
但是,这种实现只在单线程的情况下是安全的。如果涉及到多线程,就有可能得到两个实例了。考虑如下一种情况,我们的巧克力工厂需要唯一一个锅炉,但是如果存在两个线程都要请求这个锅炉变量(并且是第一次请求,这意味着之前并没有实例化巧克力锅炉),就可能会造成返回两个巧克力锅炉实例:
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
}
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
利用这个做法,我们在加载这个类时马上创建唯一的单件实例,就确保了任何线程访问uniqueInstance静态变量之前,一定先创建此实例。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关键字!比较:
实现要点:
优点 :
缺点 :
适用性 :
在软件设计中,有时候面临着一个非常复杂的对象的创建工作。这个复杂的对象通常可以分成几个较小的部分,由各个子对象组合出这个复杂对象的过程相对来说比较稳定,但是子对象的创建过程各不相同并且可能面临变化。根据OOD中的OCP(开闭原则),我们自然应该对这些子对象的创建过程进行变化封装。这就是生成器模式的思路。定义一个抽象的建造者的角色(Builder),规定所有具体的建造者都应该具有的功能——这些功能就是如何创建复杂对象的某个特定部分(子对象),而具体如何创建子对象有具体的创建者实现。再定义一个指导者的角色,它把创建者作为工具,知道如何使用这个工具来创建复杂对象。这样,客户在需要创建这个复杂对象的时候,只需要给指导者一个具体的创建者就可以了。至于具体创建者如何创建子对象的细节以及这些子对象之间的差异,不是指导者,也不是客户关心的。
意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示(GoF)。
协同图:
角色及指责:
适用范围:生成器模式用于分步骤构建一个复杂的对象,如何划分步骤是一个稳定的算法,而复杂对象的各个部分的创建过程则经常变化。通过把各部件的创建过程封装到生成器中,使得复杂对象的创建过程和最终表象实现分分离。使用生成器模式,隐藏了具体产品的表示方法、内部结构和装配过程。通过定义一个新的生成器,就可以改变产品的内部表示。生成器模式中的指导者角色控制着生成器生成部件的过程。因此,通过指导者,可以实现对复杂产品生成步骤进行精细的控制——这一点在复杂产品部件的生成必须遵循一定的次序时显得十分方便。
优点:
附注:与抽象工厂模式的比较
生成器模式关注于将构造对象的过程和构造的各个部分分开,而抽象工厂关注于构建一个产品系列。实际上,最大的区别是生成器模式创建的产品不一定有共同的父类,只要有类似的构造过程即可。实际上我们常见到的文件资源管理器的实现完全可以使用生成器模式。
生成器模式和工厂模式很相象,但是它们有很明显的区别。那就是工厂模式只是根据给的参数不同,工厂"生产"并返回不同的对象。生成器模式除了根据不同参数"生产"不同对象外,这些不同的对象还包含着不同的数据。生成器模式比工厂模式复杂就复杂在多"数据"这一部分。
注意问题:
定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
——————————————————————————————————————
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
角色及职责:
优点:引入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();
}
}
实现要点:
效果:
适用性:
附注:
Prototype模式同工厂模式,同样对客户隐藏了对象的创建工作,但是,与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的,达到了"隔离类对象的使用者和具体类型(易变类)之间的耦合关系"的目的。
此处引入的知识点式浅拷贝与深拷贝的问题:
概念:
a.浅拷贝(Shallow Copy影子克隆):只复制对象的基本类型,对象类型,仍属于原来的引用。
b.深拷贝(Deep Copy深度克隆):不紧复制对象的基本类,同时也复制原对象中的对象.完全产生新对象。
实现机制:
2.深拷贝与浅拷贝实现机制:
对于值类型:
a.浅拷贝:通过赋值等操作直接实现,将对象中的值类型的字段拷贝到新的对象中。
b.深拷贝:通过赋值等操作直接实现,将对象中的值类型的字段拷贝到新的对象中。和浅拷贝相同
对于引用类型:
a.浅拷贝:MemberwiseClone方法创建一个浅副本,方法是创建一个新对象,如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用原始对象,与原对象引用同一对象。
b.深拷贝:拷贝对象应用,也拷贝对象实际内容,也就是创建了一个新的改变新对象不会影响到原始对象的内容
这种情况需要为其实现ICloneable接口中提供的Clone方法。
差别就是在对于引用类型的实现深拷贝和浅拷贝的时候的机制不同,前者是MemberwiseClone 方法实现,后者是通过继承实现ICloneable接口中提供的Clone方法,实现对象的深拷贝。