本期我们要介绍一个能让你烘烤自己的OO设计的一种模式,工厂模式        

请问除了使用new以外,你还有其他创造对象的方法吗?如果你说没有,那么和我一起好好学习下这个模式吧。你会认识到往往实例化不应该总是公开进行,也会认识到初始化经常造成的“耦合”问题。


在之前的模式当中,我有介绍过一个原则,我们不应该针对实现编程。但当看到“new”时,就会想到“具体”,无论是 A a=new A()还是 A a=new B(),都其实在实例化一个具体类,所以的确用的是实现,而不是接口,或者再比如在之前“模拟鸭子”当中
Duck d = new MallardDuck(); Duck是一个接口,但MallardDuck还是得建立具体类呀,OK若这样想那么至少明白了,代码与具体类“捆绑”是耦合,缺少弹性一种表现。

那这时就有小伙伴说了:“但总要有创建对象的时候把,Java中只提供了new关键字键对象,还能有别的吗?” 当然new关键字本身没有问题,这是Java原生部分,其实阻碍我们的是“改变”,针对接口编程,可以隔离掉当“改变”来了以后的许多问题,如果代码是针对接口(这里我在此强调,接口是一个大概念,不单单interface是接口,抽象类,类也能是一个“接口”)而写,那么通过多态,它可以与任何新类实现该接口。但是我个人也觉得似乎有问题:当代码大量使用具体类时,会自找麻烦,因为当一旦要求增加某些内容时,就必须改变代码。也就是说这样代码也不是“对修改关闭”(前期内容)了,想要新的具体类型来拓展代码,必须重新修改并覆盖它。

说了那么多,具体咋写代码呢,不能纸上谈兵把?OK,我具体来写个实例。假设你有家披萨店。

注:设计模式需要反复多次练习,方可灵活运用,前期可以先模仿。

一开始这样写(这里我引用书中笔记)
图片描述

当然我们还要传入类型来制作不同口味的披萨,如下图
图片描述

但是你知道吗?这样设计是会有很大压力的,比如你要添加新的口味披萨,或者取出掉某些卖的不好风味的披萨的话。要这么写,无论增加新披萨或者删除掉原有披萨你都要到这里操作,上面部分会进行修改,而下面部分准备原料,烘烤,切片,装盘,操作都是不改变的,只有上面打算做什么口味披萨的这个动作会改变。

注:请区别出哪些是会改变的,哪些不会改变


图片描述

我们做的改进,封装创建对象的代码,要把创建披萨的代码转移到另一个对象中,由这个对象专职创建披萨,这个新对象只干这一件事,如图
图片描述

没错,我们称呼为它“工厂”。
在JavaWeb当中有个持久层框架Hibernate,其一个核心接口“工厂”(SessionFactory)就是用到工厂模式,负责初始化hibernate,是数据源的代理,负责创建Session对象,一个数据库对象只需配置一个SessionFactory,若有多个也可以配置多个。

工厂(factory)处理创建对象的细节,一旦有了Factory,orderPizza()就变成了,此对象的客户。当需要什么类型披萨时,就叫披萨工厂做一个。现在orderPizza()方法只关心从工厂得到一个披萨,而这个披萨实现了Pizza接口,所以可以调用准备材料,烘烤,切片,装盘方法。

那我们来创建个简单工厂。
图片描述

可能有小伙伴提问:“感觉只是把之前会被大量修改的问题移到另一个对象里而已”
注:SimlePizzaFactory会有很多客户,目前我们看到orderPizza()方法是它客户,但是,可能还有PizzaShopMenu(披萨店菜单),xxxxPizzaShop(各种不同风格披萨店)等等会利用这个工厂来取得披萨里的内容,总而言之,SimlePizzaFactory会有许多客户。所以,把创建披萨的代码包装进一个类,当以后实现改变时,只需修改此类即可。我正要把具体实例化的过程,在客户代码中删除。

此时我们写一个披萨“工厂”的客户类(PizzaStore)
图片描述

以上就是简单工厂模式的内容了,
注:简单工程严格来说不是一个设计模式,更像是一种编程习惯,但由于经常被使用,所以有时候很多开发人员误认为“工厂模式”。

下面是简单工程的类图。(请注意理解)
图片描述

注:
再次提醒:所谓的“实现一个接口”并不一定表示“写一个(interface)类,然后利用implements关键词来实现某个Java接口”。“实现一个接口”泛指“实现某个超类型(可以是类或接口)的某个方法”。

你以为这篇文章写道这里就结束了?哈哈,别忘了我的口号是啥~~

既然我们有了自己的披萨店,我们当然希望将它做大做强,我们开始加盟披萨店。
图片描述

这里我们创建对应的“工厂”对象。
图片描述

但这又有一个问题了,难道你能放任加盟店不管吗?这样各个加盟店就按照自己的方式,烘烤的做法不同,不要切片,使用其他厂商的盒子,如果能够建立一个框架,把加盟店和创建披萨捆绑在一起的同时又保持一定的弹性。

有个做法可以把披萨制作活动局限在客户(PizzaStore)类中,而同时又能让这些加盟店依然可以自由的创建该区域披萨,就是将createPizza方法放回PizzaStore内,将其设置为“抽象方法”,让后每个区域的加盟店创建它的子类,就是说两者之间形成依赖关系。
图片描述
让我们详细一点。
图片描述

从orderPizza()方法观点来看,此方法在抽象父类PizzaStore里定义,但具体实现还是在不同子类中。面对每个不同子类,父类的orderPizza()方法其实并不知道哪个子类将实际上制作披萨。

orderPizza()方法对Pizza对象指向,(准备材料,烘烤,切片,装盒)但由于Pizza对象是抽象的,orderPizza()对象也并不知道哪些实际参与的具体类,换句话说,这就是解耦。(WTF?这就是解耦,很多时候往往初学设计模式的人还不能做到很敏感,包括我自己,需要长时间不断学习)

OK,我们这时候开一家加盟店咯。
图片描述
注:超类(父类)PizzaStore中orderPizza()方法,并不知道正要创建什么披萨,它只知道制作披萨有四个步骤。。。

现在我们创建工厂方法,之前都是new,对象负责具体类的实例化,现在我们通过PizzaStore做一个小转变(其实在这里,我个人感觉说他是工厂方法感觉怪怪的)
图片描述

我们再来巩固下工厂方法(这里我放出我的笔记截图)
图片描述

我们来写下购买披萨流程
1:首先,客户们需要取得披萨店实例。客户需要实例化一个NYStylePizzaStore或ChicagoStylePizzaStore
2:有了各自的PizzaStore订单,客户们分别调用orderPizza()方法,并传入他们所喜爱的比萨类型(芝士,素食)。
3:orderPizza调用createPizza()创建披萨,纽约风味披萨(NYStylePizzaStore)或者芝加哥风味披萨(ChicagoStylePizzaStore)。
4:orderPizza()并不知道具体创建的是哪一种披萨,只知道这是一个披萨,能够准备原料,烘烤,切片,被打包装盒,然后送出给顾客。

定义工厂方式模式:工厂模式定义了一个创建对象的接口,不在直接使用new了,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类了。
注:工厂方法模式能够封装具体类型的实例化。工厂方法让子类决定要实例化的类是哪一个。所谓“决定”,并不是指模式允许子类本身在运行时做决定,而是指在编写创建者类时,不需要知道实际创建的产品是哪一个。选择了哪个子类,自然就决定了实际创建的东西是什么(可能有点难以理解。。。)

下图是工厂方法模式类图
图片描述

好的说这么多,下面就是具体实现的时候了,由于此例子量稍多,但是量大不代表我会偷工减料的,嘿嘿嘿~~~

这是参考的Java工程class图
图片描述

代码开始

PizzaStore(工厂类)

package Store;

import Pizza.Pizza;

/**
 * PizzaStore相当于客户(工厂类), 往后会有很多类似此类进行拓展
 * 
 * 在这里orderPizza()方法对pizz对象做了很多事(createPizza,prepare,bake,cut,box)
 * 但是由于Pizza对象是抽象的(接口),
 * orderPizza()并不知道具体哪些实际类参与进来了(相当于实现Pizza接口的实现类有哪些orderPizza()方法并不知道)
 * 换句话说,这就是解耦,减少相互依赖
 * 
 * 同理createPizza()方法中也不知道创建的具体是什么披萨 只知道披萨可以被准备,被烘烤,被切片,被装盒行为而已
 * 
 */
// 披萨店
public abstract class PizzaStore {

    // 点单
    public Pizza orderPizza(String type) {
        // createPizza()方法从工厂对象中取出,
        // 并且方法也不再这里实现,定义为抽象类,谁需要在拿走方法去具体实现
        Pizza pizza = createPizza(type);
        System.out.println("----制作一个" + pizza);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    // 把工厂对象移到这个方法里,并封装
    // 现在工厂方法为抽象的,子类必须实现这个抽象方法
    public abstract Pizza createPizza(String item);
}

下面是“工厂”具体实现
纽约风格披萨店(分店)

package Store;

import Pizza.NYStyleCheesePizza;
import Pizza.NYStyleClamPizza;
import Pizza.NYStylePepperoniPizza;
import Pizza.NYStyleVeggiePizza;
import Pizza.Pizza;

/**
 * 此时NYStylePizzaStore所封装的是关于如何制作纽约风格的比萨
 * 
 * @author Joy
 * 
 */

// 纽约风格披萨店(分店)
public class NYStylePizzaStore extends PizzaStore {

    // 根据披萨的不同,创建不同披萨(具体实现类)
    @Override
    public Pizza createPizza(String item) {
        if (item.equals("芝士")) {
            return new NYStyleCheesePizza();
        } else if (item.equals("素食")) {
            return new NYStyleVeggiePizza();
        } else if (item.equals("海鲜")) {
            return new NYStyleClamPizza();

        } else if (item.equals("香肠")) {
            return new NYStylePepperoniPizza();
        }
        return null;
    }
}

芝加哥披萨店(分店)

package Store;

import Pizza.ChicagoStyleCheesePizza;
import Pizza.ChicagoStyleClamPizza;
import Pizza.ChicagoStylePepperoniPizza;
import Pizza.ChicagoStyleVeggiePizza;
import Pizza.Pizza;

/**
 * 此时ChicagoStylePizzaStore所封装的是关于如何制作芝加哥风格的比萨
 * 
 * @author Joy
 * 
 */
// 芝加哥披萨店(分店)
public class ChicagoStylePizzaStore extends PizzaStore {

    @Override
    public Pizza createPizza(String item) {
        if (item.equals("芝士")) {
            return new ChicagoStyleCheesePizza();
        } else if (item.equals("素食")) {
            return new ChicagoStyleVeggiePizza();
        } else if (item.equals("海鲜")) {
            return new ChicagoStyleClamPizza();
        } else if (item.equals("香肠")) {
            return new ChicagoStylePepperoniPizza();
        }
        return null;
    }
}

Pizza类
(相当于各个“产品”的接口)

package Pizza;

import java.util.ArrayList;

public abstract class Pizza {
    String name;// 披萨名称
    String dough;// 面团类型
    String sauce;// 酱料
    ArrayList toppings = new ArrayList();// 一套佐料

    public void prepare() {
        System.out.println("准备:" + name);
        System.out.println("添加面团");
        System.out.println("添加酱料");
        System.out.println("添加佐料如下:");
        for (int i = 0; i < toppings.size(); i++) {
            System.out.print(toppings.get(i) + "   ");
        }
    }

    public void bake() {
        System.out.println("\n" + "披萨正在烘烤,需烘烤25分钟。。。");
    }

    public void cut() {
        System.out.println("制作完成,给披萨切片。。。");
    }

    public void box() {
        System.out.println("给披萨打包装盒。。。");
    }

    public String getName() {
        return name;
    }

    // 输出客人点的披萨信息
    @Override
    public String toString() {
        StringBuffer display = new StringBuffer();
        display.append(name + " ----\n");
        display.append(dough + "\n");
        display.append(sauce + "\n");
        for (int i = 0; i < toppings.size(); i++) {
            display.append((String) toppings.get(i) + "\n");
        }
        return display.toString();
    }

}

下面是各种披萨具体实现类

package Pizza;

//加州披萨店的芝士披萨
public class ChicagoStyleCheesePizza extends Pizza {
    public ChicagoStyleCheesePizza() {
        name="加州芝士披萨";
        dough="厚饼";
        sauce="番茄酱";
        toppings.add("干碎奶酪");                
    }
    @Override
    public void cut() {    
        System.out.println("将披萨切成方形");
    }

}
package Pizza;

//加州披萨店的海鲜披萨
public class ChicagoStyleClamPizza extends Pizza {

    public ChicagoStyleClamPizza() {
        name = "加州海鲜披萨";
        dough = "厚饼";
        sauce = "番茄酱";
        toppings.add("干碎奶酪");
        toppings.add("蟹黄");
        toppings.add("龙虾");
        toppings.add("各种海鲜");
    }
    
    @Override
    public void cut() {    
        System.out.println("将披萨切成三角形");
    }    

}
package Pizza;

//加州披萨店里的香肠披萨
public class ChicagoStylePepperoniPizza extends Pizza {

    public ChicagoStylePepperoniPizza() {
        name = "加州香肠披萨";
        dough = "厚饼";
        sauce = "番茄酱";

        toppings.add("干碎奶酪");
        toppings.add("黑胡椒");
        toppings.add("菠菜");
        toppings.add("茄子");
        toppings.add("香肠切片");
    }

    @Override
    public void cut() {
        System.out.println("切成方形薄披萨");
    }

}
package Pizza;

//加州披萨店里的素披萨
public class ChicagoStyleVeggiePizza extends Pizza {

    public ChicagoStyleVeggiePizza() {
        name = "加州素披萨";
        dough = "厚饼";
        sauce = "番茄酱";
        toppings.add("干碎奶酪");
        toppings.add("圣女果");
        toppings.add("黑胡椒粉");
        toppings.add("红辣椒");
        toppings.add("甜玉米粒");
        toppings.add("色拉");
    }

    @Override
    public void cut() {
        System.out.println("切成方形薄披萨");
    }
}
package Pizza;


//纽约披萨店里的芝士披萨
public class NYStyleCheesePizza extends Pizza {

    public NYStyleCheesePizza() {
        name="纽约芝士披萨";
        dough="薄饼";
        sauce="番茄酱";
        toppings.add("干奶酪");    
    }    
}
package Pizza;

//纽约披萨店里的河鲜披萨
public class NYStyleClamPizza extends Pizza {

    public NYStyleClamPizza() {
        name = "纽约海鲜披萨";
        dough="薄饼";
        sauce="番茄酱";
        toppings.add("巴马干奶酪");
        toppings.add("蟹黄");
        toppings.add("龙虾");
    }
}
package Pizza;




//纽约披萨店里的香肠披萨
public class NYStylePepperoniPizza extends Pizza {

    public NYStylePepperoniPizza() {
        name = "纽约香肠披萨";
        dough="薄饼";
        sauce="番茄酱";
        toppings.add("巴马干酪奶酪");
        toppings.add("意大利香肠切片");
        toppings.add("大蒜");
        toppings.add("洋葱");
        toppings.add("香菇");
        toppings.add("红辣椒");
    }
}
package Pizza;

//纽约披萨店里的素披萨
public class NYStyleVeggiePizza extends Pizza {

    public NYStyleVeggiePizza() {
        name = "纽约素食披萨";
        dough = "薄饼";
        sauce = "番茄酱";
        toppings.add("巴马干酪奶酪");
        toppings.add("洋葱");
        toppings.add("各种蔬菜");
    }
}

测试类

package TestMain;

import Pizza.Pizza;
import Store.ChicagoStylePizzaStore;
import Store.NYStylePizzaStore;
import Store.PizzaStore;

public class TestMain {
    public static void main(String[] args) {
        PizzaStore nyStore = new NYStylePizzaStore();
        PizzaStore chicagoStore = new ChicagoStylePizzaStore();
        // System.out.println("====================================");
        // Pizza pizza1 = nyStore.orderPizza("芝士");
        // System.out.println("Joy点了一个" + pizza1.getName());
        // System.out.println("====================================");
        // Pizza pizza2 = nyStore.orderPizza("素食");
        // System.out.println("Joy点了一个" + pizza2.getName());
        System.out.println("====================================");
        Pizza pizza3 = nyStore.orderPizza("海鲜");
        System.out.println("Joy点了一个" + pizza3.getName() + " ,正在送出");
        // System.out.println("====================================");
        // Pizza pizza4 = nyStore.orderPizza("香肠");
        // System.out.println("Joy点了一个" + pizza4.getName());
        System.out.println("====================================");
        Pizza pizza11 = chicagoStore.orderPizza("芝士");
        System.out.println("Joy点了一个" + pizza11.getName() + " ,正在送出");
        // System.out.println("====================================");
        // Pizza pizza22 = chicagoStore.orderPizza("素食");
        // System.out.println("Joy点了一个" + pizza22.getName());
        // System.out.println("====================================");
        // Pizza pizza33 = chicagoStore.orderPizza("海鲜");
        // System.out.println("Joy点了一个" + pizza33.getName());
        // System.out.println("====================================");
        // Pizza pizza44 = chicagoStore.orderPizza("香肠");
        // System.out.println("Joy点了一个" + pizza44.getName());

    }
}

效果如下:
图片描述

这个模式是个很有用的模式,让我更加能封装变化了,模拟代码已经全部放出,注释也写的比较清楚,若有不理解欢迎留言。

感谢你看到这里,工厂模式上部分结束,但工厂模式内容还没完全结束 本人文笔随便,若有不足或错误之处望给予指点,90度弯腰~~~很快我会补全完这个内容。 生命不息,编程不止!

参考书籍:《Head First 设计模式》

暖心先森
287 声望16 粉丝

生命不息,编程不止!