设计模式--工厂模式(六)

工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类;

知识点的梳理:

  • 在设计模式中,所谓的"实现一个接口"并"不一定"表示"写一个类,并利用implements关键字来实现某个java接口","实现一个接口"泛指"实现某个超类型(可以是类或接口)的某个方法";
  • 依赖倒置原则:要依赖抽象,不要依赖具体类;
  • 几个指导方针来遵循此原则:(注意,这些原则只是需要我们尽量遵守,不要墨守成规)
    • 变量不可以持有具体类的引用:如果使用new,就会持有具体类的引用。可以改用工厂来避开这样的做法;
    • 不要让类派生自具体类:如果派生自具体类,你就会依赖具体类。请派生自一个抽象(接口或抽象类);
    • 不要覆盖基类中已实现的方法:如果覆盖基类已实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已经实现的方法,应该由所有的子类共享;
  • 所有的工厂都是用来封装对象的创建;
    • 所有工厂模式都通过减少应用程序和集体类之间的依赖促进松耦合;
  • 简单工厂,虽然不是真正的设计模式,但仍不失为一个简单的方法,可以将客户程序从具体类中解耦;
  • 工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象;
  • 工厂方法允许类将实例化延迟到子类进行;

     

  • 识别变化的方面
    • 假设你有一家比萨店,你的代码可能是这样的:

设计模式--工厂模式(六)

  • 此时,你想要更多比萨类型。我们只能增加代码,来"决定"适合的比萨类型,然后再"制造"这个比萨:
    • 设计模式--工厂模式(六)
  • 再此基础上,再次增加比萨种类;
    • 加入Clam Pizza,Veggie Pizza,删除Greek Pizza
    • 设计模式--工厂模式(六)
    • 如果实例化"某些"具体类,将使orderPizza()出问题,而且也无法让orderPizza()对修改关闭;但是我们已经知道哪些会改变,哪些不会改变,现在来使用封装吧;
  • 封装创建对象的代码
    • 现在最好将创建对象移动到orderPizza()外。最好是把创建比萨的代码移动到另一个对象中,由这个新对象专职创建比萨;
      • 设计模式--工厂模式(六)
    • 这个新对象,就可以被称之为"工厂"!
      • 工厂(factory)处理创建对象的细节;
      • 一旦有了SimplePizzaFactory,orderPizza()就变成此对象的客户。当需要比萨时,就叫比萨工厂做一个。
      • 现在orderPizza()方法只关心从工厂得到一个比萨,而这个比萨实现了Pizza接口,所以它可以调用prepare(),bake(),cut(),box()来分别进行准备,烘烤,切片,装盒;
  • 建立一个简单比萨工厂
    • 定义一个类,为所有比萨封装创建对象的对象

/**

* SimplePizzaFactory是我们的新类,它只做一件事情,帮它的客户创建比萨

*/

public class SimplePizzaFactory {

//首先,在这个工厂内定义一个createPizza()方法。所有客户用这个方法来实例化新对象

public Pizza createPizza(String type){

Pizza pizza = null;

//这些if else就是从orderPizza方法中移动过来的代码

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;

}

}

问题:
1.这么做只是将问题搬到另一个对象而已吧?
SimplePizzaFactory可以有很多的客户,现在只有orderPizza方法是它的客户。当客户增加时,就可以看出这样分类的好处。我们把创建比萨的代码包装进一个类,当以后实现改变时,只需修改这个类就可以了。我们也要把具体实例化的过程,从客户的代码中删除!

2.把工厂定义成一个静态的方法,有何差别?
利用静态方法定义一个简单工厂被称为静态工厂。这种方式不需要创建对象方法来实例化对象。这种方式不能通过继承来改变创建方法的行为;

  • 重做PizzaStore类
    • 现在要依靠工厂来创建比萨

public class PizzaStore {

SimplePizzaFactory factory;

//构造函数需要一个工厂作为参数

public PizzaStore(SimplePizzaFactory factory){

this.factory = factory;

}

public Pizza orderPizza(String type){

Pizza pizza;

//orderPizza()方法通过简单传入订单类型类使用工厂创建比萨

//我们把new操作符替换成工厂对象的创建方法,这里不再使用具体实例化

pizza = factory.createPizza(type);

pizza.prepare();

pizza.bake();

pizza.cut();

pizza.box();

return pizza;

}

}

  • 定义简单工厂
    • 简单工厂不算是设计模式,像是一种编程习惯
    • 来看看新的比萨店类图:

      设计模式--工厂模式(六)

  • 加盟比萨店
    • 现在新加入了加盟店,这些加盟店可以开在不同地方。所以每家店提供的比萨风味需要不同。
      • 设计模式--工厂模式(六)
    • 想法1:
      • 利用SimplePizzaFactory,写出三种不同的工厂,分别是NYPizzaFactory,ChicagoPizzaFactory,CaliforniaPizzaFactory,那么加盟店都有适合的工厂可以使用了;

设计模式--工厂模式(六)

设计模式--工厂模式(六)

  • 问题:
    • 使用此类可以确保加盟店使用该工厂创建比萨,但是其它部分,例如烘烤的方式,切片的方法等等,加盟店开始使用自己的方式;
    • 此时,我们需要建立一个框架,把加盟店和创建比萨捆绑在一起的同时,又保持一定的弹性;
  • 重新设计比萨店的框架
    • 制作比萨的代码绑定在PizzaStore里,这么做没有任何弹性。现在修改这个类,来尝试解决问题;

//更改PizzaStore为抽象类

public abstract class PizzaStore {

public Pizza orderPizza(String type){

Pizza pizza;

//现在createPizza()方法从工厂对象中移回PizzaStore

pizza = createPizza(type);

pizza.prepare();

pizza.bake();

pizza.cut();

pizza.box();

return pizza;

}

//PizzaStore里,"工厂方法"现在是抽象的

//现在把工厂对象移动这个方法中

protected abstract Pizza createPizza(String type);

}

使用此类作为超类,让每个域类型(NYPizzaStore,ChicagoPizzaStore,CaliforniaPizzaStore)都继承此类,每个子类各自决定如何制造比萨

  • 现在的情况是这样的,由orderPizza()方法统一负责处理订单,让createPizza()来负责各个加盟店不同的比萨加工方式(做法是,让PizzaStore的各个子类负责定义自己的createPizza()方法);如下图:
    • 设计模式--工厂模式(六)
  • 现在来开一家比萨店吧
    • 加盟店可以从PizzaStore免费取得所有的功能。只需要继承PizzaStore,然后提供createPizza()方法实现自己的比萨风味即可;
    • 本例只声明一个加盟店

//NYPizzaStore扩展PizzaStore,所以拥有orderPizza()方法(以及其他方法)

public class NYPizzaStore extends PizzaStore {

//该方法返回一个Pizza对象,由子类全权负责该实例化哪一个具体Pizza

@Override

protected Pizza createPizza(String type) {

//创建具体类的地方。对于每一种比萨类型,都是创建纽约风味

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

return new NYStyleCheesePizza();

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

return new NYStyleVeggiePizza();

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

return new NYStyleClamPizza();

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

return new NYStylePepperoniPizza();

}

return null;

}

//注意,超累的orderPizza()方法,并不知道正在创建的比萨是哪一种,它只知道这个比萨可以被准备,被烘烤,被切片,被装盒!

}

  • 声明一个工厂方法
    • 原本是由一个对象负责所有具体类的实例化,现在通过对PizzaStore做一些转变,变成由一群子类负责实例化。转变的过程就像下图:

设计模式--工厂模式(六)

设计模式--工厂模式(六)

  • 编写比萨类

//从一个抽象比萨类开始,所有的具体比萨都必须派生自这个类

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));

}

}

public void bake(){

System.out.println("Bake for 25 minutes at 350");

}

public void cut(){

System.out.println("Cutting the pizza into diagonal slices");

}

public void box(){

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

}

public String getName(){

return name;

}

}

  • 编写具体子类

public class NYStyleCheesePizza extends Pizza {

public NYStyleCheesePizza(){

//纽约比萨有自己的大蒜番茄酱和博饼

name = "NY Style Sauce and Cheese Pizza";

dough = "Thin Crust Dough";

sauce = "Marinara Sauce";

//上面覆盖的是意大利高级干酪

toppings.add("Grated Reggiano Cheese");

}

}

  • 编写测试类

public class PizzaTestDrive {

public static void main(String[] args) {

//建立一个店

PizzaStore nyStore = new NYPizzaStore();

//然后下单

Pizza pizza = nyStore.orderPizza("cheese");

System.out.println("Ethan ordered a "+pizza.getName()+"\n");

}

} 

  • 效果:

设计模式--工厂模式(六)

超类从来不管细节,通过实例化正确的比萨类,子类会自行照料这一切

  • 重新认识工厂方法模式
    • 所有工厂模式都用来封装对象的创建。工厂方法模式通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。
    • 工厂方法模式类图
      • 设计模式--工厂模式(六)
      • 设计模式--工厂模式(六)
    • 另一种观点:平行的类层级
      • 讲一个orderPizza()方法和一个工厂方法联合起来,就可以成为一个框架;
      • 此外,工厂方法将生产知识封装进各个创建者,这样的做法,也可以被视为是一个框架;
      • 认清这两个平行的类层级:
        • 设计模式--工厂模式(六)
  • 定义工厂方法模式
    • 工厂方法能够封装具体类型的实例化;
    • 下图,抽象的Creator提供了一个创建对象的方法的接口,也称为"工厂方法"。在抽象的Creator中,任何其他实现的方法,都可能使用到这个工厂方法所制造出来的产品,但只有子类真正实现这个工厂方法并创建产品;
      • 设计模式--工厂模式(六)
    • 针对上图的问题
      • 只有一个ConcreateCreator的时候,工厂方法模式有什么优点?
        • 尽管只有一个具体创建者,工厂方法模式依然有用,因为它帮助我们将产品的"实现"从"使用"中解耦。如果增加产品或者改变产品的实现,Creator并不会受到影响(因为Creator与任何ConcreteProduct之间都不是紧耦合)
      • 工厂方法和创建者是否总是抽象的?
        • 不,可以定义一个默认的工厂方法来产生某些具体的产品,这么一来,即使创建者没有任何子类,依然可以创建产品;
      • 每个商店基于传入的类型制造出不同种类的比萨,是否所有的具体创建者都必须如此?能不能只创建一种比萨?
        • 这里所采用的方式成为"参数化工厂方法"。它可以根据传入的参数创建不同的对象。然而,工厂经常只产生一种对象,不需要参数化。模式的这两种形式都是有效的。
      • 简单工厂和工厂方法之间的差异很模糊。差别在于,在工厂方法中,返回比萨的类是子类,是这样的吗?
        • 子类的确很像简单工厂。简单工厂把全部的事情,在一个地方都处理完了,然而工厂方法却是创建一个框架,让子类决定要如何实现。比如,在工厂方法中,orderPizza()方法提供了一般的框架,以便创建比萨,orderPizza()方法依赖工厂方法创建具体类,并制造出实际的比萨。可通过继承PizzaStore类,决定实际制造出的比萨是什么。简单工厂的做法,可以将对象的创建封装起来,但是简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品;
  • 依赖导致原则
    • 该原则很像是"针对接口编程,不针对实现编程",但更加强调"抽象";
      • 该原则说明了:不能让高层组件依赖低层组件,而且,不管高层或底层组件,"两者"都应该依赖于抽象;
      • 如:PizzaStore是个高层组件,因为它的行为是由比萨定义的。PizzaStore创建所有不同的比萨对象,准备,烘烤,切片,装盒,而比萨本身属于低层组件;
    • 原则的应用
      • 针对比萨店这个例子,不使用工厂模式,对象的依赖看起来就像这样:
        • 设计模式--工厂模式(六)
      • 非常依赖比萨店的主要问题在于:它依赖每个比萨类型,因为它是在自己的orderPizza()方法中,实例化这些具体类型的;
      • 虽然我们创建一个抽象Pizza,但我们仍然在代码中,实际地创建了具体的Pizza,所以,这个抽象没什么影响力;
      • orderPizza()中将实例化对象的代码独立出来,工厂方法刚好能派上用处;
    • 应用工厂方法之后的类图,看起来像这样:
      • 设计模式--工厂模式(六)
      • 应用工厂方法之后,高层组件(也就是PizzaStore)和低层组件(也就是这些比萨)都依赖了Pizza抽象。想要遵循依赖倒置原则,工厂方法并非是唯一的技巧,但也是最有威力的技巧之一;
    • 依赖倒置原则,究竟倒置在哪里?
      • 依赖倒置原则中的倒置指的是和一般OO设计的思考方式完全相反;
      • 查看上图你会注意到低层组件现在竟然依赖高层的抽象。同样地,高层组件现在也依赖相同的抽象。之前的笔记中绘制的依赖图是由上而下的,现在却倒置了,而且高层与低层模块现在都依赖这个抽象;
    • 倒置自己的思考方式
      • 比萨店进行准备,烘烤,装盒,所以比萨店必须能制作许多不同风味的比萨,例如:芝士比萨,素食比萨等等;
        • 所以现在需要实现一个比萨店,那么第一件事情应该做什么呢?
      • 是的,芝士比萨,素食比萨都是比萨,所以它们应该共享一个Pizza接口;
        • 没错,先从顶端开始,然后往下到具体类。但是,比萨店不应理会这些具体类,要不然比萨店将全都依赖这些具体类。现在,倒置这些想法,别从顶端开始,而是从比萨(Pizza)开始,然后想想看能抽象化些什么。
        • 是的,需要抽象化一个Pizza。现在回头重新思考如何设计比萨店。
      • 既然已经有一个比萨抽象,就可以开始设计比萨店,而不用理会具体的比萨类了;
        • 如果这么做,必须靠一个工厂来将这些具体类取出比萨店。一旦这样做,各种不同的具体比萨类型就只能依赖一个抽象,而比萨店也会依赖这个抽象。这就倒置了一个商店依赖具体类的设计,而且也倒置了我们的思考方式;