美文网首页设计模式系列教程
设计模式系列教程—Factory Pattern(工厂模式)

设计模式系列教程—Factory Pattern(工厂模式)

作者: Vander1991 | 来源:发表于2019-05-14 21:16 被阅读0次

    4 Factory Pattern(工厂模式)

    前言:工厂模式是为了解决new的问题
    案例分析:
    REQ1:Vander作为pizza店的老板,具有一整套制作pizza的流程,准备食材、烘焙、切片、包装,随着pizza种类的渐渐增加,设计如下:

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

    分析:随着pizza店的发展,后期发现要制作越来越多的pizza,而且种类也是超级多,多达几十种pizza,而且有些旧类型的pizza,几乎没人点,greek和pepperoni pizza就从菜单上撤离了,而增加了FruitsPizza、HawaiiPizza、DurianPizza,所以上面orderPizza()方法就会频繁地改动,经常改动导致pizza店的订单系统经常处在维护状态,这非常不利于接单,很明显,实例化某些具体类,将使得orderPizza()方法出问题,而且这违反了“开放-关闭原则”,这个方法就无法对修改关闭了;但是现在已经知道哪些是会改变的,哪些是不会改变的部分,接下来用封装来解决上面的问题。
    解决方法1:Vander 又开始改进设计了
    将创建pizza的代码封装起来转移到另一个对象中,然后orderPizza()方法专门负责pizza的制作工作。new pizza这件事专门交给其他类去负责。
    重新修改PizzaStore的代码:

    public class PizzaStore {
    
        public SimplePizzaFactory simplePizzaFactory;
        
        public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
            this.simplePizzaFactory = simplePizzaFactory;
        }
        
        public Pizza orderPizza(String type) {
            Pizza pizza = simplePizzaFactory.producePizza(type);
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            
            return pizza;
        }
        
    }
    
    简单工厂的代码:
    public class SimplePizzaFactory {
    
        public Pizza producePizza(String type) {
            Pizza pizza = new Pizza();
            
            if(type.equals("cheese")) {
                pizza = new CheesePizza();
            } else if(type.equals("greek")) {
                pizza = new GreekPizza();
            } else if(type.equals("pepperoni")) {
                pizza = new Pepperoni();
            }
            return pizza;
        }
        
    }
    

    现在相当于,每次修改SimplePizzaFactory的代码的时候,可以暂时不对线上的版本进行影响,修改完SimplePizzaFactory之后再替换掉线上的SimplePizzaFactory就OK了。而orderPizza()方法相当于变成了SimplePizzaFactory的使用者,它使用pizza工厂来完成pizza的new操作。
    下面定义简单工厂,实际上简单工厂不算做一个设计模式,更多地是一种编程习惯,我们会经常用到它,所以也是非常重要的。


    image.png

    REQ2:Vander的Pizza店名气非常大,吸引了很多大腕来加盟,但是由于区域的差异,每个加盟店都可能需要作出不同风格的pizza,例如四川的希望水果Pizza里面有辣椒,广东的则希望水果Pizza里面多一点菠萝,这受到了开店地区当地人口味的影响。
    首先要完成加盟店,所以PizzaStore的SimpleFactory就成了一些地区PizzaFactory,如GuangDongPizzaFactory、SiChuanPizzaFactory。然后他们各自使用各自的Pizza类,例如广东的创造广东类型的Pizza-GDPizza;四川的创作自己类型的Pizza-SCPizza,然后他们各自再覆盖原来的pizza里面的prepare、bake、cut、box等方法,他们自创了自己的流程,而且box的时候还用了别的商家的盒子,这完全就是打着Vander Pizza店的牌子在乱搞嘛。
    问题来了,怎样才能加强质量把控,又不失弹性,让这些地区Pizza店可以依照自己的口味去。

    解决方法2:工厂方法模式
    Vander思前想后就是想不到合适的办法,这时候他只能去请教Panda大师,Panda大师说这个可以用工厂方法解决,首先要给加盟Pizza点使用框架,先从PizzaStore开始改造:
    由于想控制加盟店制作Pizza类中的bake、cut、box,不能让这些加盟店随便乱弄,也不能让它们使用别的公司的包装,需要把控整个制作过程,把PizzaStore的producePizza方法设为抽象类,让GDPizzaStore和SCPizzaStore去继承,它们在生产完Pizza之后只能调用父类的orderPizza方法,继续完成准备、烘焙、切片、包装。让我们来看看现在的改造吧:

    image.png
    public abstract class PizzaStore {
        
        public final Pizza orderPizza(String type) {
            Pizza pizza = producePizza(type);
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            
            return pizza;
        }
        
        protected abstract Pizza producePizza(String type);
        
    }
    
    public abstract class Pizza {
    
        protected String name;
        //面团类型
        protected String dough;
        //酱料
        protected String sauce;
        //佐料    
        protected ArrayList<String> toppings = new ArrayList<String>(10);
        
        public 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 mins at 350℃");
        }
        
        public void cut() {
            System.out.println("Cutting the pizza into diagonal slices");
        }
        
        public void box() {
            System.out.println("Boxing the pizza");
        }
        
        public String getName() {
            return name;
        }
        
    }
    
    public class GDStyleFruitsPizza extends Pizza {
    
        public GDStyleFruitsPizza() {
            name = "GuangDong Style Fruits Pizza";
            dough = "Thin Crust Dough";
            sauce = "Marinara Sauce";
            
            toppings.add("GuangDong Delicious Cheese");
            toppings.add("GuangDong Pipeapple");
        }
        
    }
    
    public class SCStyleFruitsPizza extends Pizza {
    
        public SCStyleFruitsPizza() {
            name = "SiChuan Style Fruits Pizza";
            dough = "Thick Crust Dough";
            sauce = "Marinara Sauce";
            
            toppings.add("SiChuan Delicious Cheese");
            toppings.add("SiChuan Pepper");
        }
        
    }
    
    public class GDPizzaStore extends PizzaStore {
    
        @Override
        public Pizza producePizza(String type) {
            Pizza pizza = null;
            
            if(type.equals("cheese")) {
                pizza = new GDStyleCheesePizza();
            } else if(type.equals("fruits")) {
                pizza = new GDStyleFruitsPizza();
            } else if(type.equals("durian")) {
                pizza = new GDStyleDurianPizza();
            } else if(type.equals("Hawaii")) {
                pizza = new GDStyleHawaiiPizza();
            }
            return pizza;
        }
        
    }
    
    public class SCPizzaStore extends PizzaStore {
    
        @Override
        public Pizza producePizza(String type) {
            Pizza pizza = null;
    
            if(type.equals("cheese")) {
                pizza = new SCStyleCheesePizza();
            } else if(type.equals("fruits")) {
                pizza = new SCStyleFruitsPizza();
            } else if(type.equals("durian")) {
                pizza = new SCStyleDurianPizza();
            } else if(type.equals("Hawaii")) {
                pizza = new SCStyleHawaiiPizza();
            }
            return pizza;
        }
        
    }
    

    说明:工厂方法用来处理对象的创建,并将这样的行为封装在子类中,这样客户程序中关于超类的代码就和子类对象创建代码解耦了。也就是说不管以后有多少加盟店,父类PizzaStore都不会去改变了,任由子类PizzaStore天翻地覆,PizzaStore都以不变应万变。工厂方法模式通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。


    image.png

    Vander就纳闷了,我之前用的简单工厂难道不能实现吗,然后Vander就想Panda大师这么干到底有什么优势,接着Vander发现他也能给像Panda那样做到把控整个控制过程。实际上关键的代码就是将PizzaStore的orderPizza()方法写成final,让子类加盟店都无法修改整个制作过程,接着让Pizza类的box()方法也写成final,这样子子类Pizza就无法使用别的商家的包装了。接着Vander给出了他的实现。


    image.png

    以下是主要的代码:

    public class GDPizzaStore extends PizzaStore {
    
        public SimplePizzaFactory simplePizzaFactory;
        
        public GDPizzaStore(SimplePizzaFactory simplePizzaFactory) {
            super(simplePizzaFactory);
        }
        
    }
    
    public class GDPizzaFactory extends SimplePizzaFactory{
    
        public Pizza producePizza(String type) {
            Pizza pizza = null;
            
            if(type.equals("cheese")) {
                pizza = new GDStyleCheesePizza();
            } else if(type.equals("fruits")) {
                pizza = new GDStyleFruitsPizza();
            } else if(type.equals("durian")) {
                pizza = new GDStyleDurianPizza();
            } else if(type.equals("Hawaii")) {
                pizza = new GDStyleHawaiiPizza();
            }
            return pizza;
        }
        
    }
    
    public class PizzaStore {
    
        public SimplePizzaFactory simplePizzaFactory;
        
        public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
            this.simplePizzaFactory = simplePizzaFactory;
        }
        
        public final Pizza orderPizza(String type) {
            Pizza pizza = simplePizzaFactory.producePizza(type);
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            
            return pizza;
        }
        
    }
    
    public abstract class SimplePizzaFactory {
    
        public abstract Pizza producePizza(String type); 
        
    }
    

    说明:从代码上看来,其实确实跟工厂方法模式差不多,只是把producePizza方法放到了简单工厂而已,但是这么操作最后在运行的时候,必须要实例化相应的工厂,例如GDPizzaStore需要传入GDPizzaFactory,则需要实例化GDPizzaFactory,并且还不能传错,传错了就会导致最后广东的Pizza店卖的确是其它工厂造出来的Pizza。
    下面比对一下工厂方法模式和简单工厂是怎么造出Pizza的。
    简单工厂的制作Pizza方法:

    public class Main {
        public static void main(String[] args) {
            GDPizzaFactory gdFactory = new GDPizzaFactory();
            GDPizzaStore gdPizzaStore = new GDPizzaStore(gdFactory);
            gdPizzaStore.orderPizza("fruits");
            
            SCPizzaFactory scFactory = new SCPizzaFactory();
            SCPizzaStore scPizzaStore = new SCPizzaStore(scFactory);
            scPizzaStore.orderPizza("fruits");
        }
    }
    

    工厂方法的制作Pizza方法:

    public class Main {
        public static void main(String[] args) {
            GDPizzaStore gdPizzaStore = new GDPizzaStore();
            Pizza gdFruitsPizza1 = gdPizzaStore.orderPizza("fruits");
            SCPizzaStore scPizzaStore = new SCPizzaStore();
            Pizza scfruitsPizza2 = scPizzaStore.orderPizza("fruits");
        }
    }
    

    最后Vander发现原来他的方法需要先实例化GDPizzaFactory,然后将Factory作为参数放进GDPizzaStore里面,Vander就想,既然都是GDPizzaFactory对应GDPizzaStore,SCPizzaFactory对应SCPizzaStore,那我直接在GDPizzaStore里面实例化GDPizzaFactory,SCPizzaStore里面实例化SCPizzaFactory不就好了。接着又进行了一轮改造:

    public class GDPizzaStore extends PizzaStore {
        
        public GDPizzaStore() {
            simplePizzaFactory = new GDPizzaFactory();
        }
        
    }
    
    public class PizzaStore {
    
        protected SimplePizzaFactory simplePizzaFactory;
        
        public final Pizza orderPizza(String type) {
            Pizza pizza = simplePizzaFactory.producePizza(type);
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            
            return pizza;
        }
        
    }
    

    说明:最后虽然在运行时的调用是一样的,但是这里相当于直接让GDPizzaStore和GDPizzaFactory直接绑定在一起了,也增加了代码的耦合度。而且每次多开一个加盟店,都需要实现PizzaStore和PizzaFactory多一次,确实也没多大必要。

    REQ3:由于Pizza大家已经吃腻了,所以Vander为了招来更多的客户,想扩展菜单了,现在不只是卖Pizza了,现在Pizza店学习必胜客的方式,还卖鸡翅、汉堡并且还出售各种独家饮料。那么怎么扩展接口呢?在PizzaStore基类中继续添加produceFiredChicken()、produceBurger()、produceBeverage(),这么做的后果就是所有的加盟店都得跟着做改变,但是有些加盟店Pizza生意做得火热,觉得根本没必要添加这么多种类进去。这时候Vander想到了他之前实现的SimpleFactory2,就是将简单工厂编程“抽象工厂”,让子类工厂去完成产品的创建,这样做的好处就是加盟店的PizzaStore都可以不用动,而且把这些食物的创建都放到了一个大工厂去做这么一件事情了。
    下面是Vander的设计:

    image.png

    这个设计图看起来有一定的复杂度,但是其实就是在SimpleFactory2上的PizzaFactory改为了FoodFactory添加了几个生产其他产品的方法,本来只有一个Pizza的抽象类,现在多了Beverage、Burger、FiredChicken这几个抽象类,相当于现在FoodFactory是一个大工厂来生产各种各样的产品。可能你会发现这FoodFactory里面实际上不就是工厂方法模式嘛,没错!就是这样相当于FoodFactory里面生产各种各样的食物,但是生产的食物是什么样的Pizza、什么样的炸鸡,都是由子类做决定的。
    实际上SimpleFactory2已经不算是简单工厂了,简单工厂应该是更单纯一点的,通过一个含参的方法来实例化产品类。SimpleFactory2经过改进之后已经成为了抽象工厂模式了。
    抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产出的具体产品是什么,这样一来,客户就从具体的产品中被解耦了。上面这段话通俗地说,就是像子类工厂完成Pizza的创建,然后PizzaStore在orderPizza()方法中直接是拿基类Pizza类来进行prepare、bake、cut、box等操作,而不用理会是哪种口味哪个区域的Pizza。从而达到了PizzaStore与具体的Pizza类的解耦。
    下面我们梳理一下整个过程,首先刚开始PizzaStore依赖于具体的Pizza类,如下图所示:


    image.png

    而且随着Pizza类型的增加,依赖将会越来越多,代码里面减少具体的类的依赖是必须要做的,接下来我们看看我们后面的实现:


    image.png

    这样就依赖抽象的类Pizza。(遵循了依赖抽象而不是具体的类的原则)
    我们发现高层组件(PiizaStore)和底层组件(具体的Piizza子类)都依赖于Pizza的抽象类。我们一般进行设计的时候,首先设计PizzaStore,PizzaStore要能生产出很多具体的不同类型的Pizza,其实这时候你可以倒过来想,从具体的Pizza想起,发现应该先抽象出一个Pizza基类,然后这个Pizza基类直接给PizzaStore用,然后就发现这个抽象的Pizza基类就像桥梁一样连接着PizzaStore和具体的Pizza类。这里面就包含了“依赖倒置原则”,即在从上往下的设计的同时,不妨先想想能不能从下往上。
    下面有三个指导方针来避免违反“依赖倒置原则”:
    1、变量不可以持有具体类的引用。(如果需要用new,用工厂代替)
    2、不要让类派生自具体类。(如果派生具体类了,你就会依赖具体类,请派生一个抽象)
    3、不要覆盖基类中已实现的方法(基类已经实现了,说明具有通用性,否则请设为抽象)

    最后的最后,我们又来总结我们现在现有的设计模式武器。

    面向对象基础

    抽象、封装、多态、继承

    六大原则

    设计原则一:封装变化
    设计原则二:针对接口编程,不针对实现编程。
    设计原则三:多用组合,少用继承。
    设计原则四:为交互对象之间的松耦合设计而努力。
    设计原则五:对扩展开放,对修改关闭。
    设计原则六:依赖抽象,不要依赖于具体的类 。

    模式

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

    image.png

    抽象工厂模式:提供一个接口,用于创建相关对象或者是依赖对象的家族,而不需要明确指定具体的类(说白了就是一系列同类型的工厂方法集合)。

    相关文章

      网友评论

        本文标题:设计模式系列教程—Factory Pattern(工厂模式)

        本文链接:https://www.haomeiwen.com/subject/nlxyaqtx.html