工厂方法模式
工厂方法模式
核心思想
工厂方法模式提供一个接口,让这个接口的实现来决定实例化哪个类。
典型用例
依赖注入
当一个类需要创建依赖对象,而又不应该知道具体的类时,可以使用工厂方法模式。例如:在使用日志记录器时,客户端无需知道具体使用哪种日志记录器,而只需与日志工厂接口交互。
这种方式支持依赖倒置原则,使得高级模块(客户端)不会依赖于低级模块(具体的日志记录器),而是依赖于抽象(日志记录器接口)。
// npm run code src/code/design-pattern/factory-method/dependency-injection.ts
export {};
// 定义一个“产品”接口
interface Logger {
log(message: string): void;
}
// 创建具体的产品类
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(`ConsoleLogger: ${message}`);
}
}
class FileLogger implements Logger {
log(message: string): void {
// 假设这里将消息写入文件
console.log(`FileLogger: ${message}`);
}
}
// 定义一个“工厂”接口,用于创建“产品”
interface LoggerFactory {
createLogger(): Logger;
}
// 创建具体的工厂类
class ConsoleLoggerFactory implements LoggerFactory {
createLogger(): Logger {
return new ConsoleLogger();
}
}
class FileLoggerFactory implements LoggerFactory {
createLogger(): Logger {
return new FileLogger();
}
}
function clientCode(factory: LoggerFactory): void {
const logger = factory.createLogger();
logger.log("This is a message.");
}
const consoleFactory = new ConsoleLoggerFactory();
clientCode(consoleFactory); // 使用控制台记录器
const fileFactory = new FileLoggerFactory();
clientCode(fileFactory); // 使用文件记录器
创建不同类型对象
在工厂方法模式中,对象的创建是通过调用一个工厂方法来完成的,而不是直接通过调用构造函数。这个模式特别适用于情况,当你有一组类似的对象,但它们在一些细节上有所不同,或者你需要根据不同的情境来创建不同的对象实例。
在这个例子中,开发跨平台应用时可能需要根据不同的操作系统创建不同的UI元素,具体工厂用以返回特定操作系统的UI元素,客户端代码接收一个工厂对象并使用它来创建UI元素,这样,UI元素的创建就与特定的类实现解耦了,使得添加新的UI元素或支持新的操作系统变得更容易。
// npm run code src/code/design-pattern/factory-method/create-different-type-objects.ts
export {};
// 产品接口
interface UIElement {
render(): void;
}
// 具体产品类
class WindowsButton implements UIElement {
render(): void {
console.log("Rendering a Windows style button");
}
}
class MacOSButton implements UIElement {
render(): void {
console.log("Rendering a MacOS style button");
}
}
class LinuxButton implements UIElement {
render(): void {
console.log("Rendering a Linux style button");
}
}
// 工厂接口
abstract class UIFactory {
abstract createButton(): UIElement;
}
// 具体工厂类
class WindowsFactory extends UIFactory {
createButton(): UIElement {
return new WindowsButton();
}
}
class MacOSFactory extends UIFactory {
createButton(): UIElement {
return new MacOSButton();
}
}
class LinuxFactory extends UIFactory {
createButton(): UIElement {
return new LinuxButton();
}
}
// 使用工厂方法
function clientCode(factory: UIFactory) {
const button = factory.createButton();
button.render();
}
// 根据不同的操作系统创建不同的UI元素
clientCode(new WindowsFactory());
clientCode(new MacOSFactory());
clientCode(new LinuxFactory());
扩展类库或框架
当设计一个框架或库时,可以使用工厂方法来允许用户扩展或修改其标准组件。用户可以创建扩展了工厂方法的子类来生成自定义的对象。
在这个例子中,先创建了一个基本的日志类库,用户可以通过继承并重写工厂方法来创建自定义的日志记录器。
// npm run code src/code/design-pattern/factory-method/extend-framework.ts
export {};
// 日志记录器接口
interface Logger {
log(message: string): void;
}
// 默认日志记录器实现
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(`[ConsoleLogger] ${message}`);
}
}
// 包含工厂方法的类
class LoggerFactory {
// 工厂方法
createLogger(): Logger {
return new ConsoleLogger();
}
}
// 用户自定义日志记录器
class CustomLogger implements Logger {
log(message: string): void {
console.log(`[CustomLogger] ${message}`);
}
}
// 用户扩展的工厂
class CustomLoggerFactory extends LoggerFactory {
createLogger(): Logger {
return new CustomLogger();
}
}
// 客户端代码
function clientCode(factory: LoggerFactory) {
const logger = factory.createLogger();
logger.log("This is a log message.");
}
// 使用默认日志记录器
clientCode(new LoggerFactory());
// 使用自定义日志记录器
clientCode(new CustomLoggerFactory());
替代子类化
工厂方法模式是一种在不产生大量子类的情况下实现对象的灵活创建的设计模式。在某些情况下,如果每种不同的行为或变化都通过子类来实现,可能会导致子类数量急剧增加。工厂方法允许子类通过覆盖一个方法来改变创建对象的类型,这样可以避免产生过多的子类。
假设开发一个游戏应用,其中的角色(如战士、法师、弓箭手)具有不同的武器。如果为每种角色和武器组合创建一个子类,子类的数量会非常多。因此可以创建不同类型的角色,并且每个角色可以使用相应的武器,而不需要为每种角色和武器组合创建单独的子类。这种方法减少了子类的数量,并提高了代码的灵活性和可维护性。
// npm run code src/code/design-pattern/factory-method/alternative-subclassing.ts
export {};
// 武器接口
interface Weapon {
useWeapon(): void;
}
// 具体武器类
class Sword implements Weapon {
useWeapon(): void {
console.log("Swinging a sword");
}
}
class Bow implements Weapon {
useWeapon(): void {
console.log("Shooting an arrow");
}
}
class Staff implements Weapon {
useWeapon(): void {
console.log("Casting a spell");
}
}
// 角色类,包含工厂方法
abstract class Character {
// 工厂方法
abstract createWeapon(): Weapon;
fight(): void {
const weapon = this.createWeapon();
weapon.useWeapon();
}
}
// 具体角色子类
class Warrior extends Character {
createWeapon(): Weapon {
return new Sword();
}
}
class Archer extends Character {
createWeapon(): Weapon {
return new Bow();
}
}
class Mage extends Character {
createWeapon(): Weapon {
return new Staff();
}
}
// 客户端代码
function clientCode(character: Character) {
character.fight();
}
// 创建不同的角色,并使用它们的武器
clientCode(new Warrior());
clientCode(new Archer());
clientCode(new Mage());
解耦代码
工厂方法模式通过定义一个用于创建对象的接口,但让子类决定实例化哪一个类,从而实现解耦。这种模式允许代码在不知道具体类的情况下实例化对象,这样就可以降低类之间的耦合度。
在这个例子中,程序需要根据不同的配置或环境条件创建不同类型的数据库连接。如果直接在代码中实例化特定类型的数据库连接,那么代码就会与特定的数据库类紧密耦合。使用工厂方法模式,就可以避免这种耦合。
// npm run code src/code/design-pattern/factory-method/decoupled-code.ts
export {};
// 数据库连接接口
interface DatabaseConnection {
connect(): void;
}
// 具体数据库连接类
class MySQLConnection implements DatabaseConnection {
connect(): void {
console.log("Connecting to MySQL database");
}
}
class SQLiteConnection implements DatabaseConnection {
connect(): void {
console.log("Connecting to SQLite database");
}
}
class PostgreSQLConnection implements DatabaseConnection {
connect(): void {
console.log("Connecting to PostgreSQL database");
}
}
// 数据库连接工厂
abstract class ConnectionFactory {
// 工厂方法
abstract createConnection(): DatabaseConnection;
}
// 具体工厂子类
class MySQLConnectionFactory extends ConnectionFactory {
createConnection(): DatabaseConnection {
return new MySQLConnection();
}
}
class SQLiteConnectionFactory extends ConnectionFactory {
createConnection(): DatabaseConnection {
return new SQLiteConnection();
}
}
class PostgreSQLConnectionFactory extends ConnectionFactory {
createConnection(): DatabaseConnection {
return new PostgreSQLConnection();
}
}
// 客户端代码
function clientCode(factory: ConnectionFactory) {
const connection = factory.createConnection();
connection.connect();
}
// 根据需要创建不同类型的数据库连接
clientCode(new MySQLConnectionFactory());
clientCode(new SQLiteConnectionFactory());
clientCode(new PostgreSQLConnectionFactory());