设计模式——工厂模式

  这坑挖了这么久,终于想到填坑了。首先工厂模式并不是我第一个开始看的模式,但为什么放在第一个讲呢?首先,工厂模式里面的细节比较多,根据我的理解还分了3个小块。其次,工厂模式装饰模式个人感觉可以在一起使用,所以放在一起讲(难道不应该先讲装饰模式么?算了这个细节不去管了= =)。最后,也是最重要的一点,我看到现在工厂模式中一些细节是没法一下子全部接受的,所以也是为了自己的巩固,所以先把工厂梳理一下比较好。

  在工厂模式中,由浅入深的接触了 简单工厂工厂模式抽象工厂。在一开始看简单工厂的时候我感觉我理解了,不过如此嘛;看到工厂模式,喵喵喵?怎么又来一个?;看到抽象工厂,嗯?所以和前面有区别么?我也是看了很久的uml图,网上查了很多资料,做了对比和总结。

  这里其实会有一个疑问,是针对所有设计模式的疑问:只不过是把这段代码从这边,写到了另一边,到底有什么用???从代码量来说,根本没有少,反而变多了啊?(我赌5毛,萌萌肯定会这样说),在这边其实我自己也有疑问,但是最近学下来养成了一个习惯,不同功能的代码分工给不同的对象去做,一目了然,修改起来也方便。其次,在工作中,不可能是所有代码都给客户的,所以,客户端的代码必须与其他代码解耦,使用接口编程。(这一段是我自己编的。。随便看看就好)

为什么要用工厂Factory

  
  要引入这个概念首先得先扯点其他的,java面向对象中有一个原则:针对接口编程,而不针对实现编程。在我们使用“new”的时候,这时候就会实例化一个具体的类,这时候已经脱离了针对接口了。当代码捆绑着具体类的时候,就会很脆弱,缺乏了弹性。

1
Human human = new Man();//在这里Man是Human的一个子类

  当有一群相关的具体类的时候,那么就会像这样:

1
2
3
4
5
6
7
8
Human human;
if(做饭){
human = new Chef();
}else if(教育){
human = new Teacher();
}else if(开车){
human = new Driver();
}

  在这里,具体要实例化哪一个类,要在运行时的一些条件来决定,但是当需要扩展的时候,就需要重新修改这段代码,以这里为例,就需要新增一个else if语句。

new 有什么问题?
  在技术上来说没任何问题,毕竟是java。但是由于要应对扩展改变,当代码使用大量的具体类时,一旦加入新的具体类,那就肯定要动这代码了。针对接口编程,可以隔离掉以后系统可能发生的一堆改变和扩展。

  
  
  
<为了形象的说明问题,在下面都用一个例子来说明 Pizza店(来源于head first,这个比喻很适合)>

简单工厂 SimpleFactory

  
  定义:通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。以此来将创建实例的过程封装。
  现在pizza店有一个订餐的功能:

1
2
3
4
5
6
7
8
9
10
Pizza orderPizza(){
Pizza pizza = new Pizza();

pizza.prepare();//准备
pizza.bake();//烘烤
pizza.cut();//切块
pizza.box();//装箱

return pizza;
}

  但是仅仅这样是不够的,需求增加了,现在多了3种可以让订餐的用户选择的种类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Pizza orderPizza(String type){
Pizza pizza = new Pizza();

if(type.equals("cheese")){
pizza = new CheesePizza();
}else if(type.equals("chicken")){
pizza = new ChikenPizza();
}else if(type.equals("fish")){
pizza = new FishPizza();
}

pizza.prepare();//准备
pizza.bake();//烘烤
pizza.cut();//切块
pizza.box();//装箱

return pizza;
}

  暂时这样看上去没什么问题,但是和刚刚说的一样,如果要再来几种pizza的种类,那么这份代码就又要新增几个else if语句块。并且,这个方法已经有点繁杂了,他如果只负责pizz的准备—>装箱,具体制作pizza的任务交给工厂来做,岂不美哉?好,现在来一个简单工厂!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SimplePizzaFactory{
public Pizza createPizza(String style){
Pizza pizza = null;

if(type.equals("cheese")){
pizza = new CheesePizza();
}else if(type.equals("chicken")){
pizza = new ChikenPizza();
}else if(type.equals("fish")){
pizza = new FishPizza();
}

return pizza;
}
}

  
Q:ヾ(。`Д´。)窝草!这搞毛呢?这只是把问题搬到另一个对象里啊…
A:咳咳!这里把创造出pizza的功能分离出来了,不管是谁都能通过工厂实例去调用。当需要扩展或者改变时,只要去找这个类就好了。
  
静态工厂:
有一个类似的设计方式,直接把这里的SimplePizzaFactory类定义成静态类,这样就是静态工厂。优点是不用实例化直接使用,缺点是没法通过继承来改变或者创建方法。 具体的就不扯了,都是些静态类的概念了。

  啊!好了,话题接着刚才的,现在有了工厂,那么就可以用起来啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PizzaStore {
SimplePizzaFactory factory;
//构造方法来接收工厂
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}

Pizza orderPizza(String style){
Pizza pizza = factory.createPizza(style);

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

return pizza;
}

}

  到这里简单工厂就说完了,其实真的很简单。简单工厂其实严格意义上来说,算不上是一个模式,只是一个习惯。。。来看一下uml图(我tm找了半天找不到一个合适的画类图的软件,哎~ 手绘咯)

img

  
  
  

工厂方法模式 Factory

  
  定义:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例化推迟到子类。
  好了,通过简单工厂已经将实例化对象的动作交给了工厂去完成,客户端(PizzaStore)已经不需要关注Pizza对象的生成了。Pizza店越开越大,终于需要开分店了:现在需要在A,B,C三个不同的城市开3个分店,但由于地区差异,每个地方对Pizza对象的生产有着不同的要求(对象的创建),比如ABC三个城市不同特色的chickenPizza,那现在怎么做呢?
  如果利用上面说到的SimpleFactory,那么我们需要写3个不同的工厂CityASimplePizzaFactory,CityBSimplePizzaFactory,CityCSimplePizzaFactory来分别生产不同特色的同类型Pizza。在具体使用的时候:

1
2
3
4
5
6
7
8
9
10
11
CityASimplePizzaFactory factoryA = new CityASimplePizzaFactory();
PizzaStore storeA = new PizzaStore(factoryA);
storeA.orderPizza("chicken");

CityBSimplePizzaFactory factoryB = new CityBSimplePizzaFactory();
PizzaStore storeB = new PizzaStore(factoryB);
storeB.orderPizza("chicken");

CityCSimplePizzaFactory factoryC = new CityCSimplePizzaFactory();
PizzaStore storeC = new PizzaStore(factoryC);
storeC.orderPizza("chicken");

  现在总店提供的方法显得有点笨重,在使用的时候需要告诉每一家分店具体该去用哪一个工厂来生产Pizza(该去如何创建对象)。这样显然是有些…这里有一个面向对象原则:开闭原则:对扩展开放,对修改关闭。那么怎么能将不同的分店(指让不同的Store可以有不同的 对Pizza的操作)和创建Pizza(指如何创建Pizza对象)绑在一起,有保持弹性呢?(最早在使用简单工厂之前的store确实将这两者绑在了一起,但是完全没有弹性)
  再回过头去读一下上面的定义:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例化推迟到子类。好了,现在将createPizza放回总店PizzaStore中,但是写成抽象方法,具体的实现要求其子类去实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class PizzaStore{
public Pizza orderPizza(String style){
Pizza pizza;
pizza = createPizza(style);

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

return pizza;

}

//现在 实例化对象的工作被分散到了这个方法中,这个方法就相当于是一个工厂
public abstract Pizza createPizza(String style);
}

  这样又什么好处呢?因为将总店写成了超类,所以总店的一套完整的订单系统(orderPizza方法)可以被分店(子类)直接使用。子类通过实现父类的createPizza可以来自定义的创造Pizza,更进一步来说,orderPizza方法对Pizza对象做了很多的事情,但是由于Pizza对象是抽象的,他并不知道哪些实际的类参与了进来,这,就是解耦。下面来具体写一个分店类看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CityAStore extends PizzaStore{
Pizza createPizza(String style){
Pizza pizza = null;

if(style.equals("cheese")){
pizza = new CityACheesePizza();
}else if(style.equals("chicken")){
pizza = new CityAChickenPizza();
}else if(style.equals("fish")){
pizza = new CityAFishPizza();
}
return pizza;
}
}

//B,C与A类似不写了

  工厂方法可以封装具体类型的实例化过程,看下面的类图,抽象的Creator提供了一个创建对象的方法接口,也称为“工厂方法”。在抽象的Creator中任何其他实现的方法,都可能使用到这个工厂方法所制造出来的产品,但只有这个子类真正实现这个工厂方法并创建产品。如同在定义中说的,工厂方法让子类决定要实例化的类是哪一个。 这里不要产生误解,“决定”并不是指模式允许子类本身在运行的时候做决定,而是指在编写创建者类时,不需要知道实际创建的产品是哪一个。选择了使用哪个子类,那么自然就决定了实际创建的对象是什么。

img

  
  
  

依赖倒置原则

  
  在说抽象工厂之前,先停一下,来说说依赖倒置。直接说明可能会有些麻烦,所以我们还是一步步来:如果从来没有听说过工厂,下面的案例就没有使用工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class DependentPizzaStore{
public Pizza createPizza(String style,String city){
if(city.equals("cityA")){
if(style.equals("chicken")){
pizza = new CityAChickenPizza();
}else if(style.equals("cheese")){
pizza = new CityACheesePizza();
}else if(style.equals("fish")){
pizza = new CityAFishPizza();
}
}else if(city.equals("cityB")){
if(style.equals("chicken")){
pizza = new CityBChickenPizza();
}else if(style.equals("cheese")){
pizza = new CityBCheesePizza();
}else if(style.equals("fish")){
pizza = new CityBFishPizza();
}
}else{
System.out.println("非常抱歉,没有符合的种类");
return null;
}

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

return pizza;

}
}

  敲上面这些代码的时候我都快炸了。(没法自动对齐)当直接实例化一个对象时,就是在依赖具体类,很明显的,当代码中减少对具体类的依赖是一件“好事”。这在设计模式中就被称为依赖倒置原则。
依赖倒置原则:要依赖抽象,不要依赖具体类
  这个原则乍一看很像是“针对接口编程,不要针对实现编程” 他们确实很相似,但是这里更加强调“抽象”。不能让高层组件依赖低层组件,而且不管是高层组件还是低层组件,都应该依赖与抽象。
  这里说的是不是有点“抽象”,结合上面的例子看,PizzaStore相当于高层组件,ChickenPizza,FishPizza,CheesePizza就相当于低层组件,很明显,PizzaStore依赖这些具体的Pizza类。如果将他们画成一张图,那就是这样的(因为比较简单 这里就不画出来了):PizzaStore→许多不同种类的Pizza
  现在,看到这个倒置原则,要怎么做呢?让高层对象和低层对象都依赖抽象类,而不去依赖具体类。
  虽然,上面的例子已经有了Pizza类作为所有不同种类Pizza的抽象,但在具体使用的时候仍然创建了具体的pizza。我们用了工厂方法来解决这个问题,因此现在图是这样的:PizzaStore→Pizza←许多不同种类的Pizza。
  
以下几点可以帮助遵守依赖倒置原则:
变量不可以持有具体类的引用
  如果使用如果使用new,就会持有具体类的引用,可以使用工厂方法来避开这样的做法
不要让类派生自具体类
  如果派生自具体类,就会依赖具体类,请派生自一个抽象(抽象类或者接口)
不要覆盖基类中已经实现的方法
  如果覆盖基类已经实现的方法,那么这个基类就不是一个真正适合被继承的抽象,基类中已经实现的方法,应由所有的子类共享。

  
  
  

抽象工厂 AbstractFactory

  
  定义:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定类。
  现在,Pizza店已经开的红红火火,总店开始担忧一个问题,那么多分店,要是有一家做Pizza用的原料稍微有点偷工减料,那总店的名声都被糟蹋了怎么破?不行,我得造一个原料工厂,原料工厂由总店管控,将生产的原料送给分店。怎么一来似乎不错,但还有一点需要额外注意,不同的城市对原料的要求可能不同,城市A喜欢玉米粉,城市B喜欢面粉;城市A靠海,那fish就新鲜的鱼,城市B在内陆地区,fish就是鱼罐头了。
  好,就着这个思路,我们开始规划一下原料工厂,每个工厂需要生产:面团,酱料,芝士,蔬菜,特色佐料等

1
2
3
4
5
6
7
8
public interface PizzaIngredientFactory{
public Dough createDough();//面团
public Sauce createSause();//酱料
public Cheese createCheese();//芝士
public Meat createMeat();//肉
public Veggies[] createVeggies();//蔬菜
public Fish createFish();//鱼
}

  上面给工厂定义了一个接口,这个接口负责创建所有的原料,下面就是这个接口的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CityAPizzaIngredientFactory implements PizzaIngredientFactory{
public Dough createDough(){
return new CityADough();
}
public Sauce createSause(){
return new CityASause();
}
public Cheese createCheese(){
return new CityACheese();
}
public Meat createMeat(){
return new CityAMeat();
}
public Veggies[] createVeggies(){
Veggies CityAVeggies[] = { 各种cityA城市洗好的蔬菜 };
return CityAVeggies;
}
public Fish createFish(){
return new CityAFish();
}
}

  上面写了城市A的原料工厂,BC类似省略。现在我们来写一下Pizza抽象类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public abstract class Pizza{
String name;
Dough dough;
Sauce sauce;
Cheese cheese;
Meat meat;
Veggies veggies[];
Fish fish;

abstract void prepare();

void bake(){
System.out.println("烘烤中");
}
void cut(){
System.out.println("切块中");
}
void box(){
System.out.println("装箱中");
}
void setName (String name){
this.name = name;
}
public String getName(){
return name;
}
}

  上面写了一个抽象的pizza,这个抽象的pizza类规定了一个pizza可能所用到的原料,一鼓作气,现在来做个海鲜pizza试试吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FishPizza extends Pizza{
PizzaIngredientFactory ingredientFactory;

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

void prepare(){
System.out.println("Preparing" + name);
douth = ingredientFactory.createDough();
sause = ingredientFactory.createSause();
cheese = ingredientFactory.createCheese();
fish = ingredientFactory.createFish();//如果是城市A 那么就会用新鲜的鱼,如果是城市B 则用鱼罐头
}

}

  上面写了一个fishpizza,其他类型一样,只需要重写父类的prepare类,用本类型所用得到的原料就好了,现在回到pizza分店,看看怎么运作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CityAStore extends PizzaStore{

public Pizza createPizza(String style){
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new CityAPizzaIngredientFactory();

if(type.equals("cheese")){
pizza = new CheesePizza(ingredientFactory);
}else if(type.equals("chicken")){
pizza = new ChikenPizza(ingredientFactory);
}else if(type.equals("fish")){
pizza = new FishPizza(ingredientFactory);
}
return pizza;
}
}

  现在的分店被指定了该地区“总店认证”的原料工厂,好了,现在就不用担心分店偷工减料了O(∩_∩)O

一系列的操作,我们到底做了什么?
  我们引入了新类型的工厂,也就是所谓的抽象工厂,来创建pizza的原料家族。通过抽象工厂所提供的接口,可以创建产品家族,利用这个接口来写代码,我们的代码将从实际的工厂中解耦,以便在不用的地方实现各种各样的工厂。由于代码从实际的产品中解耦了,所以我们可以替换不同的工厂来取得不同的行为。
  现在我们在回头看一下定义:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定类。抽象工厂允许客户使用抽象接口来创建一组相关的产品,而不需要知道实际产出的产品是什么,这样一来客户就从具体的产品中被解耦。
下面我们来看一下类图:
img
本案例的图就不画拉,就当做练习,看到这篇文章的时候再想想吧!
(其实是抽象工厂的图相比之前的有点大,一页画不下这么多,而且本案例和标准类图差不多,我就懒得动手了,画一下也好累的。。。)

  
  
  

总结

  
  抽象工厂往往会通过工厂方法来实现,抽象工厂的任务是:定义一个负责创建一组产品的接口,这个接口内的每个方法都负责创建一个具体的产品。我们利用实现抽象工厂的子类来实现具体的方法,所以会经常产生一些混淆。下面我对工厂方法和抽象工厂来做一下区分:
  工厂方法 和 抽象工厂 的任务都是负责创建对象,但 工厂方法用的是继承,而抽象工厂则是通过对象的组合(组合模式我应该先讲的TAT)。所以这意味着,使用工厂方法时,需要扩展一个类(extends也就是继承),并且覆盖他的工厂方法,其实整个工厂方法,只不过就是通过子类来创建对象。用这种做法客户只需要知道他们所使用的抽象类型就可以了,而由子类来负责具体类型,换句话说,工厂方法只负责将客户从具体类型中解耦。再看抽象工厂,他提供了一个用来创建一个产品家族的抽象类型(接口或抽象类),这个类型的子类定义了产品被产生的方法,要想使用这个抽象工厂,必须实例化他,然后将它传入一些针对抽象类型所写的代码中。因此和工厂方法一样,抽象工厂也能将客户从具体类型中解耦,同时,抽象工厂可以将一群相关的产品集合起来。但是有利有弊,当要扩展这组产品的时候,就要动这个接口(OMG)
  再来说一下具体什么时候使用吧,其实仁者见仁智者见智,但一般来说:当需要创建产品家族想让自导的相关产品集合起来时,使用抽象工厂;当要把客户代码从需要实例化的具体类中解耦或者目前还不知道将来需要实例化哪些具体类时,使用工厂方法。
  
  最后,在网上看到一个对于3个工厂的解释,我觉得非常形象,在这理贴一下:
  不能说简单工厂模式”对于增加新的产品,无能为力“,因为如果简单工厂是用来生产”东西“的,那任何”东西“的子类,比如汽车,自行车,轮船,洗发水都是可以被生产的,但此处简单工厂的压力太大了啊,任何”东西“的子类都可以被生产,负担太重,所以一般对简单工厂类也有种称呼,叫”上帝类“。
  而工厂方法模式就很好的减轻了工厂类的负担,把某一类/某一种东西交由一个工厂生产,同时增加某一类”东西“并不需要修改工厂类,只需要添加生产这类”东西“的工厂即可,使得工厂类符合开放-封闭原则。
  对于”东西“的分类,有时候不能光是横向的分类,从另一个角度也是可以分类的,不知道这句话的意思能不能懂,打个比方:汽车可以根据品牌分为奔驰、奥迪,也可以根据类别分为普通三厢车和SUV车,如果用工厂方法来描述的话,奔驰车工厂有一个方法即生产奔驰车,奥迪车工厂有一个方法生产奥迪车,但在有多重分类的情形下,这样写已经不够用,不符合实际了,这个时候需要用到抽象工厂模式,即奥迪车工厂有两个方法,一个方法是生产普通三厢奥迪车,另一个方法是生产SUV奥迪车。奔驰车工厂有两个方法,一个方法是生产普通三厢奔驰车,另一个方法是生产SUV奔驰车。
  上面即工厂方法模式和抽象工厂模式的应用场景,因为这两者很像,所以概念上不容易区分,可以这么说,工厂方法模式是一种极端情况的抽象工厂模式,而抽象工厂模式可以看成是工厂方法模式的一种推广。
  再说下抽象工厂模式,此处的抽象工厂接口应该是有两个方法,一个是生成普通三厢车,一个是生产SUV车,可以说,生产的”东西“已经被限定住了,因此你不能生产某品牌汽车外的其他”东西“,因而可以理解成使用抽象工厂模式不能新增新的”东西“(在简单工厂和工厂方法中理论上都是可以新增任意”东西“的)
  
  
  

后记

  我的妈!终于是写完了!其实写完了我感觉:写错拉!!!还是应该先写组合模式的!!!个人感觉组合是最适合将设计模式引入的一个模式,而且对于多态,针对接口编程等概念都有使用(可能是我第一个看的是组合的缘故吧)
  算了写也写完了,我在想给自己来点奖励,比如说去fgo氪他个1单????OMG我的花钱罪恶感又上来了。。。。