这篇文章总结的主要是工厂方法和抽象工厂,顺带简单工厂这种编程习惯
一、简单工厂
简单工厂并不算是一种设计模式,他更像一种编程习惯,并没有严格的遵守开放关闭原则,而且他仅仅只是把要改变的部分跟不变的部分分离开,但是把具体产品的创造过程封装起来,客户端程序猿就不需要直接操作一堆具体子类,在知道最少的情况下达到目标,不必考虑这个类是怎么被创建出来的,降低了程序的耦合。
我们看看wiki百科的一段话:普通的工厂方法模式通常伴随着对象的具体类型与工厂具体类型的一一对应,客户端代码根据需要选择合适的具体类型工厂使用。然而,这种选择可能包含复杂的逻辑。这时,可以创建一个单一的工厂类,用以包含这种选择逻辑,根据参数的不同选择实现不同的具体对象。这个工厂类不需要由每个具体产品实现一个自己的具体的工厂类,所以可以将工厂方法设置为静态方法。 而且,工厂方法封装了对象的创建过程。如果创建过程非常复杂(比如依赖于配置文件或用户输入),工厂方法就非常有用了。
假设Pizza为产品抽象,它下层有各种具体子类Pizza,当客人预定Pizza的时候,提供一个类图:
看起来就像是SimpleFactory托管了创建过程
public abstract class Pizza { public void prepare(){System.out.println("正在准备Pizza...");} public void bake(){System.out.println("正在烘培...");} public void cut(){System.out.println("正在切片...");} public void box(){System.out.println("正在装箱...");} } public class PepperoniPizza extends Pizza { @Override public void prepare() {System.out.println("正在准备PepperoniPizza...");} @Override public void bake() {System.out.println("正在烘培PepperoniPizza...");} @Override public void cut() {System.out.println("正在切片PepperoniPizza...");} @Override public void box() {System.out.println("正在装箱PepperoniPizza...");} }其他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 class PizzaStore { private 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; } }
能注意到的是,即使他仅仅是把创建的代码搬到了另外一个地方(其实实质也是这样子),看上去问题并没有解决,但需要考虑到加入创建的逻辑非常复杂,而我们需要给客户程序员提供方便,这样的方法就很有力量了,一般来说简单工厂是声明为静态的,这样子的缺点就是不能通过继承来改变创建方法的行为
优点:
1、责任分割,客户端程序猿直接被免除了创建产品的责任,决定在什么时候创建子类
2、对客户端来说,减少了记忆创建产品逻辑过程的记忆负担
3、通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性
缺点:
1、除了无法继承,但是逻辑复杂,一旦不能正常工作,整个系统都要受到影响
2、逻辑过于复杂,产品过多的时候,产品维护的花销较大
所以,当我们考虑使用简单工厂的时候,必须要考虑到它的执行逻辑、产品数目规模是不大的
二、工厂方法(Factory Method)
首先看看工厂方法的定义:工厂方法定义一个用于创建对象的接口,让子类去决定实例化哪一个类,使其延迟实例化,也叫虚构造器(Virtual Constructor),这个名字更针对C++
创造工厂方法的动机,下面这段话来自《设计模式》:
考虑这样一个应用框架,它可以向用户显示多个文档。在这个框架中,两个主要的抽象是Application和Document。这两个类都是抽象的,客户必须通过他们的子类来做以应用相关的实现。例如,为创建一个绘图应用,我们定义类DrawingApplication和DrawingDocument。Application负责管理Document并根据需要创建他们——例如,用户从菜单中选择open或者new的时候。因为被实例化的特定Document子类是与特定应用相关的,所以Application类不可能预测到哪一个Document子类将被实例化——Application仅仅知道一个新的文档被创建,而不知道哪一种Document被创建。这就产生了一个尴尬的局面:框架必须被实例化,但是他只知道不能被实例化的抽象类。
这段话包含了一个重要的信息:工厂方法让子类决定要实例化的类,它本身并不知道要实例化哪一个
关于这句话:《Head First 设计模式》中这样阐释:所谓的“决定”,并不是指模式允许子类本身在运行时做决定,而是指在编写创建者类的时候,不需要知道实际创建的产品是哪一个。选择了使用哪一个子类,自然就决定了实际创建的产品是什么
看看工厂方法的类图:
我们试试把上面Pizza的例子改写:简单工厂允许我们生产cheese、viggie、clam、prpperoni类型的pizza,这都是把变化的部分封装到SimpleFactory里面去,现在需求变成这样,口味还是这样子的口味,但是我们需要不同的Pizza风格——NewYork的和Chicago风格的,每种风格下有这些不同的口味:(参数化工厂方法)
下面是代码:注意跟简单工厂比较,思考哪里让“子类决定要实例化的类”
这是实体类,只列举Pizza抽象类跟两个风格类,其他不一一列举
public abstract class Pizza { public void prepare(){System.out.println("正在准备Pizza...");} public void bake(){System.out.println("正在烘培...");} public void cut(){System.out.println("正在切片...");} public void box(){System.out.println("正在装箱...");} } public class NYStyleVeggiePizza extends Pizza { @Override public void prepare() {System.out.println("正在准备NYStyleVeggiePizza...");} @Override public void bake() {System.out.println("正在烘培NYStyleVeggiePizza...");} @Override public void cut() {System.out.println("正在切片NYStyleVeggiePizza...");} @Override public void box() {System.out.println("正在装箱NYStyleVeggiePizza...");} } public class ChicagoStyleClamPizza extends Pizza { @Override public void prepare() {System.out.println("正在准备ChicagoStyleClamPizza...");} @Override public void bake() {System.out.println("正在烘培ChicagoStyleClamPizza...");} @Override public void cut() {System.out.println("正在切片ChicagoStyleClamPizza...");} @Override public void box() {System.out.println("正在装箱ChicagoStyleClamPizza...");} }接下来是工厂:
public abstract class PizzaStore { public Pizza orderPizza(String type){ Pizza pizza; //工厂只知道什么时候去创建子类,但是不知道创建什么子类 pizza = createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } //交给子类去实现 protected abstract Pizza createPizza(String type); }
public class NYStylePizzaStore extends PizzaStore { @Override public Pizza createPizza(String type) { Pizza pizza = null; if(type.equals("cheese")){pizza = new NYStyleCheesePizza(); }else if(type.equals("pepperoni")){pizza = new NYStylePepperoniPizza(); }else if(type.equals("clam")){pizza = new NYStyleClamPizza(); }else if(type.equals("veggie")){pizza = new NYStyleVeggiePizza();} return pizza; } } public class ChicagoStylePizzaStore extends PizzaStore { @Override public Pizza createPizza(String type) { Pizza pizza = null; if(type.equals("cheese")){pizza = new ChicagoStyleCheesePizza(); }else if(type.equals("pepperoni")){pizza = new ChicagoStylePepperoniPizza(); }else if(type.equals("clam")){pizza = new ChicagoStyleClamPizza(); }else if(type.equals("veggie")){pizza = new ChicagoStyleVeggiePizza();} return pizza; } }
可以自己写一个main方法去实现看看结果
我们来看看工厂模式的优点:《Graphic Design Patterns》
1、在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
2、基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
3、使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
缺点:
1、在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
2、由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
其实工厂方法有他的变种形式:他可以不是类,而是在一系列的Product具体子类中用static的形式定义生成这个子类的工厂方法,同时私有化Product子类的构造方法,这样子跟单例有点像,但这样就无法体现让工厂子类去确定实例化的系列具体产品类这个优点(尤其在于参数化工厂方法上),试考虑以上的Pizza例子,把工厂方法转换成方法形式放在具体子类内部。视乎情况,我们还可以使用内部类去创建工厂方法,很多时候并不是想Pizza那样具有相同的风格的一系列子类,而是每个产品都是独立的,《Thinging in Java》中在内部类这一章节曾经提及过使用内部类去实现工厂方法,这里总结一下,直接就工厂方法的Pizza改写,上代码:
先把PizzaStore的type参数去掉,因为这里并不需要创建风格系列的产品,当然,你也可以这样子干
public abstract class PizzaStore { public Pizza orderPizza(){ Pizza pizza; //工厂只知道什么时候去创建子类,但是不知道创建什么子类 pizza = createPizza(); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } //交给子类去实现 protected abstract Pizza createPizza(); }其次是在产品的内部实现工厂方法,这样子做的优点就是安全,安全,安全,而且可以让工厂托管子类被创造出来后的逻辑(prepare、bake、cut、box)
public abstract class Pizza { public void prepare(){System.out.println("正在准备Pizza...");} public void bake(){System.out.println("正在烘培...");} public void cut(){System.out.println("正在切片...");} public void box(){System.out.println("正在装箱...");} } public class CheesePizza extends Pizza { private CheesePizza(){} @Override public void prepare() {System.out.println("正在准备CheesePizza...");} @Override public void bake() {System.out.println("正在烘培CheesePizza...");} @Override public void cut() {System.out.println("正在切片CheesePizza...");} @Override public void box() {System.out.println("正在装箱CheesePizza...");} public static PizzaStore cheeseFactory = new PizzaStore() { @Override protected Pizza createPizza() { return new CheesePizza(); } }; }其他的子类都一样,使用内部类实现了factory的方法,然后写一下main方法:
public class test { public static void main(String[] args) { CheesePizza.cheeseFactory.orderPizza(); System.out.println("------------------------------"); ClamPizza.clamFactory.orderPizza(); System.out.println("------------------------------"); PepperoniPizza.pepperoniFactory.orderPizza(); System.out.println("------------------------------"); VeggiePizza.veggieFactory.orderPizza(); System.out.println("------------------------------"); } }
你会发现,这样子直接完成了orderPizza这个业务,而且有不错的安全性
总结一下,在以下情况下可以使用工厂方法模式(From wiki):
1、一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
2、一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
3、将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
工厂方法的几个效果:
1、工厂方法模式常见于工具包和框架中,在这些库中可能需要创建客户端代码实现的具体类型的对象。
2、平行的类层次结构中(一个类将它的一些职责委托给一个独立的类的时候),常常需要一个层次结构中的对象能够根据需要创建另一个层次结构中的对象。pizza与pizzastore
3、工厂方法模式可以用于测试驱动开发,从而允许将类放在测试中(这一点倒是没有去研究过= =)
任何java程序都需要创建对象,如果想着工厂模式能不创建对象那就错了,其实工厂模式好比一个栅栏,能把你的代码围起来不至于维护他们时他们到处乱跑,对象的创建是现实的。
三、抽象工厂(Abstract Factory)
——提供一个创建一系列相关或者相互依赖对象的接口,而无需制定他们所需要的类
先看看类图:
在以下情况考虑使用抽象工厂:《Graphic Design Patterns》
1、一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
2、系统中有多于一个的产品族,而每次只使用其中某一产品族。
3、属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
4、系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
当我们在Pizza上引入产品族时,dough、sauce、cheese、pepperoni、clam、veggies,并用这些产品来组装一个Pizza——在之前的例子里面,如果说NYStyleXXXXPizza之类属于一个产品等级结构,那么这些用来组合一个Pizza的原料就属于一个产品族,在前面我们通过store来生产一个产品等级结构下的产品,而现在抽象工厂给我们的是,用一个产品族来生产一个产品。——一个针对产品等级结构、一个针对产品族,以下的例子除了上面的工厂方法,还引入了抽象工厂:
代码:
当然要提供一组原料接口:
public interface Dough { //对于其他的接口也一样被没有特殊的操作 } //工厂抽象里面提供这一组原料的实现描述 public interface PizzaIngredientFactory { public Dough createDough(); public Sauce createSauce(); public Cheese createCheese(); public Clams createClams(); public Veggies[] createVeggies(); public Pepperoni createPepperoni(); }定义实体类,描述pizza需要的材料,以下提供了一个具体子类,还有其他的构造基本相同,具体子类的实现并没有定义具体工厂,因为这里思想是,你传进来什么工厂,它便用什么工厂来生产原料,由选择者去决定
public abstract class Pizza { public Dough dough; public Sauce sauce; public Veggies veggies[]; public Cheese cheese; public Pepperoni pepperoni; public Clams clams; public abstract void prepare(); public void bake(){System.out.println("正在烘培...");} public void cut(){System.out.println("正在切片...");} public void box(){System.out.println("正在装箱...");} } public class NYStyleCheesePizza extends Pizza { PizzaIngredientFactory ingraedientFactory; public NYStyleCheesePizza(PizzaIngredientFactory ingraedientFactory) { this.ingraedientFactory = ingraedientFactory; } @Override public void prepare() { System.out.println("正在准备NYStyleCheesePizza..."); dough = ingraedientFactory.createDough(); sauce = ingraedientFactory.createSauce(); cheese = ingraedientFactory.createCheese(); } @Override public void bake() {System.out.println("正在烘培NYStyleCheesePizza...");} @Override public void cut() {System.out.println("正在切片NYStyleCheesePizza...");} @Override public void box() {System.out.println("正在装箱NYStyleCheesePizza...");} }
接下来实现抽象工厂来创造原料的产品族:这里写一个NY的工厂,Chicago的类似:
public class NYPizzaIngredientFactory implements PizzaIngredientFactory { @Override public Dough createDough() {return new ThinCrustDough();} @Override public Sauce createSauce() {return new MarinaraSauce();} @Override public Cheese createCheese() {return new ReggianoCheese();} @Override public Clams createClams() {return new FreshClams();} @Override public Pepperoni createPepperoni() {return new NYPepperoni();} @Override public Veggies[] createVeggies() { Veggies veggies[] = {new Garlic(),new Onion(),new Mushroom(),new RedPepper()}; return veggies; } }我们可以看到具体工厂上实现了创建一整套原料的方法,我们使用它时,可以调用这些方法来生产一套产品族
在Store中,他们决定了pizza的生产过程,当然这里也上上面工厂方法的体现处,这跟抽象工厂并不冲突,跟上面工厂方法不一样的地方是具体Store多了一个抽象工厂的成员变量,因为要用它来创造原料
public abstract class PizzaStore { public Pizza orderPizza(String type){ Pizza pizza; //工厂只知道什么时候去创建子类,但是不知道创建什么子类 pizza = createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } //交给子类去实现 protected abstract Pizza createPizza(String type); } public class NYStylePizzaStore extends PizzaStore { @Override public Pizza createPizza(String type) { Pizza pizza = null; PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory(); if(type.equals("cheese")){pizza = new NYStyleCheesePizza(ingredientFactory); }else if(type.equals("pepperoni")){pizza = new NYStylePepperoniPizza(ingredientFactory); }else if(type.equals("clam")){pizza = new NYStyleClamPizza(ingredientFactory); }else if(type.equals("veggie")){pizza = new NYStyleVeggiePizza(ingredientFactory);} return pizza; } }
抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。
当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。
增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点:
在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)
参考资料:http://design-patterns.readthedocs.io/zh_CN/latest/index.html(图说设计模式)、《Head First 设计模式》、《设计模式》、wiki百科