工厂方法(Factory Method)模式

工厂方法模式是一种对象创建型设计模式,它定义一个用于创建对象的接口,让子类决定实例化哪一个类。该模式封装了实例化过程,允许子类在不修改原有代码的情况下扩展。适用场景包括类不知道它所必须创建的对象的类,类希望由其子类来指定它所创建的对象,以及类将创建对象的职责委托给多个帮助子类中的某一个。工厂方法模式有封装变化、提高代码可扩展性和连接平行类层次等优点,但也可能导致客户不得不创建Creator的子类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

工厂方法(Factory Method)模式

隶属类别——对象创建型模式


1. 意图

定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类实例化延迟到其子类

2. 别名

虚构造器(Virtual Constructor)

3. 动机

框架使用抽象类定义和维护对象之间的关系。这些对象的创建通常也由框架负责。

考虑这样一个应用框架,它可以向用户显示多个文档。在这个框架中,两个主要的抽象类Application和Document。这两个类都是抽象的,用户必须通过它们的子类来做与具体应用相关的实现。例如,为创建一个绘图应用,我们定义类DrawingApplication和DrawingDocument。Applicationle类负责应用管理Document并根据需要创建它们——例如,当用户从菜单中选择Open或New的时候。

因为被实例化的特定Document子类是与特定应用相关的,所以Application类不可能预测到哪个Document子类将被实例化——Application类仅知道一个新的文档如何应被创建。而不知道哪一种Document将被创建。这就产生了一个尴尬的局面:框架必须实例化类,但是它只知道无法被实例化的抽象类。

Factory Method模式提供了一个解决方案。它封装了哪个Document子类将被创建的信息将这些信息从该框架中分离出来,如下图所示

在这里插入图片描述

Application的子类重定义Application的抽象操作CreateDocument以返回适当的Document子类对象。一旦一个子类Application对象实例化以后,它就可以实例化与应用相关的文档,而需知道这些文档的类。我们称CreateDocument是一个工厂方法(Factory Method),因为它负责“生产”一个对象。

4. 适用性

在下列情况下可以使用Factory Method模式:

  • 当一个类不知道它所必须创建的对象的类的时候
  • 当一个类希望由它的子类来指定它创建的对象的时候
  • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化(只有子类具体知道,其父类不知道)的时候。

5. 结构

在这里插入图片描述

6. 参与者

  • Product(Document)

    ——定义工厂方法创建的对象的接口(泛指不一定是真正的接口)。

  • ConcreteProduct(MyDocument)

    ——实现Product接口

  • Creator(Application)

    ——声明工厂方法,该方法返回一个Product类型的对象。Creator也可以定义一个工厂方法的缺省实现,它返回一个缺省的ConcreteProduct对象。

    ——可以调用工厂方法以创建一个Product对象

  • ConcreteCreator(MyApplication)

    ——重定义(或者说实现)工厂方法以返回一个ConcreteProduct实例

7. 协作

  • Creator依赖于它的子类来定义工厂方法,所以它返回一个适当的ConcreteProduct的实例

8. 效果

Factory Method有以下的优点和缺点:

优点:

  • 1.工厂方法不再将于特定应用有关的类绑定到你的代码中。代码仅处理Product接口,因此它可以与用户定义的任何ConcreteProduct类一起使用。

  • 2.为子类提供挂钩(hook) 用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活。Factory Method给子类一个挂钩以提供对象的扩展版本。

  • 3.连接平行的类层次 迄今为止,我们所考虑的例子中,工厂方法并不往往只是被Creator调用,客户可以找到一些有用的工厂方法。尤其在平行类层次的情况下。

    当一个类将它的一些职责委托给一个独立的类的时候,就产生了平行类层次。考虑可以被交互操作的图形,也就是说,它们可以用鼠标进行伸展,移动,或者旋转。实现这样一些交互并不总是那么容易,它通常需要存储和更新在给定时刻记录状态信息,这个状态仅仅在操纵时需要。因此它不需要被保存在图形对象中,此外,当用户操作图形,不同的图形有不同的行为。例如,将直线图形拉长可能会产生一个端点被移动的效果,而伸展正文图形则可能会改变行距。

    有了这些限制,最好使用一个独立的Manipulator对象实现交互并保存所需要的任何与特定操作相关的状态,不同的图形将使用不同的Manipulator(机械手)子类来处理特定的交互,得到的Manipulator类层次与Figure层次是平行(至少是部分平行的),如下图所示:

在这里插入图片描述

Figure类提供了一个CreateManipulator工厂方法,它使得客户可以创建一个与Figure相对应的Manipulator。Figure子类重定义实现CreateManipulator以返回一个默认的Manipulator实例。做为一种选择,Figure类可以实现CreateManipulator以返回一个默认的Manipulator实例,而Figure子类可以只是继承这个缺省实现。这样的Figure类不需要相应的Manipulator子类——因此该层次只是部分平行的。

注意工厂方法是怎样定义两个类层次之间的连接的。它将哪些类应一同工作的工作的信息局部化了。

缺点:

  • 工厂方法有一个潜在的缺点在于客户可能仅仅为了创建一个特定的ConcreteProduct对象,就不得不创建Creator的子类。当Creator子类不必需时,客户现在必然要处理类演化的其他方法(比如代码示例中CreatePizza中处理type(String));但是当客户无论如何必需创建Creator的子类时,创建子类也是可行的。

9. 实现

当应用Factory Method模式时要考虑下面一些问题:

  • 1.主要有两种不同的情况

    • 第一种情况是,Creator类是一个抽象类并且不提供它所声明的工厂方法的实现
    • 第二种情况是,Creator是一个具体的类而且为工厂方法提供了一个缺省的实现。也有可能有一个定义了缺省实现的抽象类,但这不太常见。

    第一种情况需要子类来定义实现,因为没有合理的缺省实现。它避免了对不可预见类是否进行实例化的两难局面(可以什么都不干,也可以实例化,有个抉择)。在第二种情况中,具体的Creator主要因为灵活性才使用工厂方法,它所遵循的的准则是,“用一个独立的操作创建对象,这样子类才能重新定义它们的创建方式。”这条准则保证了子类的设计者能够在必要的时候改变父类所实例化的对象的类。

  • 参数化工厂方法 该模式的另一个情况使得工厂方法可以创建多个产品(例如代码示例中的ChicagoPizzaStore.java)。工厂方法采用一个表示要被创建的对象种类的参数.工厂方法创建的所有对象将共享Product接口。在Document的例子中,Application可能支持不同种类的Document。你给createDocument传递一个外部参数来指定将要创建的文档类型。

    图形编辑框架Unidraw[VL90]使用这种方法来重构存储在磁盘上的对象。Unidraw定义了一个Creator类,该类拥有一个以类标识符为参数的工厂方法Create()。类标识符指定要被实例化的类。当Unidraw将一个对象存盘时,它首先写类标识符,然后是它的实例变量。当它从磁盘中重构该对象时,它首先读取的是类标识符。

    一旦类标识符被读取后,这个框架就将该标识符作为参数,调用create(),create()到构造器中查询相应的类并用它实例化对象。最后,Create()调用对象的read()操作,读取磁盘上剩余的信息并初始化该对象的实例变量。

    重定义一个参数化的工厂方法使你可以简单而又选择性的扩展或改变一个Creator生产的产品。你可以为新产品引入新的标识符,或可以将已有的标识符和不同的产品相关联(例如新的Pizza引入新的Pizza名)。

    	public class ChicagoPizzaStore extends PizzaStore{
    	
    	public Pizza createPizza(String type) {
    		if (type.equals("cheese")) {
    			return new ChicagoStyleCheesePizza();
    		} else if (type.equals("pepperoni")) {
    			return new ChicagoStylePepperoniPizza();
    		} else if (type.equals("clam")) {
    			return new ChicagoStyleClamPizza();
    		} else if (type.equals("veggie")) {
    			return new ChicagoStyleVeggiePizza();
    		} else return null;
    	}
    }
    

    注意这个操作所做的最后一件事是调用父类的Create.这是因为ChicagoPizzaStore.createPizza()仅处理了"cheese",“pepperoni”,“clam”,"veggie"这种四种情况,最后相当于缺省操作或默认操作,当然,如果父类中的createPizza()有对应的缺省操作的话,可以调用super.createPizza()把创建职责延迟给父类,JDK8接口可以使用Default来实现接口的缺省的方法实现,接口中也可以写方法了!

  • 3.特定语言的变化和问题 不同的语言有助于产生其他一些有趣的变化和警告(caveat)。

  • 4.使用模板以避免创建子类 正如我们已经提及的,工厂方法另一个潜在的问题时它可能为了创建适当的Product对象而迫使你创建Creator子类。在C++中另一个解决办法是提供Creator的一个模板子类,它使用Product对象类作为模板参数。在Java就相当于使用泛型,可是泛型如何实例化?可不可以不使用构造器传入参数,实例化一个具体的类型 如下:

    public class ConcreteCreator<T extends Product> extends Creator{
    
    	@Override
    	public Product createProduct() {
    		// 如何实例化泛型T?达到 return new T();的效果?	
    	}
    }
    
  • 这个问题我到stackoverflow去提了,找到两个相对而言稍微靠谱一点的回答,结果如下:

    • A1:In java, there is already a generic type for factory methods. It’s Supplier<T>.

      You should probably use Supplier<Product> instead of your Creator.

      Then, you typically use a lambda function or method reference to supply an instance.

      If you want to call setCreator(Supplier<Product>), for example, and you want it to create your MyProduct subclass, then you just call setCreator(MyProduct::new).

      Lambdas allow you to do more complex constructions without subclassing, even when an appropriate constructor doesn’t exist, like setCreator(() -> new MyProduct(CONST_VAL_1, UtilityClass.getCurrentValue2());

    • A2:Simple answer: you can’t that easily in Java.

      Java isn’t C++, and generics are in many ways less powerful than C++ templates.

      The point is: there is no way to “generically” create new objects of some unknown arbitrary class. You might be able to work around that by using reflection, and Class.forName() and newInstance(), but that comes at the usual cost of reflection: it is clumsy and errorprone.

      Sure, you can return a specific subclass type (and instance there), but then you have to write a specific method for each such type.

  • 5.命名约定 使用命名约定是一个好的习惯,它可以清楚地说明你正在使用工厂方法。例如。Macintosh的应用框架MacApp总是声明那些定义为工厂方法的抽象操作为 Class doMakeClass(){},此处Class 是Product类。

10. 代码示例

首先是Product——Pizza.java

public abstract class Pizza {
	String name;
	String dough;
	String sauce;
	ArrayList<String> toppings = new ArrayList<>();
	
	public void bake() {
		System.out.println("Bake for 25 minutes at 350");
	}
	
	public void cut() {
		System.out.println("Cutting the pizze int odiagonal slices");
	}
	
	public void box() {
		System.out.println("Place pizza in official PizzaStore box");
	}
	
	public String getName() {
		return name;
	}
	
	public void prepare() {
		System.out.println("Prepareing " + name);
		System.out.println("Tossing dough...");
		System.out.println("Adding sauce...");
		System.out.println("Adding toppings: ");
		for(String s : toppings)
			System.out.println(" " + s);
	}
	
	@Override
	public String toString() {
		StringBuffer display = new StringBuffer();
		display.append("---- " + name + " ----\n");
		display.append(dough + "\n");
		display.append(sauce + "\n");
		for (String topping : toppings) {
			display.append(topping + "\n");
		}
		return display.toString();
	}
}

然后是Creator——PizzaStore.java

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

接下是ConcreteProduct——也就是八种具体Pizza

NYStyleVeggiePizza.java

public class NYStyleVeggiePizza extends Pizza {

	public NYStyleVeggiePizza() {
		name = "NY Style Veggie Pizza";
		dough = "Thin Crust Dough";
		sauce = "Marinara Sauce";
 
		toppings.add("Grated Reggiano Cheese");
		toppings.add("Garlic");
		toppings.add("Onion");
		toppings.add("Mushrooms");
		toppings.add("Red Pepper");
	}
}

NYStyleClamPizza.java

public class NYStyleClamPizza extends Pizza {

	public NYStyleClamPizza() {
		name = "NY Style Clam Pizza";
		dough = "Thin Crust Dough";
		sauce = "Marinara Sauce";
 
		toppings.add("Grated Reggiano Cheese");
		toppings.add("Fresh Clams from Long Island Sound");
	}
}

NYStylePepperoniPizza.java

public class NYStylePepperoniPizza extends Pizza {

	public NYStylePepperoniPizza() {
		name = "NY Style Pepperoni Pizza";
		dough = "Thin Crust Dough";
		sauce = "Marinara Sauce";
 
		toppings.add("Grated Reggiano Cheese");
		toppings.add("Sliced Pepperoni");
		toppings.add("Garlic");
		toppings.add("Onion");
		toppings.add("Mushrooms");
		toppings.add("Red Pepper");
	}
}

NYStyleVeggiePizza

public class NYStyleVeggiePizza extends Pizza {

	public NYStyleVeggiePizza() {
		name = "NY Style Veggie Pizza";
		dough = "Thin Crust Dough";
		sauce = "Marinara Sauce";
 
		toppings.add("Grated Reggiano Cheese");
		toppings.add("Garlic");
		toppings.add("Onion");
		toppings.add("Mushrooms");
		toppings.add("Red Pepper");
	}
}

ChicagoStyleCheesePizza.java

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");
	}
	
    @Override
	public void cut() {
		System.out.println("Cutting the pizza into quare slices");
	}
}


ChicagoStyleClamPizza.java

public class ChicagoStyleClamPizza extends Pizza {
	public ChicagoStyleClamPizza() {
		name = "Chicago Style Clam Pizza";
		dough = "Extra Thick Crust Dough";
		sauce = "Plum Tomato Sauce";
 
		toppings.add("Shredded Mozzarella Cheese");
		toppings.add("Frozen Clams from Chesapeake Bay");
	}
 
	@Override
	public void cut() {
		System.out.println("Cutting the pizza into square slices");
	}
}

ChicagoStylePepperoniPizza.java

public class ChicagoStylePepperoniPizza extends Pizza {
	
	public ChicagoStylePepperoniPizza() {
		name = "Chicago Style Pepperoni Pizza";
		dough = "Extra Thick Crust Dough";
		sauce = "Plum Tomato Sauce";
 
		toppings.add("Shredded Mozzarella Cheese");
		toppings.add("Black Olives");
		toppings.add("Spinach");
		toppings.add("Eggplant");
		toppings.add("Sliced Pepperoni");
	}
 
	@Override
	public void cut() {
		System.out.println("Cutting the pizza into square slices");
	}
}

ChicagoStyleVeggiePizza.java

public class ChicagoStyleVeggiePizza extends Pizza {
	
	public ChicagoStyleVeggiePizza() {
		name = "Chicago Deep Dish Veggie Pizza";
		dough = "Extra Thick Crust Dough";
		sauce = "Plum Tomato Sauce";
 
		toppings.add("Shredded Mozzarella Cheese");
		toppings.add("Black Olives");
		toppings.add("Spinach");
		toppings.add("Eggplant");
	}
 
	@Override
	public void cut() {
		System.out.println("Cutting the pizza into square slices");
	}
}

以及ConcreteCreator——NYPizzaStore.java & ChicagoPizzaStore.java

NYPizzaStore.java

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

ChicagoPizzaStore.java

public class ChicagoPizzaStore extends PizzaStore{
	
	public Pizza createPizza(String type) {
		if (type.equals("cheese")) {
			return new ChicagoStyleCheesePizza();
		} else if (type.equals("pepperoni")) {
			return new ChicagoStylePepperoniPizza();
		} else if (type.equals("clam")) {
			return new ChicagoStyleClamPizza();
		} else if (type.equals("veggie")) {
			return new ChicagoStyleVeggiePizza();
		} else return null;
	}
}

接着是测试类——PizzaTestDrive.java

public class PizzaTestDrive {
 
	public static void main(String[] args) {
		PizzaStore nyStore = new NYPizzaStore();
		PizzaStore chicagoStore = new ChicagoPizzaStore();
 
		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");

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

以及对应的测试结果:

Prepareing NY Style Sauce and Cheese Pizza
Tossing dough...
Adding sauce...
Adding toppings: 
 Grated Reggiano Cheese
Bake for 25 minutes at 350
Cutting the pizze int odiagonal slices
Place pizza in official PizzaStore box
Ethan ordered a NY Style Sauce and Cheese Pizza

Prepareing Chicago Style Deep Dish Cheese Pizza
Tossing dough...
Adding sauce...
Adding toppings: 
 Shredded Mozzarella Cheese
Bake for 25 minutes at 350
Cutting the pizza into quare slices
Place pizza in official PizzaStore box
Joel ordered a Chicago Style Deep Dish Cheese Pizza

Prepareing NY Style Clam Pizza
Tossing dough...
Adding sauce...
Adding toppings: 
 Grated Reggiano Cheese
 Fresh Clams from Long Island Sound
Bake for 25 minutes at 350
Cutting the pizze int odiagonal slices
Place pizza in official PizzaStore box
Ethan ordered a NY Style Clam Pizza

最后是整个类图的UML:
在这里插入图片描述

11. 已知应用

工厂方法主要用于工具包和框架中。例如MacApp和ET++[WGM88].

另外操作器的例子来自Unidraw真实应用。

Smalltalk-80中Behavior类是实现了parserClass,返回一个标志的Smalltalk Parse类。一个包含嵌入SQL语句的类重定义了该方法(以类方法的形式)并返回SQLParser类。

12. 相关模式

  • Abstract Factory(抽象工厂):经常使用工厂方法来实现
  • Template Methods(模板方法):工厂方法通常在模板方法中被调用。在之前上面的例子中,newDocument()其实就是一个模板方法。
  • Prototypes(原型) 不需要创建Creator的子类。但是,它们通常要求一个针对Product类的Initialize操作。Creator使用Initialize来初始化对象,而Factory Method不需要这样的操作。

13. 设计原则口袋

  • 封装变化
  • 多用组合,少用继承
  • 针对接口编程,不是针对实现编程
  • 为交互对象之间的松耦合设计而努力
  • 类要对扩展开放,对修改关闭
  • 依赖抽象,不要依赖具体类

14. 参考文献

《HeadFirst设计模式》

《设计模式:可复用面向对象软件的基础》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值