简单工厂-工厂方法-抽象工厂模式

新增一个OO设计原则:
要依赖抽象,不要依赖具体类
1 简单工厂

  • 针对接口编程,可以隔离掉以后系统可能发生的一大堆改变。为什么呢?如果代码是针对接口而写,那么通过多态,它可以与任何新类实现该接口。但是,当代码使用大量的具体类时,等于是自找麻烦,因为一旦加入新的具体类,就必须改变代码。也就是说,你的代码并非“对修改关闭”。想用新的具体类型来扩展代码,必须重新打开它。所以,当遇到这样的问题时,就应该回到OO设计原则去寻找线索。我们的第一个设计原则用来处理改变,并帮助我们“找出会变化的方面,把它们从不改变的部分分离出来“。

如下面代码示例暴露出的问题

/**
 * @author hgl
 * @data 2018年5月6日
 * @description pizza抽象类
 */
public abstract class Pizza {

    public String name;
    //所有的比萨都需要做一些准备,如擀面皮,酱汁,加上配料(如芝士等)
    /**
     * 面团
     */
    public String dough;

    /**
     * 酱汁
     */
    public String sauce;


    /**
     * 配料
     */
    public List toppings = new ArrayList();

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

    //所有的比萨都会有这些动作
    /**
     * void
     * description:烤的动作
     */
    public void bake(){
        System.out.println("Bake for 25 minutes at 350");   
    }

    /**
     * void
     * description:切的动作
     */
    public void cut(){
        System.out.println("Cutting the pizza into diagonal slices");
    }

    /**
     * void
     * description:包装的动作
     */
    public void box(){
        System.out.println("Place piaaz in official PizzaStore box");
    }

    /**
     * String
     * @return
     * description:返回这个pizza的名字
     */
    public String getName(){
        return name;
    }

}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 奶酪比萨
 */
public class Cheese extends Pizza {

    public Cheese(){
        name = "Cheese Pizza";
        dough = "Thin Crust Dough";
        toppings.add("cheese");
    }
}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 蛤蜊比萨
 */
public class Clam extends Pizza {

    public Clam(){
        name = "Clam Pizza";
    }
}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 希腊比萨
 */
public class Greek extends Pizza{

    public Greek(){
        name = "Greek Pizza";
        dough = "Thin Crust Dough";
        sauce = "Greek sauce";
        toppings.add("unique toppings");
    }
}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 比萨店
 */
public class PizzaStore {

    public Pizza orderPizza(String type){
        Pizza pizza;
        /*
          if判断中的部分是变化的部分。随着时间过去,比萨菜单
          会改变,比如加入新的比萨。这里就必须一改再改。
        */
        if(type.equals("cheese")){
            pizza = new Cheese();
        }else if(type.equals("greek")){
            pizza = new Greek();
        }else if(type.equals("clam")){
            pizza = new Clam();
        }else{
            throw new RuntimeException("Error:invalid type");
        }
        /*
          下面的是不会改变的,所有的比萨必须要经过这几个程序。
        */
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

}

现在最好将创建对象移到orderPizza()之外,但怎么做呢?
可以把创建比萨的代码移到另外一个对象中,由这个新对象专职创建比萨。

我们称这个新对象为“工厂”
工厂处理创建对象的细节,一旦有了工厂,orderPizza就变成此对象的客户。当需要比萨时,就叫比萨工厂做一个。那些orderPizza()方法需要知道希腊比萨或者蛤蜊比萨的日子一去不复返了。现在orderPizza()方法只关心从工厂得到了一个比萨,而这个比萨实现了Pizza接口,所以它可以调用prepare(),bake(),cut(),box()来分别进行准备,烘烤,切片,装盒。

  • 建立一个简单比萨工厂
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 简单比萨工厂
 */
public class SimplePizzaFactory {

    public Pizza createPizza(String type){
        Pizza pizza = null;
        if(type.equals("cheese")){
            pizza = new Cheese();
        }else if(type.equals("greek")){
            pizza = new Greek();
        }else if(type.equals("clam")){
            pizza = new Clam();
        }else{
            throw new RuntimeException("Error:invalid type");
        }
        return pizza;
    }
}

这么做有什么好处?似乎把一个问题搬到另外一个对象罢了
答:别忘了,SimplePizzaFactory可以有许多的客户。虽然目前只看到orderPizza()方法是它的客户,然而,可能还有PizzaShopMenu(比萨菜单)类会利用这个工厂来取得比萨的价钱和描述。等等。所以,把创建比萨的代码包装进一个类,当以后实现改变时,只需要修改这个类即可。

新的PizzaStore的代码:

/**
 * @author hgl
 * @data 2018年5月6日
 * @description 比萨店
 */
public class PizzaStore {

    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory) {
        super();
        this.factory = factory;
    }

    public Pizza orderPizza(String type){
        Pizza pizza;
        pizza = factory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

}

简单工厂其实不是一个设计模式,反而比较像是一种编程习惯,但是经常会被使用。

2 工厂方法
现在出现了很多加盟店,而我们希望确保加盟店营运的质量,但是区域的差异呢?每家加盟店都可能想要提供不同风味的比萨(比方说纽约,芝加哥,加州)。质量怎么控制?比如加盟店采用它们自创的流程:烘烤的做法有些差异,不要切片,使用其他厂商的盒子。
我们希望建立一个框架,把加盟店和创建比萨捆绑在一起的同时又保持一定的弹性。
有个做法可让比萨制作活动局限于PizzaStore类,而同时又能让这些加盟店依然可以自由地制作该区域的风味。
所要做的事情,就是把createPizza()方法放回到PizzaStore中,不过要把它设置成“抽象方法”,然后为每个区域风味创建一个PizzaStore的子类。
改变后的代码:

public abstract class PizzaStore {

    public Pizza orderPizza(String type){
        Pizza pizza;
        pizza = createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
    /*
      现在,实例化比萨的责任被移到一个方法中,此方法就如同是
      一个“工厂”.  
      工厂方法是抽象的,所以依赖子类来处理对象的创建
      工厂方法必须返回一个产品。超类中定义的方法,通常使用到工厂方法的返回值
      工厂方法将客户(也就是超类中的代码,例如orderPizza())和实际创建
      具体产品的代码分割开来。
    */
    public abstract Pizza createPizza(String type);
}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 纽约店
 */
public class NYPizzaStore extends PizzaStore{

    @Override
    public Pizza createPizza(String type) {
        /*
         * 这里我只写了一个纽约pizza,其实有很多
         */
        if(type.equals("cheese")){
            return new NYStyleCheesePizza();
        }else return null;
    }


}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 芝加哥店
 */
public class ChicagoStore extends PizzaStore {

    @Override
    public Pizza createPizza(String type) {
        //这里我也只写了一个,当然还有很多中披萨
        if(type.equals("cheese")){
            return new ChicagoStyleCheesePizza();
        }
        return null;
    }

}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 纽约风味的pizza
 */
public class NYStyleCheesePizza extends Pizza {

    public NYStyleCheesePizza(){
        /*
         * 纽约比萨有自己的大蒜番茄酱和薄饼
         */
        name = "NY style Sauce and Cheese Pizza";
        dough = "Thin Crust Dough";
        sauce = "Marinara Sauce";

        /*
         * 上面覆盖的是意大利reggiano高级奶酪
         */
        toppings.add("Grated Reggiano Cheese");
    }
}
public class ChicagoStyleCheesePizza extends Pizza {

    public ChicagoStyleCheesePizza(){
        /*
         * 芝加哥比萨使用小番茄作为酱料,并使用厚饼
         */
        name = "Chicago Style Deep DIsh Cheese Pizza";
        dough = "Extra Thick Crust Dough";
        sauce = "Plum Tomato Sauce";

        /*
         * 芝加哥风味的深盘比萨使用许多mozzarella(意大利白干酪)
         */
        toppings.add("Shredded Mozzarella Cheese");
    }

    /*
     * 这个芝加哥风味比萨覆盖类cut()方法,将比萨切成正方形
     */
    public void cut(){
        System.out.println("Cutting the pizza into square slices");
    }
}
public class PizzaTestDrive {

    public static void main(String[] args) {
        PizzaStore nyStore = new NYPizzaStore();
        PizzaStore chicagoStore = new ChicagoStore();

        Pizza pizza = nyStore.orderPizza("cheese");
        System.out.println("Ethan ordered a "+pizza.getName()+"\n");

        pizza = chicagoStore.orderPizza("cheese");
        System.out.println("Joel ordered a "+pizza.getName()+"\n");
    }
}

所有工厂模式都用来封装对象的创建。工厂方法模式(Factory Method Pattern)通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。

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

  • 依赖倒置原则
    要依赖抽象,不要依赖具体类
    即:不能让高层组件依赖低层组件,而且不管高层或低层组件,“两者”都应该依赖于抽象。
    所谓“高层”组件,是由其他低层组件定义其行为的类。例如,PizzaStore是个高层组件,因为它的行为是由比萨定义的:PizzaStore创建所有不同的比萨对象,准备,烘烤,切片,装盒,而比萨本身属于低层组件。
    看一段代码:
/*
 这个版本的PizzaStore依赖于所有的比萨对象,因为它直接创建这些比萨对象
 如果这些类的实现改变了,那么可能必须修改PizzaStore
 每新增一个比萨种类,就等于让PizzaStore多了一个依赖
 因为对于比萨具体实现的任何改变都会影响到PizzaStore。我们说PizzaStore“依赖于”比萨的实现
*/
public class DependentPizzaStore {

    public Pizza createPizza(String style,String type){
        Pizza pizza = null;
        if(style.equals("NY")){
            if(type.equals("cheese")){
                pizza = new NYStyleCheesePizza();
            }
        }else if (style.equals("Chicago")){
            if(type.equals("cheese")){
                pizza = new ChicagoStyleCheesePizza();
            }
        }else {
            System.out.println("Erro:invalid type of pizza");
            return null;
        }
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

当直接实例化一个对象时,就是在依赖它的具体类。上面的这个比萨店,它由比萨店类创建所有的比萨对象,而不是委托给工厂。
现在,这个原则告诉我们,应该重写代码以便于我们依赖抽象类,而不依赖具体类。对于高层及低层模块都应该如此。
之前运用工厂方法之后,高层组件PizzaStore和低层组件(也就是这些比萨)都依赖了Pizza对象。高层组件并没有依赖低层组件,而且两者都依赖了抽象,低层组件依赖高层的抽象。

  • 几个指导方针帮助你遵循此原则
    *变量不可以持有具体类的引用:如果使用new,就会持有具体类的引用。你可以用工厂来避开这样的做法。
    *不要让类派生自具体类:如果派生自具体类,就会依赖具体类。
    *不要覆盖基类中已实现的方法:如果覆盖基类已实现的方法,那么这个基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。

3 抽象工厂
生产原料,但是每个区域所使用的原料都不相同,比如纽约的红酱料和芝加哥的红酱料是不同的。该如何做呢?一下代码解决了这个问题


/**
 * @author hgl
 * @data 2018年5月6日
 * @description pizza抽象类
 */
public abstract class Pizza {
    public String name;
    public Dough dough;
    public Sauce sauce;
    public Veggies veggies[];
    public Cheese cheese;
    public Pepperoni pepperoni;
    public Clams clam;

    public abstract void prepare();
    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 void setName(String name){
        this.name=name;
    }

    public String getName(){
        return name;
    }
    public String toString(){
        return name;
    }
}
public interface PizzaIngredientFactory {

    public Dough createDough();
    public Sauce createSauce();
    public Cheese CreateCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
    public Clams createClam();
}
public interface Dough {

}
public interface Sauce {

}
public interface Cheese {

}
public interface Clams {

}
public interface Pepperoni {

}
public interface Veggies {

}
public class ThinCrustDough implements Dough {

}
public class SlicedPepproni implements Pepperoni {

}
public class ReggianoCheese implements Cheese {

}
public class FreshClams implements Clams {

}
public class MarinaraSauce implements Sauce {

}
public class Mushroom implements Veggies {

}
public class Onion implements Veggies {

}
public class Garlic implements Veggies {

}
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {

    @Override
    public Dough createDough() {
        // TODO Auto-generated method stub
        return new ThinCrustDough();
    }

    @Override
    public Sauce createSauce() {
        // TODO Auto-generated method stub
        return new MarinaraSauce();
    }

    @Override
    public Cheese CreateCheese() {
        // TODO Auto-generated method stub
        return new ReggianoCheese();
    }

    @Override
    public Veggies[] createVeggies() {
        // TODO Auto-generated method stub
        Veggies[] veggies = {new Garlic(),new Onion(),new Mushroom()};
        return veggies;
    }

    @Override
    public Pepperoni createPepperoni() {
        // TODO Auto-generated method stub
        return new SlicedPepproni();
    }

    @Override
    public Clams createClam() {
        // TODO Auto-generated method stub
        return new FreshClams();
    }

}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 纽约店
 */
public class NYPizzaStore extends PizzaStore{

    @Override
    public Pizza createPizza(String type) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
        if(type.equals("cheese")){//只写了一个例子
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("New York Style Cheese Pizza");
        }
        return pizza;
    }


}

我们引入新类型的工厂,也就是所谓的抽象工厂,来创建比萨原料家族。
通过抽象工厂所提供的接口,可以创建产品的家族,利用这个接口书写代码,我们的代码将从实际工厂解藕,以便在不同上下文中实现各式各样的工厂,制造出各种不同的产品。因为代码从实际的产品中解藕了,所以我们可以替换不同的工厂来取得不同的行为。

抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

猜你喜欢

转载自blog.csdn.net/weixin_37885641/article/details/80214485