设计模式之工厂模式

简单工厂

代码实现

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

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 abstract class PizzaStore {
    
    // 工厂方法 createPizza
	// 创建披萨 -- 子类实现,子类各自决定如何创建披萨
	protected abstract Pizza createPizza(String item);

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

生产 纽约芝士口味 比萨

public class NYPizzaStore extends PizzaStore {

	@Override
    Pizza createPizza(String item) {
		switch (item) {
			case "cheese":
				return new NYStyleCheesePizza();  // 纽约芝士口味 比萨
			case "veggie":
				return new NYStyleVeggiePizza();
			case "clam":
				return new NYStyleClamPizza();
			case "pepperoni":
				return new NYStylePepperoniPizza();
			default:
				return null;
		}
	}
}


PizzaStore nyStore = new NYPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese"); // orderPizza()方法会调用createPizza()方法
System.out.println("ordered a : " + pizza.getName() + "\n");

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

创建者类

产品类

工厂方法类图模板

image.png

对象依赖

要依赖抽象,不要依赖具体类。
这个原则说明了:不能让高层组件依赖低层组件,而且,不管高层或低层组件,两者都应该依赖于抽象。
比如,这个例子里的PizzaStore是高层组件,而披萨实现是低层组件,很清楚的,PizzaStore依赖这些具体披萨类。
现在,这个原则告诉我们,应该重写代码以便于我们依赖抽象类,而不依赖具体类。对于高层及低层模块都应该如此。

错误示范

image.png

正确方式

image.png
image.png

在应用工厂方法后,高层组件(也就是PizzaStore)和低层组件(也就是这些披萨)都依赖了Pizza抽象。

下面的指导方针,能帮你避免在OO设计中违反依赖倒置原则:

  • 变量不可以持有具体类的引用
    如果使用new,就会持有具体类的引用。你可以改用工厂来避开这样的做法。
  • 不要让类派生自具体类
    如果派生自具体类,你就会依赖具体类。请派生自一个抽象(接口或抽象类)。
  • 不要覆盖基类中已实现的方法
    如果覆盖基类已实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。

抽象工厂

定义

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

解决的问题

问题:加盟店坐落在不同的区域,纽约的红酱料和芝加哥的红酱料是不一样的。加盟店之间有相同的产品家族(意式腊肠、酱料、芝士、蔬菜等等),但是制作方式根据区域的不同而有差异。所以对于纽约和芝加哥,你准备了两组不同的原料。

建造原料工厂

public interface PizzaIngredientFactory {
    // 在接口中,每个原料都有一个对应的方法创建该原料
    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
    public Clams createClams();
}

创建纽约原料工厂

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    // 对于原料家族内的每一种原料,我们都提供了纽约的版本
    @Override
    public Dough createDough() {
        return new ThinCrushDough();
    }

    @Override
    public Sauce createSauce() {
        return new MarinaraSauce();
    }

    @Override
    public Cheese createCheese() {
        return new ReggianoCheese();
    }

    @Override
    public Veggies[] createVeggies() {
        Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom(), new RedPepper()};
        return veggies;
    }

    @Override
    public Pepperoni createPepperoni() {
        // 这是切片的意式腊肠,纽约和芝加哥都会用到它
        return new SlicedPepperoni();
    }

    @Override
    public Clams createClams() {
        // 纽约靠海,所以有新鲜的蛤蜊。芝加哥就必须使用冷冻的蛤蜊
        return new FreshClams();
    }
}

重做披萨

工厂已经一切就绪,准备生成高质量原料了。现在我们只需要重做披萨,好让它们只使用工厂生产出来的原料。我们先从抽象的Pizza类开始:

public abstract class Pizza {
    String name;
    // 每个披萨都持有一组在准备时会用到的原料
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clams;

    // 现在把prepare()方法声明成抽象。在这个方法中,我们需要收集披萨所需的原料,而这些原料当然是来自原料工厂了。
    abstract void prepare();

    // 其他的方法保持不动
    void bake(){
        // ...
    }

    void cut(){
        // ...
    }

    void box(){
        // ...
    }
}

现在已经有了一个抽象披萨,可以开始创建纽约和芝加哥风味的披萨了。从今以后,加盟店必须直接从工厂取得原料。
其实我们不需要设计两个不同的类来处理不同风味的披萨,让原料工厂处理这种区域差异就可以了。下面是CheesePizza:

public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;

    // 要制作披萨,需要工厂提供原料。
    // 所以每个披萨类都需要从构造器参数中得到一个工厂,并把这个工厂存储在一个实例变量中。
    public CheesePizza(PizzaIngredientFactory ingredientFactory){
        this.ingredientFactory = ingredientFactory;
    }

    @Override
    void prepare() {
        // prepare()方法一步一步地创建芝士披萨,每当需要原料时,就跟工厂要。
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}

Pizza的代码利用相关的工厂生产原料。所生产的原料依赖所使用的工厂,Pizza类根本不关心这些原料,它只知道如何制作比萨。现在,Pizza和区域原料之间被解耦。

public class NYPizzaStore extends PizzaStore {

    protected Pizza createPizza(String item) {
        Pizza pizza = null;
        // 纽约店会用到纽约披萨原料工厂,由该原料工厂负责生产使用纽约风味披萨所需的原料
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();

        if (item.equals("cheese")) {
            // 把工厂传递给每一个披萨,以便披萨能从工厂中取得原料
            pizza = new CheesePizza(ingredientFactory);
        } else if (item.equals("veggie")) {
            pizza = new VeggiePizza(ingredientFactory);
        } else if (item.equals("clam")) {
            pizza = new ClamPizza(ingredientFactory);
        } else if (item.equals("pepperoni")) {
            pizza = new PepperoniPizza(ingredientFactory);
        }
        return pizza;
    }
}

订单流程测试

  1. 首先我们需要一个纽约比萨店: PizzaStore nyStore = new NYPizzaStore();
  2. 现在已经有一个比萨店了,可以接受订单: Pizza pizza = nyStore.orderPizza("cheese");
  3. orderPizza()方法首先调用createPizza()方法: createPizza(type);
protected Pizza createPizza(String item) {
    if (item.equals("cheese")) {
       pizza = new CheesePizza(ingredientFactory);
       pizza.setName("New York Style Cheese Pizza");
    }
}
  1. 创建一个比萨的实例,然后将它和纽约原料工厂结合在一起 : new CheesePizza(ingredientFactory);

  2. 接下来需要准备比萨。一旦调用了prepare()方法,工厂将被要求准备原料 :

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

抽象工厂总结

抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。让我们看看类图来了解其中的关系:

image.png

问:对于简单工厂和工厂方法之间的差异,我依然感到困惑。他们看起来很类似,差别在于,在工厂方法中,返回比萨的类是子类。能解释一下吗?
答:子类的确看起未很像简单工厂。简单工厂把全部的事情,在一个地方都处理完了,然而工厂方法却是创建一个框架,让子类决定要如何实现。比方说,在工厂方法中,orderPizza()方法提供了一般的框架,以便创建萨,orderPizza()方法依赖工厂方法创建具体类,并制造出实际的比萨。可通过继承PizzaStore类,决定实际制造出的比萨是什么。// 简单工厂的做法,可以将对象的创建封装起来,但是简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品。

问:工厂方法是不是潜伏在抽象工厂里面?
答:你的观察力很敏锐!是的,抽象工厂的方法经常以工厂方法的方式实现,这很有道理,对吧?抽象工厂的任务是定义一个负责创建一组产品的接口。这个接口内的每个方法都负责创建一个具体产品,同时我们利用实现抽象工厂的子类来提供这些具体的做法。所以,在抽象工厂中利用工厂方法实现生产方法是相当自然的做法。

抽象工厂与工厂方法的对比

  • 抽象工厂和工厂方法都是负责创建对象。
  • 抽象工厂是通过对象的组合。 工厂方法是继承。
  • 定义了一个创建对象的接口,但由于子类决定要实例化的类是哪一个,工厂方法让类把实例化推迟到子类。所以利用工厂方法创建对象时,需要扩展一个类,并覆盖它的工厂方法。
  • 整个工厂方法模式,只不过就是通过子类来创建对象,这种做法,客户只需要知道他们所使用的抽象类型就可以了,而由子类负责决定具体类型。将客户从具体类型中解耦。
  • 抽象工厂是将一群相关的产品集合起来,用于创建相关或依赖对象的家族,而不需要明确指定具体类。要想使用这个工厂,必须先实例化它。

image.png

image.png

要点

  • 所有的工厂都是用来封装对象的创建
  • 简单工厂,虽然不是真正的设计模式,但仍不失为一个简单的方法,可以将客户程序从具体类解耦。
  • 工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象。
  • 抽象工厂使用对象组合:对象的创建被实现在工厂接口所暴露出来的方法中。
  • 所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合。
  • 工厂方法允许类将实例化延迟到子类进行。
  • 抽象工厂创建相关的对象家族,而不需要依赖它们的具体类。
  • 依赖倒置原则,指导我们避免依赖具体类型,而要尽量依赖抽象。
  • 工厂是很有威力的技巧,帮助我们针对抽象编程,而不要针对具体类编程。
posted @ 2021-12-22 20:25  追梦少年阿飞  阅读(55)  评论(0)    收藏  举报