java设计模式-------工厂模式

java设计模式--------工厂模式


  • 分类和定义

1)简单工厂模式(Simple Factory):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据自变量的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

2)工厂方法模式(Factory Method):又称为多形性工厂;工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method是一个类的实例化延迟到其子类。 在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。这个核心类则摇身一变,成为了一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。

3)抽象工厂模式(Abstract Factory):又称为工具箱,产生产品族,但不利于产生新的产品; 抽象工厂模式提供一个创建一系列或相互依赖的对象的接口,而无需指定它们具体的类。

  • 实例

简单工厂模式

一个批萨店,出售不同的批萨,有水果批萨、奶酪批萨、蔬菜批萨等等 。每一个批萨的制作过程: 准备(prepare)、烘烤(bake)、切片(cut)、装箱(box)。批萨店需要为客户提供各种各样的批萨,当然,为客户提供批萨前得制作批萨

首先看批萨的种类 定义批萨父类

abstract public class Pizza {
    String name;
    String dough;
    String sauce;
    ArrayList<String> toppings = new ArrayList<String>();

    public String getName() {
        return name;
    }

    public void prepare() {
        System.out.println("Preparing " + name);
    }

    public void bake() {
        System.out.println("Baking " + name);
    }

    public void cut() {
        System.out.println("Cutting " + name);
    }

    public void box() {
        System.out.println("Boxing " + name);
    }

    public String toString() {
        // code to display pizza name and ingredients
        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(toppings.get(i) + "\n");
        }
        return display.toString();
    }
}

批萨的种类有一些,依次继承父类实现批萨,这里用一个例子代表

public class CheesePizza extends Pizza {
    public CheesePizza() {
        name = "Cheese Pizza";
        dough = "Regular Crust";
        sauce = "Marinara Pizza Sauce";
        toppings.add("Fresh Mozzarella");
        toppings.add("Parmesan");
    }
}

定义批萨店 为 (批萨店专职生产批萨,但是它自己不能主动提供批萨,需要操作员来调用)

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

定义一个操作员,即简单工厂类,通过工厂类可以大量的生产批萨

public 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,但是,现实中,可能每一个地方都有批萨店,如北京。上海各有各的批萨店,那么此时就需要创建更多的批萨店了

在上面的简单工厂模式中,批萨店的批萨制作模式是固定的,如准备(prepare)、烘烤(bake)、切片(cut)、装箱(box),所以,制作批萨的代码相当于绑定在批萨店中了,而现实中不同地方的批萨店的做法是不相同的

这时候就需要重新设计批萨店的,将批萨店变成抽象的超类,用其他子类继承

public abstract class PizzaStore {

    abstract Pizza createPizza(String item);

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        System.out.println("--- Making a " + pizza.getName() + " ---");
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

其他地方的批萨店继承超类:每一个批萨店都有自己的制作方法


public class NYPizzaStore extends PizzaStore {

    Pizza createPizza(String item) {
        if (item.equals("cheese")) {
            return new NYStyleCheesePizza();
        } else if (item.equals("veggie")) {
            return new NYStyleVeggiePizza();
        } else if (item.equals("clam")) {
            return new NYStyleClamPizza();
        } else if (item.equals("pepperoni")) {
            return new NYStylePepperoniPizza();
        } else return null;
    }
}
public class ChicagoPizzaStore extends PizzaStore {

    Pizza createPizza(String item) {
            if (item.equals("cheese")) {
                    return new ChicagoStyleCheesePizza();
            } else if (item.equals("veggie")) {
                    return new ChicagoStyleVeggiePizza();
            } else if (item.equals("clam")) {
                    return new ChicagoStyleClamPizza();
            } else if (item.equals("pepperoni")) {
                    return new ChicagoStylePepperoniPizza();
            } else return null;
    }
}

批萨店的关系图如下:

技术分享

再看此时的批萨,因为已经提供了比较多种类的批萨,不同地方的批萨店的同一款批萨做法不一样


import java.util.ArrayList;

public abstract class Pizza {
    String name;
    String dough;
    String sauce;
    ArrayList<String> toppings = new ArrayList<String>();

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

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

看同一款奶酪批萨的不同做法


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

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

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

工厂模式如何实现调用不同批萨过程:

public class PizzaTestDrive {

    public static void main(String[] args) {
        PizzaStore nyStore = new NYPizzaStore(); //首先,定义一个批萨店


    Pizza pizza = nyStore.orderPizza("cheese");//然后,在调用店中生产的批萨,得到一个奶酪批萨
        System.out.println("Ethan ordered a " + pizza.getName() + "\n");

        PizzaStore chicagoStore = new ChicagoPizzaStore(); //再接着定义一个其他地方的批萨店
        pizza = chicagoStore.orderPizza("cheese"); //得到该店里的奶酪批萨
        System.out.println("Joel ordered a " + pizza.getName() + "\n");

    }
}

工厂模式中很明显的将工厂(不同的批萨店)和产品(不同的批萨)分开了

如下图:

工厂类 
技术分享

产品类 
技术分享

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

- 简单工厂模式和工厂模式区别

简单工厂把全部的事情,在一个地方都处理完了,然而工厂方法却是创建一个框架,让子类决定要如何实现,比方说,在工厂方法中,oreserPizza()方法提供了一般的框架,以便创建批萨,orderPizza()方法依赖于工厂方法创建具体类,并制造出实际的批萨。可通过继承PizzaStore类,决定实际制造出的批萨是什么。简单工厂的做法,可以将对象的创建封装起来。但是简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品。

这正体现了 工厂模式的精髓:要依赖抽象,而不是具体类(依赖倒置原则

几个指导方针,防止在OO设计时违反依赖倒置原则

  • 1 -变量不可以持有具体类的引用
  • 2-不要让类派生自具体类
  • 3-不要覆盖基类中以实现的方法

但是规则是死的,并不是要一定完全遵循,可以尽量

抽象工厂模式

接着上面的例子,我们已经实现了能够制作出不同地方的批萨。虽然各地批萨是采用当地的制作方法,但是原材料的采用没有一定得标准,就是说,这样可能会导致不同的加盟店加入了劣质的原材料,从而影响口碑。

现在就是要采用统一的原材料供应,实现统一的原材料供应系统。

注意,北京的批萨店采用的蘑菇跟上海店采用的蘑菇可能不同。这样每一个地方的原材料品种都有可能不同。

北京 : 小蘑菇,黄奶酪,小洋葱
上海 :  大蘑菇,白奶酪,大洋葱
深圳:  金针菇, 甜奶酪,短洋葱

首先,我们定义一个批萨原材料接口

public interface PizzaIngredientFactory {

    public Dough createDough();  //蘑菇 
    public Sauce createSauce();  //洋葱
    public Cheese createCheese(); //奶酪
}

然后定义北京的原材料厂,实现接口:



public class BJPizzaIngredientFactory implements PizzaIngredientFactory {

    public Dough createDough() {
        return new ThinCrustDough();  //小蘑菇
    }

    public Sauce createSauce() {
        return new MarinaraSauce();   //小洋葱
    }

    public Cheese createCheese() { 
        return new ReggianoCheese();   // 黄奶酪
    } 

}

原材料一个例子

public interface Dough {
    public String toString();
}

public class ThinCrustDough implements Dough {
    public String toString() {
        return "Thin Crust Dough";
    }
}

同理可以实现其他地方的原材料厂,这里代码就不一一例举了

有了新的原材料就可以重新做正宗原材料的批萨了


public abstract class Pizza {
    String name;

    Dough dough;  //三种原材料 
    Sauce sauce;
    Cheese cheese;

    abstract void prepare(); // 做批萨前的准备  原材料  这个地方和前面不一样是因为原材料已经一样了,需要重新实现

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

    void setName(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    public String toString() {
        StringBuffer result = new StringBuffer();
        result.append("---- " + name + " ----\n");
        if (dough != null) {
            result.append(dough);
            result.append("\n");
        }
        if (sauce != null) {
            result.append(sauce);
            result.append("\n");
        }
        if (cheese != null) {
            result.append(cheese);
            result.append("\n");
        }
        return result.toString();
    }
}

批萨超类已经做好了,那么就可以实现奶酪批萨了

public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;

    public CheesePizza(PizzaIngredientFactory ingredientFactory) {
        this.ingredientFactory = ingredientFactory;
    }

    void prepare() {
        System.out.println("Preparing " + name);
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}

奶酪批萨做好之后,就可以制作具有北京黄奶酪原材料的北京奶酪批萨了,但是在这之前,我们还需要一个北京批萨店,由于这里采用了全新的北京原材料(所以和上面已经不同了),北京批萨店如下:

public class BJPizzaStore extends PizzaStore {

    protected Pizza createPizza(String item) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = 
            new BJPizzaIngredientFactory();

        if (item.equals("cheese")) {

            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("BeiJIng Style Cheese Pizza");

        } 
        return pizza;
    }
}

有了这些之后就可以下单叫北京奶酪批萨了



public class PizzaTestDrive {

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

        Pizza pizza = nyStore.orderPizza("cheese"); //先调用父类 pizzastroe  order方法
        System.out.println("Ethan ordered a " + pizza + "\n");

    }
}

类图如下所示:

技术分享

可能很多人觉得工厂模式和抽象工厂模式很相似,这其实是有依据的。如上面,抽象工厂模式中有很多工厂模式的方法,如createDough()、createSauce()等等都是声明为抽象,然后用子类来实现。这个地方我们定义了一组对象产品,如 蘑菇、奶酪、洋葱等等都是一个个抽象的对象,都放在一起封装成一个 原材料 工厂 。
洋葱和奶酪应该是两个不相关的东西,但是通过抽象工厂模式将其组合到一起。
工厂模式中的pizzastroe提供一个接口,创建一个产品
抽象工厂模式中的 原材料 工厂 提供一个接口,创建 一个产品家族

三种模式的优缺点介绍

-简单工厂模式

  • 简单工厂模式的优点如下:
    (1)工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。
    (2)客户端无需知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。
    (3)通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
  • 简单工厂模式的缺点如下:
    (1)由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
    (2)使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。
    (3)系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
    (3)简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

- 工厂模式

  • 工厂方法模式的优点如下:
    (1)在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无需关心创建细节,甚至无需知道具体产品类的类名。
    (2)基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,正是因为所有的具体工厂类都具有同一抽象父类。
    (3)使用工厂方法模式的另一个优点是在系统中加入新产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户端,也无需修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
  • 工厂方法模式的缺点如下:
    (1)在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
    (2)由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

- 抽象工厂模式

  • 优点:
    (1) 隔离了具体类的生成,使得用户不需要知道什么被创建了。
    (2) 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
  • 缺点:
    (1)添加新的产品对像时,难以扩展抽象工厂以便生产新种类的产品。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。