理解设计模式:工厂方法
工厂方法:基本思想
工厂方法模式:何时使用
工厂方法模式:优点和缺点
工厂方法模式示例
示例 1:工厂方法模式的基本结构
示例 2 - 餐厅的 POS(简单工厂)
示例 3 - 使用工厂方法的餐厅 POS
结论
原书中描述了23种经典的设计模式Design Patterns: Elements of Reusable Object-Oriented Software。这些模式为软件开发中经常重复出现的特定问题提供了解决方案。
在本文中,我将描述工厂方法模式的工作原理以及何时应用它。
工厂方法:基本思想
工厂方法模式是一种创建型模式,它使用工厂方法来处理创建对象的问题,而无需指定要创建对象的具体类。创建对象是通过调用工厂方法来创建的——工厂方法要么在接口中指定并由子类实现,要么在基类中实现并由派生类选择性地重写——而不是调用构造函数——维基百科
定义一个用于创建对象的接口,但让子类决定实例化哪个类。工厂方法允许将类的实例化推迟到子类——设计模式:可复用面向对象软件的元素
很多时候,我们需要创建不同类型的对象,而这些对象并非从可能的对象列表中预先知道的。自然的倾向是创建一个factoryManager类,使我们能够根据参数获取不同类型的对象。然而,这种解决方案有两个严重的缺点,我们将在本文中逐一描述:
-
它违反了开放封闭原则,导致代码不干净;并且当软件扩展时不易维护。
-
该类
factoryManager附加到您想要构建的所有类型的对象,从而创建称为的代码spaghetti code。
下面的代码展示了一个经典问题,其中有一个create方法根据作为参数传递的参数返回一个类型的对象:
function create(type) {
switch(type){
case '0': return new Object1();
case '1': return new Object2();
case '2': return new Object3();
default: return new Object4();
}
}
工厂方法模式避免了上述问题,使得代码更加清晰。该模式的UML图如下:
组成该模式的类别如下:
-
产品它是所有可以创建的对象的通用接口。
-
ConcreteProductOne和ConcreteProductTwo是该
Product接口的实现。 -
Creator是一个抽象类,其中
factoryMethod声明了 的方法,该方法将负责生成一个 类型的对象Product。该对象的具体实现并不由此类执行,而是委托给 和ConcreteCreator1类ConcreteCreator2。 -
ConcreteCreator1和ConcreteCreator2覆盖了
factoryMethod具体对象的创建。
需要澄清的是,由于该模式的名称,经常会存在几个被误解的点:
-
此模式不实现
factory负责创建特定对象的方法,而是将责任委托给实现抽象类的子类。 -
此模式是模板方法模式的一个特例,它将算法中变量的责任委托给具体类。在工厂方法模式中,创建对象的责任被委托给实现接口的类。
- 该
factoryMethod方法不必每次都创建新的实例,而是可以从内存缓存、本地存储等中返回这些对象,重要的是该方法必须返回一个实现该Product接口的对象。
- 该
工厂方法模式:何时使用
-
工厂方法模式所解决的问题很容易理解:客户端必须操作的对象并非先验已知的,而是直接依赖于其他用户(最终用户或系统)与系统的交互。需要使用此模式的典型示例是用户从选项列表中选择对象类型。
-
如果需要扩展内部组件(创建的对象数量),则无需附加代码,而是必须实现一个接口,并且只能通过创建相对于要包含的新对象及其特定创建者的类来扩展它。
工厂方法模式:优点和缺点
工厂方法模式具有许多优点,可以概括为以下几点:
-
由于客户端类与其依赖项之间的耦合度较低,因此代码更易于维护。
-
由于可以引入新的具体类而不必破坏现有代码,因此可以保证开放封闭原则,从而实现干净的代码。
Product -
由于遵循单一责任原则 (SRP),代码更加简洁,因为创建具体对象的责任
Product被转移到具体对象创建者类,而不是由客户端类承担这一责任。
然而,工厂方法模式的主要缺点是增加了代码的复杂性以及所需的类数量。这是应用设计模式时众所周知的缺点——为了获得代码的抽象而必须付出的代价。
工厂方法模式示例
接下来我们来说明两个工厂方法模式的应用示例:
-
工厂方法模式的基本结构。在此示例中,我们将理论 UML 图转换为 TypeScript 代码,以便识别该模式中涉及的每个类。
-
一家快餐店的服务点 (POS) 系统如果错误地应用了工厂方法模式,就会形成一种名为“简单工厂”的软件模式(并非设计使然),这种模式不遵循开闭原则。然而,当不需要过多的抽象时,这种编程技术非常有用。不过,当项目规模扩大时,代价可能不菲。
-
应用工厂方法模式解决先前的问题。
以下示例将展示如何使用 TypeScript 实现此模式。我们选择使用 TypeScript 而不是 JavaScript 来实现此模式——后者缺乏接口或抽象类,因此实现接口和抽象类的责任都落在了开发人员身上。
示例 1:工厂方法模式的基本结构
在第一个示例中,我们将把理论 UML 图转换为 TypeScript,以测试此模式的潜力。这是要实现的图:
首先,我们要定义Product问题的接口()。由于它是一个接口,因此所有具体产品(ConcreteProduct1和ConcreteProduct2)中必须实现的方法都已定义。因此,Product我们问题中的接口非常简单,如下所示:
export interface Product {
operation(): string;
}
我们想要在问题中构建的对象必须实现先前定义的接口。因此,ConcreteProduct1需要ConcreteProduct2创建满足Product接口并实现该operation方法的具体类。
import { Product } from "./product.interface";
export class ConcreteProduct1 implements Product {
public operation(): string {
return "ConcreteProduct1: Operation";
}
}
import { Product } from "./product.interface";
export class ConcreteProduct2 implements Product {
public operation(): string {
return "ConcreteProduct2: Operation";
}
}
下一步是定义Creator抽象类,其中factoryMethod必须定义一个抽象类,它将委托给具体类来创建具体对象的实例。真正重要的是,它必须返回该类的对象Product。
另一方面,操作方法已定义,并使用了factoryMethod抽象方法。factoryMethod实际执行的方法将是定义它的具体类中的方法。
import { Product } from "./product.interface";
export abstract class Creator {
protected abstract factoryMethod(): Product;
public operation(): string {
const product = this.factoryMethod();
return `Creator: ${product.operation()}`;
}
}
负责创建具体对象的类称为ConcreteCreator。每个类都ConcreteCreator实现了 的方法,该方法根据所使用的类来创建或类factoryMethod的新对象。ConcreteProduct1ConcreteProduct2creator
import { ConcreteProduct1 } from "./concrete-product1";
import { Creator } from "./creator";
import { Product } from "./product.interface";
export class ConcreteCreator1 extends Creator {
protected factoryMethod(): Product {
return new ConcreteProduct1();
}
}
import { ConcreteProduct2 } from "./concrete-product2";
import { Creator } from "./creator";
import { Product } from "./product.interface";
export class ConcreteCreator2 extends Creator {
protected factoryMethod(): Product {
return new ConcreteProduct2();
}
}
最后,我们将看到类如何Client选择Context在没有先验知识的情况下创建哪些对象,以及这种模式如何保持开放封闭原则(OCP)。
import { ConcreteCreator1 } from "./concrete-creator1";
import { ConcreteCreator2 } from "./concrete-creator2";
import { Creator } from "./creator";
function client(creator: Creator) {
console.log(`Client: I'm not aware of the creator's class`);
console.log(creator.operation());
}
const concreteCreator1 = new ConcreteCreator1();
const concreteCreator2 = new ConcreteCreator2();
client(concreteCreator1);
console.log("----------");
client(concreteCreator2);
示例 2 - 餐厅的 POS(简单工厂)
本例将开发一个不满足工厂方法模式的解决方案,而是使用一个FactoryManager类来负责构建任意对象。该解决方案不仅违背了开放封闭原则,而且在对象创建过程中还包含冗长的代码。有趣的是,这个示例使用工厂方法模式重构为以下示例。
这里提出的解决方案不是一种设计模式,但它是业界广泛使用的解决方案。事实上,它被称为简单工厂,并且随着应用程序的扩展而出现严重的问题。
要构建的应用程序是一个简单的应用程序,它允许您创建不同类型的对象:Pizza,Burger或Kebab。
这些对象的创建并非先验已知,而是依赖于用户交互。ProductManager类负责通过createProduct方法构建某个类的对象。
下面是第一个方案的 UML 图。我们先验地观察到了该解决方案的两个问题:
-
ProductManager类与系统的耦合度高。 -
当你想扩展到其他类型的产品时,用 构建的类
createProduct的方法中的意大利面条式代码会破坏开放封闭原则。ProductManagerswitch-case
与其他示例一样,我们将逐步展示此解决方案的实现代码。Product接口与工厂方法模式提出的解决方案中使用的接口完全相同。
export interface Product {
operation(): string;
}
下一步包括实现您想要在此问题中创建的每个特定对象:Burger和。KebabPizza
import { Product } from "./product.interface";
export class Burger implements Product {
public operation(): string {
return "Burger: Results";
}
}
import { Product } from "./product.interface";
export class Kebab implements Product {
public operation(): string {
return 'Kebab: Operation';
}
}
import { Product } from "./product.interface";
export class Pizza implements Product {
public operation(): string {
return 'Pizza: Operation';
}
}
最后,我们实现该类ProductManager,该类负责根据类型参数创建每个对象类型。我们使用枚举类型,这样我们就可以在语句中使用字符串了switch-case。
import { Burger } from "./burger.model";
import { Kebab } from "./kebab.model";
import { PRODUCT_TYPE } from "./product-type.enum";
import { Pizza } from "./pizza.model";
export class ProductManager {
constructor() {}
createProduct(type): Product {
switch (type) {
case PRODUCT_TYPE.PIZZA:
return new Pizza();
case PRODUCT_TYPE.KEBAB:
return new Kebab();
case PRODUCT_TYPE.BURGER:
return new Burger();
default:
throw new Error("Error: Product invalid!");
}
}
}
最后,有必要展示使用该类的类Client。显然,从该类中我们无法观察到该类下存在违反整洁代码原则的强耦合代码。ContextproductManagerClient
import { PRODUCT_TYPE } from "./product-type.enum";
import { ProductManager } from "./product-manager";
const productManager = new ProductManager();
const burger = productManager.createProduct(PRODUCT_TYPE.BURGER);
const pizza = productManager.createProduct(PRODUCT_TYPE.PIZZA);
const kebab = productManager.createProduct(PRODUCT_TYPE.KEBAB);
console.log(burger.operation());
console.log(pizza.operation());
console.log(kebab.operation());
示例 3 - 使用工厂方法的餐厅 POS
在这个例子中,我们将处理示例 2(餐厅的 POS 系统)中提出的问题,并提出使用工厂方法模式的解决方案。此解决方案的目标是避免类中生成的意大利面条式代码productManager,并遵循开放封闭原则。
因此,按照我们在前面的例子中提出的相同方法,我们将首先查看 UML 图,这将有助于我们识别该模式的每个部分。
在这种情况下,我们要构建的对象将是与Pizza、Burger和Kebab类对应的对象。这些类实现了Product接口。这部分代码与上一个示例中的代码完全相同。不过,我们先回顾一下代码,记住这一点:
export interface Product {
operation(): string;
}
import { Product } from "./product.interface";
export class Burger implements Product {
public operation(): string {
return "Burger: Results";
}
}
import { Product } from "./product.interface";
export class Kebab implements Product {
public operation(): string {
return 'Kebab: Operation';
}
}
import { Product } from "./product.interface";
export class Pizza implements Product {
public operation(): string {
return 'Pizza: Operation';
}
}
在 UML 图的另一侧,我们可以看到creator类。首先,我们来回顾一下Creator负责定义factoryMethod方法的类,该方法必须返回一个实现Product接口的对象。此外,我们还会用到someOperation在factoryMethod每个具体创建者类中开发的抽象方法。
import { Product } from "./product.interface";
export abstract class Creator {
public abstract factoryMethod(): Product;
public someOperation(): string {
const product = this.factoryMethod();
return `Creator: The same creator's code has just worked with ${product.operation()}`;
}
}
我们仍然必须定义每个特定的BurgerCreator、KebabCreator以及PizzaCreator将创建每个特定对象的创建者类(注意:请记住,没有必要总是创建一个对象,如果我们有一个数据结构,可以从中检索缓存的实例,则该模式也会被实现)。
import { Creator } from "./creator";
import { Kebab } from "./kebab.model";
import { Product } from "./product.interface";
export class KebabCreator extends Creator {
public factoryMethod(): Product {
return new Kebab();
}
}
import { Creator } from "./creator";
import { Pizza } from "./pizza.model";
import { Product } from "./product.interface";
export class PizzaCreator extends Creator {
public factoryMethod(): Product {
return new Pizza();
}
}
import { Burger } from "./burger.model";
import { Creator } from "./creator";
import { Product } from "./product.interface";
export class BurgerCreator extends Creator {
public factoryMethod(): Product {
return new Burger();
}
}
完成示例的最后一步是应用我们在Client或Context类中开发的模式。需要注意的是,该Client函数不需要任何关于Creator或 要创建的对象类型的知识。这允许将责任完全委托给特定的类。
import { BurgerCreator } from "./burger-creator";
import { Creator } from "./creator";
import { KebabCreator } from "./kebab-creator";
import { PizzaCreator } from "./pizza-creator";
function client(creator: Creator) {
console.log('Client: I\'m not aware of the creator\'s class, but it still works.');
console.log(creator.someOperation());
}
const pizzaCreator = new PizzaCreator();
const burgerCreator = new BurgerCreator();
const kebabCreator = new KebabCreator();
console.log('App: Launched with the PizzaCreator');
client(pizzaCreator);
console.log('----------');
console.log('App: Launched with the BurgerCreator');
client(burgerCreator);
最后,我创建了三个npm scripts可以执行本文中介绍的代码的程序:
npm run example1
npm run example2
npm run example3
GitHub仓库:https://github.com/Caballerog/blog/tree/master/factory-method-pattern
结论
工厂方法是一种遵循开放-封闭原则的设计模式,它使用多态性将创建对象的责任委托给特定的类。这使得我们的代码更加简洁,扩展性更强。它主要解决了当需要创建不同类型的对象时出现的问题,这些对象依赖于客户端与系统的交互,并且无法预先知道客户端将创建哪个对象。
最后,关于这个模式最重要的不是它的具体实现,而是能够识别这个模式可以解决的问题,以及何时可以应用它。具体实现是最重要的,因为它会根据所使用的编程语言而变化。
鏂囩珷鏉ユ簮锛�https://dev.to/carlillo/understanding-design-patterns-factory-method-52fc



