为了账号安全,请及时绑定邮箱和手机立即绑定

Head First设计模式读书总结——工厂模式

标签:
Java 设计

除了使用new操作符之外,还有更多制造对象的方法。你讲了解到实例化这个活动不应该总是公开的进行,也会认识到初始化经常造成“耦合”问题。你讲了解工厂模式如何从复杂的依赖中帮你脱困。
例题:披萨店。
假设你有一个披萨店,身为披萨店的主人,你的代码可能是这样:

Pizza orderPizza(){
//为了让系统有弹性,我们很希望这是一个抽象类或接口。但如果这样,这些类或接口就无法直接实例化。
        Pizza pizza =new Pizza();
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
return pizza;
}

但是你需要更多披萨类型……
所以必须增加一些代码,来决定适合的披萨类型,然后再“制造”这个披萨。

Pizza orderPizza(String type){
        Pizza pizza ;
        if(type.equals("cheese")){
               pizza=new CheesePizza();
        }else if(type.equals("greek")){
               pizza=new GreekPizza();
        }else if(type.equals("pepperoni")){
               pizza=new PepperoniPizza();
        }
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

但是压力来自于增加更多的披萨类型
你发现其他餐厅又多了很多流行风味的披萨:ClamPizza(蛤蜊披萨)、VeggiePizza(素食披萨)。很明显你要赶上他们,需要把这些加入你的菜单中,而最近GreekPizza(希腊披萨)卖的不好,所以你决定将它从菜单中去掉:

Pizza orderPizza(String type){
        Pizza pizza ;
        if(type.equals("cheese")){
               pizza=new CheesePizza();
        }//else if(type.equals("greek")){
        //    pizza=new GreekPizza();
       //  }
        else if(type.equals("pepperoni")){
               pizza=new PepperoniPizza();
        }else if(type.equals("clam")){
               pizza=new ClamPizza();
        }else if(type.equals("veggie")){
               pizza=new VeggiePizza();
        }
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

很明显,如果实例化“某些”具体类,将使orderPizza()出问题,而且也无法让orderPizza()对修改关闭;但是,现在我们已经知道哪些会代表。那些不会改变,该是封装的时候了。
我们把创建对象的代码从orderPizza()方法中抽离。
然后把这部分的代码搬到另一个对象中,这个心对象只管如何创建披萨,如果任何对象想要创建披萨,找它就对了。
我们称这个新对象为“工厂”
工厂(factory)处理创建对象细节,一旦有了SimplePizzaFactory,orderPizza()就变成此对象的客户。
建立一个简单披萨工厂

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

重做PizzaStore类
修改我们的客户代码,我们要做的是仰仗工厂来为我们创建披萨。

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

定义简单工厂
简单工厂其实不是一个设计模式,反而比较像是一种编程习惯,但由于进场被使用。所以任务是工厂模式。
我们来看看新的披萨店类图:新的披萨店类图
上面是简单工厂带来的热身,接下来登场的是两个重量级的模式,他们都是工厂。
*再次提醒:在设计模式中,所谓的“实现一个接口”并“不一定”表示“写一个类,并利用implement关键词来实现某个java接口”。“实现一个接口”泛指“实现某个超级类型(可以使类或接口)的某个方法”
加盟披萨店
你的披萨店经营有成,击败了竞争者,现在大家都希望比萨店能在自己家附加有加盟店。每家加盟店都可能想要提供不同风味的披萨(纽约,芝加哥,加州)。
我么已经有一个做法……
如果利用SimplePizzaFactory,写出三种不同的工厂,分别是
NYPizzaFactory,ChicagoPizzaFactory,CaliforniaPizzaFactory,各地加盟店都有适合的工厂可以使用,这是一种做法。

NYPizzaFactory nyFactory=new NYPizzaFactory();
PizzaStore nyStore=new PizzaStore(nyFactory);
nyFactory.orderPizza("Veggie");

但是你想要多一些质量控制……
你发现加盟店确实采用你的工厂创建披萨,但是其他部分,却开始采用他们自创的流程:烘烤的做法有些差异,不要切片,使用其他厂商的盒子。
在我们稍早的SimplePizzaFactory代码之前,制作披萨的代码绑在PizzaStore里,但是这么做却没有弹性,那么,该怎么办。
给披萨店使用的框架
有个做法可让披萨制作轰动局限于PizzaStore类,而同时又能让这些加盟店依然可以自由地制作该区域的风味。
所要做的事情,就是把createPizza()方法放回到PizzaStore中,不过要把它设置成“抽象方法”,然后为每个区域风味创建一个PizzaStore的子类。

bastract class PizzaStore(){
    public Pizza orderPizza(String type){
         Pizza pizza;

        pizza=createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
    abstract Pizza createPizza(String type);
}

现在已近有一个PizzaStore作为超类,让每个域类型(NYPizzaFactory,ChicagoPizzaFactory,CaliforniaPizzaFactory)都继承这个PizzaStore,每个子类各自决定如何制造披萨,
允许子类做决定
别忘了,PizzaStore已经有一个不错的订单系统,由orderPizza()方法负责处理订单,我们现在要让createPizza()能够应对这些变化来负责创建正确种类的披萨,做法是让PizzaStore的各个子类负责定义自己的createPizza()方法,所以我们会得到一些PizzaStore具体的子类,每个子类都有自己的披萨变体,而任然适合PizzaStore框架,并使用调试好的orderPizza()方法。
图片描述
让我们开一家披萨店吧!
纽约风味店:

class NYPizzaStore extends PizzaStore{
    Pizza createPizza(String type){
         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();
        }
    }
}

披萨类:

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");
    }
    public String getName(){
        return name;
    }
}

我们需要一些具体子类,来定义纽约和芝加哥风味的芝士披萨。

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

class ChicagoStyleCheesePizza extends  Pizza{
    public ChicagoStyleCheesePizza(){
        name="Chicago Style Deep Dish Cheese Pizza";
        dough="Extra Thick Crust Dough";
        sauce="Plum Tomato Sauce";
        toppings.add("Shredded Mozzarella Cheese");
    }

    @Override
    void cut() {
        System.out.println("Cutting the pizza into square slices");
    }
}

你已经等得够久了,来吃些披萨吧

class PizzaTestDrive{
    public static void main(String[] args){
            PizzaStore nyStore =new NYPizzaStore();
            PizzaStore chicagoStore=new ChicagoPizzaStore();
            Pizza pizza=nyStore.orderPizza("cheese);
            System.out.println("Ethan ordered a"+pizza.getName()+"\n")
    }
}

认识工厂方法模式的时刻终于到了
所有工厂模式都用来封装对象的创建,工厂方法模式通过让子类决定改创建的对象是什么,来达到将对象创建的过程封装的目的,看看类图:
图片描述
图片描述
另一个观点:平行的类层级
图片描述
定义工厂方法模式
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
工厂模式类图:
工厂模式类图
工厂方法模式能够封装具体类型的实例化,抽象的Creator提供了一个创建对象的方法的接口,在抽象的Creator中,任何其他实现的方法,都可能使用到这个工厂方法所制造出来的产品,但只有子类真正实现这个工厂方法并创建产品。
看看对象依赖
当你直接实例化一个对象时,就是在依赖它的具体类,返回前页看看这个依赖性很高的比萨店例子,它由披萨店类来创建所有的披萨对象,而不是委托给工厂。
依赖倒置原则:要依赖抽象,不要依赖具体类。
首先,这个原则听起来很像是“针对接口编程,不针对实现编程”。这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层或底层组件,“两者”都应该依赖抽象。
让我们看前面,PizzaStore是“高层组件”,而披萨实现是“底层组件”很清楚地,PizzaStore依赖这些具体披萨类。
现在这个原则告诉我们,应该重写代码以便于我们依赖抽象类,而不依赖具体类。对于高层及底层模块都应该如此。
依赖倒置原则,究竟倒置在哪里?
你会注意到前面的底层主键现在竟然依赖高层的抽象。同样的,高层组件现在也依赖相同的抽象。
倒置你的思考方式
先从顶端开始,然后往下倒具体类,但是,正如你所看到的你不想让披萨店理会这些具体类,要不然披萨店将全都依赖这些具体类,“倒置”你的想法,别从顶端开始,而是从披萨开始,然后想想看能抽象化些什么。
所有的披萨应该共享一个Pizza接口。
现在回头重新思考如何设计披萨店。
必须靠一个工厂来将这些具体类取出披萨,一旦这样做了,各种不同的具体披萨类型就只能依赖一个抽象,而披萨店也会依赖这个抽象,我们已经倒置了一个商店依赖具体类的设计。
几个指导方针帮助你遵循此原则
1.变量不可能持有具体类的引用。
2.不要让类派生自具体类。
3.不要覆盖基类中已实现的方法。
回到披萨店
有些加盟店,使用低价原料来增加利润,你必须采取一些手段,以免回调你的披萨店平牌。
你打算建造一家成产原料的工厂,并将原料运送到各家加盟店,那么剩下最后一个问题,不同的区域原料是不一样的,对于两个加盟店给出了两组不同的原料:
图片描述
建造原料工厂
我们要建造一个工厂来生产原料,这个工厂负责创建原料家族中的每一种原料。

interface PizzaIngredientFactory{
    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
    public Clams createClams();

}

要做的事情是:
1.为每个区域建造一个工厂,你需要创建一个继承自PizzaIngredientFactory的子类来实现每一个创建方法。
2.实现一组原料类供工厂使用,例如ReggianoCheese,RedPeppers,ThickCrustDough.这些类可以在何时的区域间共享。
3.然后你仍然需要将这一切组织起来,将新的原料工厂整合进旧的PizzaStore代码中。
创建纽约的原料工厂

class NYPizzaIngredientFactory implements PizzaIngredientFactory{

    @Override
    public Dough createDough() {
        return null;
    }

    @Override
    public Sauce createSauce() {
        return null;
    }

    @Override
    public Cheese createCheese() {
        return null;
    }

    @Override
    public Veggies[] createVeggies() {
        return new Veggies[0];
    }

    @Override
    public Pepperoni createPepperoni() {
        return null;
    }

    @Override
    public Clams createClams() {
        return null;
    }
}

重做披萨

abstract class Pizza{
    String name;
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clams;
    abstract void prepare();
    void cut(){
        System.out.println("Cutting the pizza into diagonal slices");
    }
    void box(){
        System.out.println("Place pizza in official PizzaStore box");
    }
    void setName(String name){
        this.name=name;
    }
    public String getName(){
        return name;
    }
    public void toString2(){
        //这里打印披萨的代码
    }
}

继续重做披萨

class CheesePizza extends Pizza{
    PizzaIngredientFactory ingredientFactory;
    public CheesePizza(PizzaIngredientFactory ingredientFactory){
        this.ingredientFactory=ingredientFactory;
    }
    @Override
    void prepare() {
        System.out.println("Preparing"+name);
        dough=ingredientFactory.createDough();
        sauce=ingredientFactory.createSauce();
        cheese=ingredientFactory.createCheese();
    }
}

再回到披萨店

class NYPizzaStore extends PizzaStore{
   protected Pizza createPizza(String item){
       Pizza pizza=null;
       PizzaIngredientFactory ingredientFactory=new NYPizzaIngredientFactory();
       if(item.equals("cheese")){
           pizza=new CheesePizza(ingredientFactory);
           pizza.setName("New ……");
       }else if(item.equals("veggie")){
           //……
       }
       return pizza;
   }
}

我们做了些什么?
我们引入新类型的工厂,也就是所谓的抽象工厂,来创建披萨原料家族。
通过抽象工厂所提供的接口可以创建产品的家族,利用这个接口书写代码,我们的代码将从实际工厂解耦,以便在不同上下文中实现各式各样的工厂,制造出各种不同的产品。
定义抽象工厂模式
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
抽象工厂模式类图:
图片描述
这是一张更复杂的类图,让我们从PizzaStore的观点来看一看它:
图片描述
工厂方法与抽象工厂区别?
待续。

点击查看更多内容
11人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
26
获赞与收藏
228

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消