[架构] 工厂模式详解与实例

所有文章目录:my.oschina.net/ChenTF/blog…

本篇文章地址: my.oschina.net/ChenTF/blog…

会持续的更新所有历史文章, 所以收藏的话请收藏上面的地址。

    本文将介绍简单工厂、工厂模式、抽象工厂, 所有的工厂模式都是一个目的: “为了处理创建对象的细节”。

    本文内容来源于, 很棒的一本书, 你值得拥有。

1. 简单工厂

1.1 背景

     当我们需要根据条件创建一系列对象时, 一般会将判断写在一个方法里面(如下面代码)。这样做在简单的稳定的业务逻辑下, 也没什么大问题, 一样可以实现功能。但是, 当我们新加了几个类时, 就需要修改业务方法的代码, 违反了”对扩展开发, 对修改关闭”原则。

     我们还有更优雅的方式, 一起来看看吧。

public class PizzaStore {

	Pizza orderPizza(String type) {
		Pizza pizza;

		// 变化的部分(需要抽离出来)
		if (type.equals("cheese")) {
        	pizza = new CheesePizza();
    	} else if (type.equals("greek")) {
        	pizza = new GreekkPizza();
    	} else if (type.equals("pepperoni")) {
        	pizza = new PepperoniPizza();
    	}
		
	
		pizza.perpare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}	
}

1.2 修改后的代码

1.2.1 业务类

public class PizzaStore {
	SimplePizzaFactory factory;

	// 初始化
	public PizzaStore(SimplePizzaFactory factory) {
		this.factory = factory;
	}

	Pizza orderPizza(String type) {
		Pizza pizza;

		// 通过工厂得到产品实例
		pizza = factory.createPizza(type);
	
		pizza.perpare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}	
}

1.2.2 工厂类

public class SimplePizzaFactory {

	// 抽离出来的方法
	Pizza createPizza(String type) {
		Pizza pizza;
		if (type.equals("cheese")) {
        	pizza = new CheesePizza();
    	} else if (type.equals("greek")) {
        	pizza = new GreekkPizza();
    	} else if (type.equals("pepperoni")) {
        	pizza = new PepperoniPizza();
    	}
    	
    	return pizza;
	}
}

1.3 讲解

1.3.1 类图

查看图片

     简单的说, 就是将创建对象的代码都移到了工厂类里面。

    那么这样有毛好处? 还多创建了一个类。

1.3.2 优点

  1. 复用性: 如果有别的地方也用到了这些实例, 这时候是不是就复用了?
  2. 可扩展性: 可以在工厂类中加入功能方法, 比如数据转换…
  3. 结构清晰: 具体的业务类引用是不是变少了? 只需要引入一个工厂类即可;
  4. 健壮性: 以后修改代码时, 直接有对应的类了, 不用怕有的改了, 有的忘记了, 改错了排查问题了。

1.3.3 讨论

     a. 之前提到”对扩展开发, 对修改关闭”。 新的方式下, 当新增/移除某个工厂时, 不也得修改工厂里面的方法吗? 谈何修改关闭?

          我对这个模式下, 该原则的理解是: 当业务改变时, 修改是必不可少的。当有新的业务扩展时, 业务的调用方是不用改变的。而简单工厂类就是为了维护对象的创建而生, 该类的修改是可以接受的。

     b.将返回对象换成静态方法, 岂不更方便?

          这是很常见的技巧, 也成为静态工厂。但缺点是不能通过继承来改变创建方法的行为。继承, 看下一篇你就明白了。

2.工厂方法模式

2.1 背景

     在简单工厂模式中, 我们将一系列对象的创建封装到一个类中, 不过现在业务扩展了, 我们需要加入更多的类。(比如纽约风味的芝士/素食/香肠/花蛤披萨,  芝加哥风味的..等等), 如果还使用”简单工厂”模式的话, 会是怎么样? 一个方法内有12个判断, 12个类的new, 一个类引用了12个类(如下图, 写这个例子时都看蒙了…), 所以我们需要更优雅的方式来处理当前的问题。

/* 简单工厂的实现方式 */
public class SimplePizzaFactory {
	
	Pizza createPizza(String type) {
		Pizza pizza;

		if (type.equals("NYcheese")) {
        	pizza = new NYCheesePizza();
    	} else if (type.equals("YNgreek")) {
        	pizza = new NYGreekkPizza();
    	} else if (type.equals("YNpepperoni")) {
        	pizza = new NYPepperoniPizza();
    	} else if (type.equals("XJPcheese")) {
        	pizza = new XJPPepperoniPizza();
    	} else if (type.equals("XJPgreek")) {
        	pizza = new XJPGreekkPizza();
    	} else if (type.equals("XJPpepperoni")) {
        	pizza = new XJPPepperoniPizza();
    	} else if (type.equals("ZGcheese")) {
        	pizza = new ZGPepperoniPizza();
    	} else if (type.equals("ZGPgreek")) {
        	pizza = new ZGreekkPizza();
    	} else if (type.equals("ZGpepperoni")) {
        	pizza = new ZGepperoniPizza();
    	}

    	return pizza;
	}
}

2.2 修改后的代码

2.2.1 抽象工厂

/* 抽象工厂类 */
public abstract class PizzaStore {

	// 获取一个处理好的披萨
	public Pizza orderPizza(String type) {

		Pizza pizza = createPizza(type);	// 使用时使用是具体对象, 所以是知道当前的类型

		// 将共同的处理方式统一了
		pizza.perpare();
		pizza.bake();
		pizza.cut();
		pizza.box();

		return pizza;
	}

	// 获取一个披萨(变化的部分) "抽象方法"
	propected abstract Pizza createPizza(String type);
}

     在抽象工厂中, 我们定义了子类需要实现的接口(createPizza), 和新增了一个orderPizza, 来处理共同的逻辑。

2.2.2 具体工厂

/* 具体工厂(纽约的工厂) */
public class YNPizzaStore extends PizzaStore {

	public Pizza createPizza(String type) {

		if (type.equals("chees")) {
			return new NYStyleCheesPizza();
		} else if (type.equals("veggle")) {
			return new NYStyleVegglePizza();
		} else if (type.equals("Peperonl")) {
			return new NYStylePepperoniPizza();
		} else if (type.equals("Clam")) {
			return new NYStryleClamPizza();
		}
	}
}
/* 具体工厂(中国的工厂) */
public class ZGPizzaStore extends PizzaStore {

	public Pizza createPizza(String type) {

		if (type.equals("chees")) {
			return new ZGStyleCheesPizza();
		} else if (type.equals("veggle")) {
			return new ZGStyleVegglePizza();
		} else if (type.equals("Peperonl")) {
			return new ZGStylePepperoniPizza();
		} else if (type.equals("Clam")) {
			return new ZGStryleClamPizza();
		}
	}
}

     一个”具体工厂”可以理解为一个”简单工厂”, 将某一类的对象创建放在了一起。

     当我们新增一个工厂时, 只需要继承自”抽象工厂”即可, 不会修改之前的代码。

2.2.3 抽象产品

/* 抽象产品类 */
public abstract class Pizza {
	// 定义属性
	String name;	
	String dough;
	String sauce;
	ArrayList toppings = new ArrayList();

	// 定义行为
	void prepare() {
		System.out.println("Preparng " + 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");
	}
	void cut() {
		System.out.println("cut");
	}
	void box() {
		System.out.println("cut");
	}

}

     抽象产品定义了产品的基本功能。

2.2.4 具体产品

/* 具体产品子类(纽约的芝士披萨) */
public class NYStyleCheesPizza extends Pizza {
	public NYStyleCheesPizza() {
		name = "纽约风味的芝士披萨";	// 设置自己的属性
	}
	// 重载了cut方法
	void cut() {
		System.out.println("切成方块形状");	
	}
}
/* 具体产品子类(中国的芝士披萨) */
public class NYStyleCheesPizza extends Pizza {
	public NYStyleCheesPizza() {
		name = "中国风味的芝士披萨";	// 设置自己的属性
	}
}

     具体产品可以重载父类的方法, 实现个性化。

     当新增一个产品时, 只需要继承自抽象产品类就可。

2.2.5 业务调用

/* 业务方, 使用方式 */
PizzaStore _YNStore = YNPizzaStore();	// 得到纽约工厂对象
Pizza _YNPizza0 = _YNStore.createPizza("chees"); // 得到一份纽约披萨
Pizza _YNPizza1 = _YNStore.orderPizza("chees");	// 得到一份处理过的纽约披萨产品对象

     当在使用时, 根据业务情况实例化对应的工厂, 然后得到对应的工厂类(多态)。

2.3 讲解

2.3.1 类图

查看图片

     可以从下向上理解, YNPizzaStore引用了YN相关的产品(XYStylePizza)。然后业务扩展了, 咱们新开了ZJG, 有对应的ZJG产品。 然后就我们抽离出工厂公有的逻辑(orderPizza)与变化的部分(creatPizza), 用抽象工厂来制定协议, 对应的, 我们也将产品公共的部分抽象出来(Pizza), 就得到了这个类图。

2.3.2 优点

     之前的简单工厂有的优点, 这个模式都有(微观上看, 一个具体工厂可以理解为一个简单工厂)。

    工厂模式都是为了解决对象创建问题, 这个模式为简单工厂增加了扩展性。

2.3.3 讨论

     a. 当只有一个工厂时, 是不是适合简单工厂, 不适合工厂模式?

          不是的。当只有一个工厂时, 用工厂模式比较好, 因为你的系统是会扩张和改变的。而当系统系统改动时, 不需要修改基础框架, 只需要继承自抽象工厂即可。工厂模式将”实现”从”使用”中解耦。

     b.使用字符串传参会容易出错.

          可以使用字符串常量, 枚举等技巧。

     c.简单工厂与工厂模式的区别?

          简单工厂是将全部的事情放在一个地方处理完了。而工厂方法是创建一个框架, 让子类决定如何实现。

          简单工厂不具备工厂方法的弹性。

     d.涉及到哪些设计原则?

          依赖倒置原则: 要依赖抽象, 不要依赖具体类。

          这个原则有两条标准: 1.不能让高层组件依赖底层组件 2.无论是高层组件还是底层组件, “两者”都应该依赖于抽象。工厂模式中, 抽象的工厂和具体的工厂都依赖于抽象的产品, 而抽象的工厂是不知道具体的工厂的任何实现部分。

3. 抽象工厂

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

3.1 背景

     工厂模式将创建单个产品的方法封装起来, 满足了单个产品的创建需求; 而如果需要将一类/多个产品的创建封装起来, 就需要抽象工厂模式。

3.2 类图

查看图片

     可以简单的这样理解: 单个具体工厂类的一个方法, 可以”看做”是一个工厂模式(例如ConcreteFactory1中的CreateProducaA()), 然后具体工厂加入了多个产品, 就变成了抽象工厂。

3.3 增长的需求

     在工厂模式中, 我们将每个区域的工厂进行了区域划分(YNPizzaStore和YNStylePizza 、ZJGPizzaStore和ZJGStylePizza、ZGPizzaStore和ZGStylePizza)。现在, 每个区域的披萨都需要有不同的调料(dough、sauce和cheese), 那么该如何做?

3.3.1 调料家族

     我们将创建调理的方式通过”抽象工厂”封装了起来, 如下:

查看图片

     代码如下: 

/* 抽象产品家族 */
public interface class Dough {
}
public interface class Sauce {
}
public interface class Cheese {
}

/* 具体产品 */
public class ThickCrustDough extends Dough {
}
public class ThinCrustDough extends Dough {
}

public class PlumTomatoSauce extends Sauce {
}
public class MarinaraSauce extends Sauce {
}

public class MozzarellaCheese extends Cheese {
}
public class ReggianoCheese extends Cheese {
}
/* 抽象工厂 */
public interface class PizzaingredientFactory {
 	Dough createDough();
	Sauce createSauce();
	Cheese createCheese();
}

/* 具体抽象产品 */
public class YNPizzaingredientFactory extends PizzaingredientFactory {
	Dough createSauce {
		return new ThinCrustDough();	// 每个工厂有自己对应的原料
	}
	Sauce createSauce {
		return new MarinaraSauce();
	}
	Cheese createCheese {
		return new ReggianoCheese();
	}
}
public class ZGJPizzaingredientFactory extends PizzaingredientFactory {
	Dough createSauce {
		return new ThickCrustDough();
	}
	Sauce createSauce {
		return new PlumTomatoSauce());
	}
	Cheese createCheese {
		return new MozzarellaCheese();
	}
}

3.3.2 总体调整

查看图片

     现在我们将调料家族加入到我们的工程中。虚线的左边是抽象工厂,提供一类原料家族的创建; 右边是工厂模式, 提供单个的产品创建。

     具体的工厂将持有对应的具体的抽象工厂, 成一对一关系(例YNPizzaStore引用YNPizzaingredientFactory)。因为原料属于产品的一部分(Pizza中的dough、sauce、cheese), 所以将具体的原料工厂传给具体的产品, 产品内调原料工厂的方法来获取对应的原料(createDough()、createSauce()、createCheese())。

代码如下:

/* 抽象工厂类 */
public abstract class PizzaStore {

	// 获取一个处理好的披萨
	public Pizza orderPizza(String type) {

		Pizza pizza = createPizza(type);	// 使用时使用是具体对象, 所以是知道当前的类型

		// 将共同的处理方式统一了
		pizza.perpare();
		pizza.bake();
		pizza.cut();
		pizza.box();

		return pizza;
	}

	// 获取一个披萨(变化的部分) "抽象方法"
	propected abstract Pizza createPizza(String type);
}

/* 具体工厂(纽约的工厂) */
public class YNPizzaStore extends PizzaStore {

	public Pizza createPizza(String type) {

		PizzaingredientFactory ingredientFactory = new YNPizzaingredientFactory(); // 得到对应的原料类
		if (type.equals("chees")) {
			return new NYStyleCheesPizza(ingredientFactory); // 将原料工厂传给产品,在产品中获取对应的原料
		} else if (type.equals("veggle")) {
			return new NYStyleVegglePizza(ingredientFactory);
		} else if (type.equals("Peperonl")) {
			return new NYStylePepperoniPizza(ingredientFactory);
		} else if (type.equals("Clam")) {
			return new NYStryleClamPizza(ingredientFactory);
		}
	}
}
/* 具体工厂(中国的工厂) */
public class ZGPizzaStore extends PizzaStore {

	public Pizza createPizza(String type) {

		PizzaingredientFactory ingredientFactory = new ZGJPizzaingredientFactory();
		if (type.equals("chees")) {
			return new ZGStyleCheesPizza(ingredientFactory);
		} else if (type.equals("veggle")) {
			return new ZGStyleVegglePizza(ingredientFactory);
		} else if (type.equals("Peperonl")) {
			return new ZGStylePepperoniPizza(ingredientFactory);
		} else if (type.equals("Clam")) {
			return new ZGStryleClamPizza(ingredientFactory);
		}
	}
}
/* 抽象产品类 */
public abstract class Pizza {
	// 定义属性
	String name;	
	Dough dough;
	Sauce sauce;
	Cheese cheese;
	ArrayList toppings = new ArrayList();

	PizzaingredientFactory ingredientFactory;  // 抽象产品引入原料家族抽象类
	public Pizza create(PizzaingredientFactory ingredientFactory) {
		this.ingredientFactory = ingredientFactory;
	}

	// 定义行为
	void prepare() {
		System.out.println("Preparng " + name);
		dough = ingredientFactory.createDough();  // 根据具体的原料工厂类,获取对应的原料
		sauce = ingredientFactory.createSauce();  // 在运行时, 子类将得到他对应的具体产品
		cheese = ingredientFactory.createCheese();

		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");
	}
	void cut() {
		System.out.println("cut");
	}
	void box() {
		System.out.println("cut");
	}

}

/* 具体产品子类(纽约的芝士披萨) */
public class NYStyleCheesPizza extends Pizza {
	public NYStyleCheesPizza() {
		name = "纽约风味的芝士披萨";	// 设置自己的属性
	}
	// 重载了cut方法
	void cut() {
		System.out.println("切成方块形状");	
	}
}
/* 具体产品子类(中国的芝士披萨) */
public class NYStyleCheesPizza extends Pizza {
	public NYStyleCheesPizza() {
		name = "中国风味的芝士披萨";	// 设置自己的属性
	}
}

业务方的使用方式依然不变

/* 业务方, 使用方式 */
PizzaStore _YNStore = YNPizzaStore();	// 得到纽约工厂对象
Pizza _pizza0 = _YNStore.orderPizza("cheese");

3.4 工厂模式与抽象工厂模式对比

  1. 抽象工厂可以实现多个产品的创建, 而工厂方法只能实现单个产品的创建;
  2. 抽象工厂的每个方法实际上看起来像是工厂方法;
  3. 无论是抽象工厂还是工厂模式, 都遵循依赖倒置原则, 将具体的实现从使用中解耦出来。

4. 工厂模式涉及到的OO原则

  • 多用组合, 少用继承
  • 针对接口编程, 不针对实现编程
  • 为交互对象之间的松耦合涉及而努力
  • 类应该对扩展开放, 对修改关闭
  • 依赖抽象, 不要依赖具体类(依赖倒置原则)
    原文作者:算法小白
    原文地址: https://juejin.im/entry/57aaec0c5bbb5000644b1717
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞