• headfirst设计模式(4)—工厂模式


    开篇

    天天逛博客园,就是狠不下心来写篇博客,忙是一方面,但是说忙能有多忙呢,都有时间逛博客园,写篇博客的时间都没有?(这还真不好说)

    每次想到写一篇新的设计模式,我总会问自己:

    1,自己理解了吗?

    2,我能以一种简单且有逻辑的方式把它说出来吗?

    不说做到有的放矢,但是一本正经的胡说八道还是要有吧,起码要忽悠得头头是道嘛(手动斜眼笑)

    关于工厂模式的几个问题

    1,这个是拿来干什么的?

    2,怎么用?

    3,不用行不行?

    第一个和第三个问题,我现在就可以告诉你答案:早点下班,可以

    所有的设计模式对我来说都是为了减少工作量。关于减少工作量我的理解是:每个需求,都应该在它适当的时候出现适当的代码!这个太重要了

    代码偷懒,后期返工多

    过度设计,后期返工多

    设计模式+经验可以解决这个问题,其他的我还不知道。没有经验怎么办?两个要点:

    1,能用

    2,简洁

    首先要达到能用,然后就是尽量简洁,这样代码就不会太差。首先你要自己看得懂,然后是让队友看得懂。

    你知道你队友看到一堆烂的看都看不懂,也一句注释都没有的代码的时候的心理阴影面积吗?

    这其实也没什么,谁没填过别人的坑呢?关键是他知道你家在哪里,而且还知道你经常走夜路,就问你怕不怕?(卧槽,又跑题了。。)

    需求:你有一个披萨店,只卖一种披萨,代码如下:

    披萨:

    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 披萨类
     * @author skysea
     */
    public class Pizza {
        
        private String name;//披萨名称
        
        private String dough;//面团
        
        private String sauce;//酱料
        
        private List<String> toppings = new ArrayList<>();//佐料
        
        public Pizza() {
                this.name = "原味披萨";
                this.dough = "原味面团";
                this.sauce = "原味酱料";
        }
        
        void prepare() {
            System.out.println("开始准备披萨:" + name);
            
            System.out.println("开始处理面团:" + dough);
            
            System.out.println("添加酱料:" + sauce);
            
            System.out.println("添加佐料:");
            if(toppings.size() > 0) {
                for(String t : toppings) {
                    System.out.println(" " + t);
                }
            }
        }
        
        void bake() {
            System.out.println("烘焙25分钟..");
        }
        
        void cut() {
            System.out.println("披萨切片..");
        }
        
        void box() {
            System.out.println("披萨打包..");
        }
        
        public String getName() {
            return name;
        }
    }

    披萨店:

    /**
     * 只卖一种披萨的披萨店
     * @author skysea
     */
    public class PizzaStore {
        
        public Pizza orderPizza() {
            Pizza pizza = new Pizza();
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            return pizza;
        }
    }

     测试类:

    /**
     * pizza测试类
     * @author skysea
     */
    public class PizzaTest {
        public static void main(String[] args) {
            PizzaStore pizzaStore = new PizzaStore();
            Pizza pizza = pizzaStore.orderPizza();
            System.out.println("当前预定的披萨:" + pizza.getName());
        }
    }

     

    现在披萨店要拓展业务了,因为卖一种披萨顾客已经吃腻了,现在要开始添加新的披萨类型

    简单工厂模式

    Pizza类的改进

    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 披萨抽象类
     * 1,修改private -> protected(保证子类拥有这些属性)
     * 2,将Pizza定义为abstract类,防止被new,也是为后面的改造做准备
     * @author skysea
     */
    public abstract class Pizza {
        
        protected String name;//披萨名称
        
        protected String dough;//面团
    
        protected String sauce;//酱料
        
        protected List<String> toppings = new ArrayList<>();//佐料
        
        void prepare() {
            System.out.println("开始准备披萨:" + name);
            
            System.out.print("开始处理面团:" + dough);
            
            System.out.println("添加酱料:" + sauce);
            
            System.out.println("添加佐料:");
            for(String t : toppings) {
                System.out.println(" " + t);
            }
        }
        
        void bake() {
            System.out.println("烘焙25分钟..");
        }
        
        void cut() {
            System.out.println("披萨切片..");
        }
        
        void box() {
            System.out.println("披萨打包..");
        }
        
        public String getName() {
            return name;
        }
    
        @Override
        public String toString() {
            return "Pizza [name=" + name + ", dough=" + dough + ", sauce=" + sauce + ", toppings=" + toppings + "]";
        }
    }

    先给出新增的披萨

    芝士披萨:

    /**
     * 芝士披萨
     * @author skysea
     */
    public class CheesePizza extends Pizza{
        public CheesePizza() {
            this.name = "芝士披萨";
            this.dough = "芝士披萨的面团";
            this.sauce = "芝士披萨的酱料";
            this.toppings.add("很多芝士....");
        }
    }
    View Code

    蛤蜊披萨:

    /**
     * 蛤蜊披萨
     * @author skysea
     */
    public class ClamPizza extends Pizza {
        public ClamPizza() {
            this.name = "蛤蜊披萨";
            this.dough = "蛤蜊披萨的面团";
            this.sauce = "蛤蜊披萨的酱料";
            this.toppings.add("蛤蜊");
        }
    }
    View Code

    意大利烤肠披萨:

    /**
     * 意大利烤肠披萨
     * @author skysea
     */
    public class PepperoniPizza extends Pizza{
        
        public PepperoniPizza() {
            this.name = "意大利烤肠披萨";
            this.dough = "意大利烤肠披萨的面团";
            this.sauce = "意大利烤肠披萨的酱料";
            this.toppings.add("一大波意大利烤肠...");
        }
    }
    View Code

     素食比萨:

    /**
     * 素食比萨
     * @author skysea
     */
    public class VeggiePizza extends Pizza {
        public VeggiePizza() {
            name = "素食比萨";
            dough = "素食比萨的面团";
            sauce = "素食比萨的酱料";
            toppings.add("素食比萨");
            toppings.add("素食比萨佐料1");
            toppings.add("素食比萨佐料2");
        }
    }
    View Code

    贴了这么多代码,先给出一波简单的实现:

    /**
     * pizza店
     * @author skysea
     */
    public class PizzaStore {
    
        public Pizza orderPizza(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();
            }
            
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
    
            return pizza;
        }
    
    }

    在不考虑继续扩展披萨种类的时候,这样的实现有没有问题,一般来说,达到了可以用的标准,但是不好用,问题如下:

    1,没有兼容原来的一种披萨方法 public Pizza orderPizza(),相信我,每一个public方法都是很重要的,因为你不知道有多少地方用到过。当然也不是没办法知道,只是你知道也不一定就能改,就算你能改,也不一定改对。

    2,String类型的type太容易出错了,个人感觉对程序开发不友好,当然这个也要分情况,灵活和严谨本来就很难做到两全

    3,推荐取不到合适的type时抛异常,而不是返回空,便于排查问题(此处的if里面只是直接new返回的对象,实际情况远比现在的复杂)

    给出第二版:

    /**
     * pizza店
     * @author skysea
     */
    public class PizzaStore {
        
        public Pizza orderPizza() {
            return orderPizza(PizzaTypeEnum.CHEESE);
        }
     
        public Pizza orderPizza(PizzaTypeEnum type) {
            Pizza pizza;
     
            pizza = SimplePizzaFactory.getPizza(type);
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
    
            return pizza;
        }
    }

    SimplePizzaFactory:

    /**
     * 简单工厂类
     * @author skysea
     */
    public class SimplePizzaFactory {
    
        /**
         * 根据类型获取pizza
         * @param type
         * @return
         */
        public static final Pizza getPizza(PizzaTypeEnum type){
            switch (type) {
                case CHEESE: return new CheesePizza();
                case CLAM: return new ClamPizza();
                case PEPPERONI: return new PepperoniPizza();
                case VEGGIE: return new VeggiePizza();
                default: throw new NoSuchPizzaException(type.getCode());
            }
        }
    }

    辅助类(枚举,异常):

    /**
     * 定义pizza类型枚举
     * @author skysea
     *
     */
    public enum PizzaTypeEnum{
        /**
         * 芝士披萨
         */
        CHEESE("cheese"),
        /**
         * 意大利烤肠披萨
         */
        PEPPERONI("pepperoni"),
        /**
         * 蛤蜊披萨
         */
        CLAM("clam"),
        /**
         * 素食比萨
         */
        VEGGIE("veggie");
        private final String code;
        PizzaTypeEnum(String code) {
            this.code = code;
        }
        public String getCode() {
            return code;
        }
    }
    View Code
    /**
     * 没有匹配的pizza异常
     * @author skysea
     */
    public class NoSuchPizzaException extends RuntimeException{
        private static final long serialVersionUID = 6831396172306375611L;
        public NoSuchPizzaException(String message) {
            super(message);
        }
    }
    View Code

    测试类:

    /**
     * pizza测试类
     * @author skysea
     */
    public class PizzaTest {
     
        public static void main(String[] args) {
            PizzaStore store = new PizzaStore();
    
            Pizza pizza = store.orderPizza(PizzaTypeEnum.CHEESE);
            System.out.println(pizza);
     
            pizza = store.orderPizza(PizzaTypeEnum.VEGGIE);
            System.out.println(pizza);
        }
    }

    好了,代码写到这里,其实对于:新增披萨类型的这个需求的实现其实已经很好了。至少来说现阶段的需求实现了,其次就是对调用方友好,至少队友不会跑过来问你类型传啥,不会告诉你他string字符串传错了,不会在你改个类型的时候,还要通知他(当然这个也可以通过常量来处理)。

    吹了半天,来说说这段代码的问题,正常情况下,需求会是这样变:

    1,PepperoniPizza暂时不要了,一般来说,你问他要不要,他会说,这个要看后面的运营情况(我:...)

    2,你给我新加一个xx披萨

    现在需要改的是两个地方,一个是工厂类,一个是枚举,但是主要的流程是不用改了,如果你觉得还是很麻烦在不考虑性能的情况下,你还可以用反射来玩,改造一下工厂类(实现通过class来创建对象)和枚举(添加一个字段来存放type对应的class)就可以了,不赘述..

    第一波需求就差不多可以这样收手了,随着业务的发展,披萨店那叫一个红火啊,虽然中间也对代码做了很多新的披萨,但是由于PizzaStore相当稳定,也没出什么大问题。

    新的问题(开分店):

    1,旗舰店在芝加哥,现在要在纽约开一家新的店

    2,分店的披萨口味要根据当地的口味来进行调整,保证能够不失品牌特色的同时,也能满足当地独特的风味

    3,分店披萨的种类与暂时与旗舰店保持一致

    工厂方法模式

    先把所有的披萨列出来

    芝加哥的披萨:

    /**
     * 芝加哥芝士披萨
     * @author skysea
     */
    public class ChicagoStyleCheesePizza extends Pizza {
    
        public ChicagoStyleCheesePizza() { 
            name = "芝加哥芝士披萨";
            dough = "芝加哥芝士披萨面团";
            sauce = "芝加哥芝士披萨酱料";
     
            toppings.add("芝加哥芝士披萨调料1");
            toppings.add("芝加哥芝士披萨调料2");
        }
     
        @Override
        void cut() {
            System.out.println("芝加哥芝士披萨版切片...");
        }
    }
    /**
     * 芝加哥蛤蜊披萨
     * @author skysea
     */
    public class ChicagoStyleClamPizza extends Pizza {
        public ChicagoStyleClamPizza() {
            name = "芝加哥蛤蜊披萨";
            dough = "芝加哥蛤蜊披萨面团";
            sauce = "芝加哥蛤蜊披萨酱料";
     
            toppings.add("芝加哥蛤蜊披萨佐料1");
            toppings.add("芝加哥蛤蜊披萨佐料2");
        }
     
        @Override
        void cut() {
            System.out.println("芝加哥蛤蜊披萨版切片...");
        }
    }
    /**
     * 芝加哥意大利烤肠披萨
     * @author skysea
     */
    public class ChicagoStylePepperoniPizza extends Pizza {
        public ChicagoStylePepperoniPizza() {
            name = "芝加哥意大利烤肠披萨";
            dough = "芝加哥意大利烤肠披萨面团";
            sauce = "芝加哥意大利烤肠披萨酱料";
     
            toppings.add("芝加哥意大利烤肠披萨调料1");
            toppings.add("芝加哥意大利烤肠披萨调料2");
            toppings.add("芝加哥意大利烤肠披萨调料3");
            toppings.add("芝加哥意大利烤肠披萨调料4");
        }
     
        @Override
        void cut() {
            System.out.println("芝加哥意大利烤肠披萨版切片...");
        }
    }
    /**
     * 芝加哥素食比萨
     * @author skysea
     */
    public class ChicagoStyleVeggiePizza extends Pizza {
        public ChicagoStyleVeggiePizza() {
            name = "芝加哥素食比萨";
            dough = "芝加哥素食比萨的面团";
            sauce = "芝加哥素食比萨的酱料";
     
            toppings.add("芝加哥素食比萨调料1");
            toppings.add("芝加哥素食比萨调料2");
            toppings.add("芝加哥素食比萨调料3");
        }
     
        void cut() {
            System.out.println("芝加哥素食比萨版切片...");
        }
    }
    View Code

    纽约的披萨:

    /**
     * 纽约芝士披萨
     * @author skysea
     */
    public class NYStyleCheesePizza extends Pizza {
    
        public NYStyleCheesePizza() { 
            name = "纽约芝士披萨";
            dough = "纽约芝士披萨面团";
            sauce = "纽约芝士披萨酱料";
     
            toppings.add("纽约芝士披萨调料1");
            toppings.add("纽约芝士披萨调料2");
        }
     
        @Override
        void cut() {
            System.out.println("纽约芝士披萨版切片...");
        }
    }
    /**
     * 纽约蛤蜊披萨
     * @author skysea
     */
    public class NYStyleClamPizza extends Pizza {
        public NYStyleClamPizza() {
            name = "纽约蛤蜊披萨";
            dough = "纽约蛤蜊披萨面团";
            sauce = "纽约蛤蜊披萨酱料";
     
            toppings.add("纽约蛤蜊披萨佐料1");
            toppings.add("纽约蛤蜊披萨佐料2");
        }
     
        @Override
        void cut() {
            System.out.println("纽约蛤蜊披萨版切片...");
        }
    }
    /**
     * 纽约意大利烤肠披萨
     * @author skysea
     */
    public class NYStylePepperoniPizza extends Pizza {
        public NYStylePepperoniPizza() {
            name = "纽约意大利烤肠披萨";
            dough = "纽约意大利烤肠披萨面团";
            sauce = "纽约意大利烤肠披萨酱料";
     
            toppings.add("纽约意大利烤肠披萨调料1");
            toppings.add("纽约意大利烤肠披萨调料2");
            toppings.add("纽约意大利烤肠披萨调料3");
            toppings.add("纽约意大利烤肠披萨调料4");
        }
     
        @Override
        void cut() {
            System.out.println("纽约意大利烤肠披萨版切片...");
        }
    }
    /**
     * 纽约素食比萨
     * @author skysea
     */
    public class NYStyleVeggiePizza extends Pizza {
        public NYStyleVeggiePizza() {
            name = "纽约素食比萨";
            dough = "纽约素食比萨的面团";
            sauce = "纽约素食比萨的酱料";
     
            toppings.add("纽约素食比萨调料1");
            toppings.add("纽约素食比萨调料2");
            toppings.add("纽约素食比萨调料3");
        }
     
        void cut() {
            System.out.println("纽约素食比萨版切片...");
        }
    }
    View Code

    披萨倒是列完了,但是在实际的开发过程中,业务逻辑这么简单那是不可能的,想要改那什么旗舰店披萨的类名是很困难的

    一般要考虑:

    1,是不是单机,有没有其他外部系统在调用

    2,改动原来的代码有什么好处,更容易理解吗?迭代了几个版本过后垃圾代码太多了吗?

    3,影响大不大

    当然,我这里是随便造,你们呢,我就不知道了,嘿嘿嘿,所以碰到这种情况,一般来说要悠着点,看时间,也要看影响,开发就是这样,同一个功能,2天有2天的做法,5天有5天的做法,10天有10天的做法

    披萨店改造:

    /**
     * 披萨店抽象类
     * @author skysea
     */
    public abstract class PizzaStore {
     
        abstract Pizza createPizza(String item);
     
        public Pizza orderPizza(String type) {
            Pizza pizza = createPizza(type);
            System.out.println("--- 制作 " + pizza.getName() + " ---");
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            return pizza;
        }
    }
    /**
     * 芝加哥披萨店
     * @author skysea
     */
    public class ChicagoPizzaStore extends PizzaStore {
        public static final String CHEESE = "cheese";
        public static final String VEGGIE = "veggie";
        public static final String CLAM = "clam";
        public static final String PEPPERONI = "pepperoni";
        Pizza createPizza(String item) {
            if (CHEESE.equals(item)) {
                return new ChicagoStyleCheesePizza();
            } else if (VEGGIE.equals(item)) {
                return new ChicagoStyleVeggiePizza();
            } else if (CLAM.equals(item)) {
                return new ChicagoStyleClamPizza();
            } else if (PEPPERONI.equals(item)) {
                return new ChicagoStylePepperoniPizza();
            } else {
                throw new NoSuchPizzaException(item);
            }
        }
    }

    纽约披萨店(和芝加哥披萨店几乎一毛一样,这里就不展开了):

    /**
     * 纽约披萨店
     * @author skysea
     */
    public class NYPizzaStore extends PizzaStore {
    
        public static final String CHEESE = "cheese";
        public static final String VEGGIE = "veggie";
        public static final String CLAM = "clam";
        public static final String PEPPERONI = "pepperoni";
        Pizza createPizza(String item) {
            if (CHEESE.equals(item)) {
                return new NYStyleCheesePizza();
            } else if (VEGGIE.equals(item)) {
                return new NYStyleVeggiePizza();
            } else if (CLAM.equals(item)) {
                return new NYStyleClamPizza();
            } else if (PEPPERONI.equals(item)) {
                return new NYStylePepperoniPizza();
            } else {
                throw new NoSuchPizzaException(item);
            }
        }
    }
    View Code

    这段代码有三个问题要理解清楚:

    1,这个地方为啥要弄个抽象类出来?

    这个就要结合实际来理解了,分店与分店之间,需不需要统一规范化管理?需不需要保证自己的特色?答案毫无疑问,都是需要的

    这个地方制作披萨的过程,毫无疑问是肯定要一致的。就像外卖一样,下单,炒菜,配送。整套流程都是这样,不能说你出来就开始炒菜了啊,这不科学。不一样的地方就是,你炒的什么菜,好不好吃。配送得快不快,稳不稳,服务好不好。

    所以,抽象类的意义就是:规范、特色

    2,factory咋个不见了?

    因为把它和具体的store合并在一起了,这样又引申出另外一个问题:为啥要合并?因为store现在充当的角色就是facotry,刚才说过的制作过程已经放到父类中实现了,现在只需要在具体的store中去解决披萨的创建问题

    3,为啥又不用枚举了,弄个String来创建pizza?

    如果还是单机,用枚举当然会比直接扔个string来得稳当。

    开了分店,要是每个分店都是一套完整的服务在玩,丢个string,要比枚举来得好。原因有2:传输过程中的序列化和反序列化、更加灵活(客户端不用每次都因为这个原因要去升级对应的包,特别是多个版本在跑得时候,升级了又会导致其他东西不能玩)

    测试类:

    /**
     * 披萨测试类
     * @author skysea
     */
    public class PizzaTest {
     
        public static void main(String[] args) {
            PizzaStore nyStore = new NYPizzaStore();
            PizzaStore chicagoStore = new ChicagoPizzaStore();
     
            Pizza pizza = nyStore.orderPizza(NYPizzaStore.CHEESE);
            System.out.println("Ethan ordered a " + pizza.getName() + "
    ");
     
            pizza = chicagoStore.orderPizza(ChicagoPizzaStore.CHEESE);
            System.out.println("Joel ordered a " + pizza.getName() + "
    ");
    
            pizza = nyStore.orderPizza(NYPizzaStore.CLAM);
            System.out.println("Ethan ordered a " + pizza.getName() + "
    ");
     
            pizza = chicagoStore.orderPizza(ChicagoPizzaStore.CLAM);
            System.out.println("Joel ordered a " + pizza.getName() + "
    ");
    
            pizza = nyStore.orderPizza(NYPizzaStore.PEPPERONI);
            System.out.println("Ethan ordered a " + pizza.getName() + "
    ");
     
            pizza = chicagoStore.orderPizza(ChicagoPizzaStore.PEPPERONI);
            System.out.println("Joel ordered a " + pizza.getName() + "
    ");
    
            pizza = nyStore.orderPizza(NYPizzaStore.VEGGIE);
            System.out.println("Ethan ordered a " + pizza.getName() + "
    ");
     
            pizza = chicagoStore.orderPizza(ChicagoPizzaStore.VEGGIE);
            System.out.println("Joel ordered a " + pizza.getName() + "
    ");
        }
    }

    结果(结果太多了,就不全部截图出来了):

  • 相关阅读:
    归并排序
    快速排序
    冒泡排序
    排序算法复杂度
    [LeetCode] 20. Valid Parentheses ☆(括号匹配问题)
    makefile编写helloworld
    shell的通俗理解
    PID三种参数的理解
    PID的原理
    PID控制温度
  • 原文地址:https://www.cnblogs.com/skyseavae/p/8030304.html
Copyright © 2020-2023  润新知