学习目的
- 提高代码可重用性:设计模式提供了一套经过验证的解决方案,能够帮助开发者在面对常见问题时,重复利用这些模式,减少重复造轮子的情况,提高代码的复用率。
- 增强代码可维护性:遵循设计模式编写的代码结构清晰,逻辑分明,便于他人阅读和维护。当系统需求发生变化时,更容易进行调整和扩展,降低维护成本。
- 促进团队协作:设计模式提供了一种共通的语言,团队成员可以基于模式的名称快速理解彼此的设计思路和代码结构,促进了沟通效率,减少了误解。
- 提升设计能力:通过学习和应用设计模式,开发者能够逐步培养出良好的设计思维,学会如何从宏观角度考虑软件架构,如何合理划分模块,如何优化类与对象之间的关系。
- 保证软件质量:设计模式强调模块化、松耦合和高内聚,遵循这些原则可以减少错误的发生,提高软件的稳定性和可靠性。
- 应对复杂性:随着软件规模的增长,复杂性会急剧上升。设计模式提供了管理这种复杂性的手段,使得大型项目也能保持良好的组织结构,易于管理和扩展。
- 促进技术创新:虽然设计模式是基于已有的经验总结,但在实际应用中,开发者可以根据具体需求对其进行变体或创新,推动技术进步。
定义
设计模式是针对某一类问题的通用解决方案,它描述了在设计过程中针对某种常见问题的一系列标准做法。设计模式不仅包括具体的代码实现,更重要的是描述了对象和类之间的关系、责任分配以及它们之间的交互方式。每个模式都旨在平衡各种设计因素,如对象之间的耦合度、系统的灵活性、可重用性等。
分类
设计模式通常被分为三大类,每一类解决软件设计的不同方面的问题:
创建型模式(Creational Patterns)
这类模式关注对象的创建过程,旨在将对象的创建与使用分离,使得系统更加灵活,易于拓展。主要模式包括:
-
- 单例模式(Singleton)
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 原型模式(Prototype)
结构型模式(Structural Patterns)
结构型模式关注类和对象的组合,通过组合获得更高效、灵活的结构,同时简化系统的设计,使其易于理解。主要模式包括:
-
- 适配器模式(Adapter)
- 桥接模式(Bridge)
- 组合模式(Composite)
- 装饰器模式(Decorator)
- 外观模式(Facade)
- 享元模式(Flyweight)
- 代理模式(Proxy)
行为型模式(Behavioral Patterns)
行为型模式关注对象之间的通信和职责分配,描述了对象之间如何协作共同完成任务,以及如何分配职责。主要模式包括:
-
- 责任链模式(Chain of Responsibility)
- 命令模式(Command)
- 解释器模式(Interpreter)
- 迭代器模式(Iterator)
- 中介者模式(Mediator)
- 备忘录模式(Memento)
- 观察者模式(Observer)
- 状态模式(State)
- 策略模式(Strategy)
- 模板方法模式(Template Method)
- 访问者模式(Visitor)
创建型模式
创建型模式(Creational Patterns)是一组设计模式,它们关注于对象的创建过程,旨在提供创建对象的最佳实践,使系统的设计更加清晰、灵活,并降低对象创建过程中的耦合度。创建型模式主要解决“如何创建对象”、“如何利用对象创建过程达到低耦合、高内聚”等问题。以下是创建型模式的主要类别及其简述:
单例模式(Singleton)
定义: 确保一个类只有一个实例,并提供一个全局访问点。适用于需要控制资源访问、共享配置或状态的情景。
PHP示例
class Singleton
{
private static $instance; // 存储单一实例的静态变量
// 构造函数设为私有,防止外部直接创建对象
private function __construct()
{
// 初始化逻辑,如果有的话
}
// 私有克隆方法,防止外部克隆对象
private function __clone()
{
}
// 获取单例的公共静态方法
public static function getInstance()
{
if (null === self::$instance) {
self::$instance = new Singleton();
}
return self::$instance;
}
// 公共方法,用于类的业务逻辑
public function someBusinessLogic()
{
echo "执行单例类的一些业务逻辑...\n";
}
}
// 使用单例
$singleton1 = Singleton::getInstance();
$singleton1->someBusinessLogic();
$singleton2 = Singleton::getInstance();
$singleton2->someBusinessLogic();
// 检查两个变量是否指向同一实例
var_dump($singleton1 === $singleton2); // 输出: bool(true)
应用场景
- 数据库连接池:在Web应用中,通常只需要一个数据库连接来服务所有的数据库操作请求,以减少频繁建立和销毁数据库连接的开销。
- 日志记录系统:在整个应用程序中,通常只需要一个日志记录器来管理日志的输出,避免日志文件被多个实例同时写入导致混乱。
- 缓存管理:一个应用程序可能只需要一个缓存实例来存储共享数据,以减少内存占用和管理复杂度。
- 配置管理器:应用程序的配置信息通常只需要加载一次,之后可以全局访问,使用单例模式可以确保配置信息的统一管理。
- 线程池管理:在支持多线程的环境中,线程池管理器通常设计为单例,以有效控制线程的创建和销毁,避免资源浪费。
单例模式的使用应当谨慎,因为过度使用可能导致代码结构复杂,不易测试和扩展。确保在确实需要控制资源访问、保持状态一致性或减少系统开销的场景下使用。
JS示例
// 使用立即执行函数表达式(IIFE)实现单例模式
const Singleton = (function () {
let instance = null;
function init() {
// 私有属性和方法
const privateVariable = 'I am a private variable.';
function privateMethod() {
console.log(privateVariable);
}
// 返回包含公有属性和方法的对象
return {
publicMethod: function () {
console.log('This is a public method.');
privateMethod();
}
};
}
return {
getInstance: function () {
if (!instance) {
instance = init();
}
return instance;
}
};
})();
// 使用单例
const singletonInstance1 = Singleton.getInstance();
const singletonInstance2 = Singleton.getInstance();
singletonInstance1.publicMethod(); // 输出: This is a public method. I am a private variable.
console.log(singletonInstance1 === singletonInstance2); // 输出: true,证明是同一个实例
应用场景
- 配置管理器:在前端应用中,可能需要一个全局的配置管理器来存储API地址、版本号等配置信息,单例模式可以确保这些配置在整个应用中只有一份实例,易于管理和维护。
- 事件管理器:在复杂的前端应用中,事件管理器负责注册、触发和移除事件,使用单例模式可以确保事件流的统一管理,避免事件冲突和混乱。
- 弹窗管理器:对于需要控制弹窗显示逻辑的应用,使用单例模式可以集中管理弹窗的堆叠顺序、显示状态,确保用户体验的一致性。
- 登录状态管理:在需要用户身份验证的应用中,可以使用单例模式来管理用户的登录状态,确保整个应用中对用户登录状态的判断是统一且准确的。
- 工具类库:例如日期处理、字符串处理等工具类,使用单例模式可以避免多次实例化带来的性能损耗,同时保持功能的全局可用性。
单例模式在JavaScript中的应用广泛,特别是在需要全局访问、资源有限或需要控制状态一致性的场景中。但需要注意的是,过度使用单例可能会增加代码的耦合度,影响代码的可测试性和可维护性,因此在设计时应权衡利弊。
简单工厂模式(Simple Factory)
定义:它定义了一个用于创建对象的接口,但允许子类决定实例化哪一个类。
关键要素
- Factory(工厂类):负责实现创建所有产品实例的逻辑。客户端通过调用工厂类的一个静态方法(通常是无参或带参的)来请求创建产品对象,而无需知道具体产品的类名。
- Product(产品接口/抽象类):定义了产品的接口或抽象类,具体产品类需要实现此接口或继承此抽象类。
- Concrete Products(具体产品类):实现了产品接口或抽象类的具体类,每个具体产品类对应一种产品实例。
JS示例
// 产品基类
class Product {
constructor(type) {
this.type = type;
}
showType() {
console.log(`Product Type: ${this.type}`);
}
}
// 具体产品类
class ConcreteProduct1 extends Product {
constructor() {
super('Product 1');
}
}
class ConcreteProduct2 extends Product {
constructor() {
super('Product 2');
}
}
// 简单工厂模块
const SimpleFactory = (function () {
return {
createProduct(type) {
switch (type) {
case '1':
return new ConcreteProduct1();
case '2':
return new ConcreteProduct2();
default:
throw new Error('Invalid product type');
}
}
};
})();
// 使用简单工厂
const product1 = SimpleFactory.createProduct('1');
product1.showType(); // 输出: Product Type: Product 1
const product2 = SimpleFactory.createProduct('2');
product2.showType(); // 输出: Product Type: Product 2
注意
虽然简单工厂模式简化了对象的创建过程,但它也存在一些缺点,比如新增产品时需要修改工厂类的代码,违反了开闭原则。因此,对于需要大量创建不同类型对象或频繁添加新类型的场景,可能需要考虑更复杂的工厂模式,如工厂方法模式或抽象工厂模式。
工厂方法模式(Factory Method)
定义: 定义一个用于创建对象的接口,但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
JS示例
// 抽象产品类
class Product {
create() {
throw new Error("抽象方法 create() 必须在子类中实现");
}
}
// 具体产品类1
class ConcreteProduct1 extends Product {
create() {
return "创建了 ConcreteProduct1 的实例";
}
}
// 具体产品类2
class ConcreteProduct2 extends Product {
create() {
return "创建了 ConcreteProduct2 的实例";
}
}
// 抽象工厂类
class Factory {
createProduct(type) {
throw new Error("抽象方法 createProduct() 必须在子类中实现");
}
}
// 具体工厂类1
class ConcreteFactory1 extends Factory {
createProduct() {
return new ConcreteProduct1();
}
}
// 具体工厂类2
class ConcreteFactory2 extends Factory {
createProduct() {
return new ConcreteProduct2();
}
}
// 应用示例
const factory1 = new ConcreteFactory1();
const product1 = factory1.createProduct();
console.log(product1.create()); // 输出: 创建了 ConcreteProduct1 的实例
const factory2 = new ConcreteFactory2();
const product2 = factory2.createProduct();
console.log(product2.create()); // 输出: 创建了 ConcreteProduct2 的实例
备注
通过工厂方法模式,我们可以将对象的创建过程推迟到子类中,使得系统在不修改现有代码的情况下容易地添加新的产品类型,符合开闭原则,同时也降低了系统的耦合度,提高了代码的可维护性和扩展性。
抽象工厂模式(Abstract Factory)
定义: 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。适用于系统需要多种产品系列且系列内部需保持一致性的场景。
JS示例
// 抽象产品接口
class ProductA {
operationA() {
throw new Error("必须在子类中实现 operationA 方法");
}
}
class ProductB {
operationB() {
throw new Error("必须在子类中实现 operationB 方法");
}
}
// 抽象工厂接口
class AbstractFactory {
createProductA() {
throw new Error("必须在子类中实现 createProductA 方法");
}
createProductB() {
throw new Error("必须在子类中实现 createProductB 方法");
}
}
// 具体产品类
class ConcreteProductA1 extends ProductA {
operationA() {
return "ConcreteProductA1 的 operationA";
}
}
class ConcreteProductB1 extends ProductB {
operationB() {
return "ConcreteProductB1 的 operationB";
}
}
// 另一组具体产品类
class ConcreteProductA2 extends ProductA {
operationA() {
return "ConcreteProductA2 的 operationA";
}
}
class ConcreteProductB2 extends ProductB {
operationB() {
return "ConcreteProductB2 的 operationB";
}
}
// 具体工厂类
class ConcreteFactory1 extends AbstractFactory {
createProductA() {
return new ConcreteProductA1();
}
createProductB() {
return new ConcreteProductB1();
}
}
class ConcreteFactory2 extends AbstractFactory {
createProductA() {
return new ConcreteProductA2();
}
createProductB() {
return new ConcreteProductB2();
}
}
// 应用示例
const factory1 = new ConcreteFactory1();
const productA1 = factory1.createProductA();
const productB1 = factory1.createProductB();
console.log(productA1.operationA()); // 输出: ConcreteProductA1 的 operationA
console.log(productB1.operationB()); // 输出: ConcreteProductB1 的 operationB
const factory2 = new ConcreteFactory2();
const productA2 = factory2.createProductA();
const productB2 = factory2.createProductB();
console.log(productA2.operationA()); // 输出: ConcreteProductA2 的 operationA
console.log(productB2.operationB()); // 输出: ConcreteProductB2 的 operationB
应用场景(工厂模式)
- 数据库操作:根据不同的数据库类型(如MySQL、MongoDB)创建相应的数据库操作对象,客户端只需指定数据库类型,由工厂自动创建相应的数据库操作实例。
- 日志记录:根据不同的日志级别(如INFO、ERROR)创建不同的日志记录器,简化客户端代码,提高系统的可扩展性。
- UI组件创建:在前端开发中,根据不同的配置或需求创建不同的UI组件(按钮、输入框等),使得组件的创建和使用更加灵活。
- 图片处理:根据不同的图片处理需求(缩放、裁剪等),创建相应的图片处理器,客户端无需了解具体的处理逻辑。
工厂模式对比(小结)
简单工厂模式 | 工厂方法模式 | 抽象工厂模式 | |
工厂=》商品 | 1=》1 | 1=》1 | n=》m |
入口形式 | 使用字符串调用工厂入口方法匹配对应商品 | 创建工厂,调用入口方法得到对应商品 | 创建工厂,调用指定方法得到对应商品 |
工厂类和必要方法 | 1个工厂类,1个入口方法 | 多个工厂类,1个入口方法 | 多个工厂类,多个入口方法 |
商品描述 | 一种商品,多个等级 | 一种商品,多个等级 | 多种商品,多个等级 |
建造者模式(Builder)
定义: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。适用于复杂对象的逐步构造过程。
JS示例
// 产品类(Product)
class Computer {
constructor(cpu, ram, hdd) {
this.cpu = cpu;
this.ram = ram;
this.hdd = hdd;
}
getConfiguration() {
return `Computer Configuration: CPU - ${this.cpu}, RAM - ${this.ram}GB, HDD - ${this.hdd}GB`;
}
}
// 抽象建造者(Builder)
class ComputerBuilder {
constructor() {
this.cpu = null;
this.ram = null;
this.hdd = null;
}
setCpu(cpu) {
this.cpu = cpu;
return this;
}
setRam(ram) {
this.ram = ram;
return this;
}
setHdd(hdd) {
this.hdd = hdd;
return this;
}
build() {
return new Computer(this.cpu, this.ram, this.hdd);
}
}
// 具体建造者(ConcreteBuilder)
class HighEndComputerBuilder extends ComputerBuilder {
constructor() {
super();
this.setCpu('Intel i9');
this.setRam(32);
this.setHdd(1024);
}
}
// 导演类(Director)
class Director {
constructor(builder) {
this.builder = builder;
}
buildComputer() {
return this.builder.build();
}
}
// 使用示例
const director = new Director(new HighEndComputerBuilder());
const computer = director.buildComputer();
console.log(computer.getConfiguration()); // 输出: Computer Configuration: CPU - Intel i9, RAM - 32GB, HDD - 1024GB
PHP示例
<?php
// 抽象建造者
abstract class BurgerBuilder {
protected $burger = [];
public function reset() {
$this->burger = [];
}
public abstract function addBun();
public abstract function addPatty();
public abstract function addVegetables();
public abstract function addSauce();
public function getBurger() {
return $this->burger;
}
}
// 具体建造者
class BeefBurgerBuilder extends BurgerBuilder {
public function addBun() {
$this->burger[] = 'Bun';
}
public function addPatty() {
$this->burger[] = 'Beef Patty';
}
public function addVegetables() {
$this->burger[] = 'Lettuce';
$this->burger[] = 'Tomato';
}
public function addSauce() {
$this->burger[] = 'BBQ Sauce';
}
}
// 导演类
class Director {
private $builder;
public function setBuilder(BurgerBuilder $builder) {
$this->builder = $builder;
}
public function buildBurger() {
$this->builder->reset();
$this->builder->addBun();
$this->builder->addPatty();
$this->builder->addVegetables();
$this->builder->addSauce();
}
}
// 使用
$director = new Director();
$beefBurgerBuilder = new BeefBurgerBuilder();
$director->setBuilder($beefBurgerBuilder);
$director->buildBurger();
$burger = $beefBurgerBuilder->getBurger();
print_r($burger); // 输出汉堡的组成部分
?>
应用场景
- DOM构建器:在前端开发中,可以使用创建者模式来构建复杂的DOM结构。开发者可以定义一系列方法来逐步添加元素、设置属性、添加事件监听器等,最后生成完整的DOM树插入页面。
- 配置对象生成器:在需要根据用户配置生成复杂配置对象的应用中,创建者模式可以用来一步步构建配置对象,确保配置的正确性和完整性,例如在设置API请求参数、构建图表配置项时。
- 表单构建器:创建者模式可以用于动态构建表单,通过添加字段、设置验证规则、布局等步骤,最终生成完整的表单结构。
- 类库或框架初始化:许多JavaScript类库和框架在初始化时会使用类似创建者模式的方式来配置和构建核心对象或组件,比如Vue、React等框架的初始化过程,可以通过一系列配置选项来定制实例的行为。
- 数据结构构建:在处理复杂数据结构时,如构建树形结构、图结构等,创建者模式可以用来一步步添加节点、边等,最后得到完整的数据结构。
- 模板引擎:虽然不是直接的创建者模式,但模板引擎在渲染复杂HTML时,其背后逻辑往往遵循创建者模式的思想,通过解析模板指令逐步构建最终的HTML字符串。
- 命令对象构建:在实现命令模式时,可以使用创建者模式来构建命令对象,每个命令对象代表一个操作,通过一步步添加操作细节,最后生成可执行的命令实例。
原型模式(Prototype)
定义: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。适用于创建新对象成本较高或需要减少对象创建时间的场景。
JS示例
class Dog {
constructor(name, breed) {
this.name = name;
this.breed = breed;
}
bark() {
console.log(`${this.name} says Woof!`);
}
clone() {
// 使用 Object.assign 或者 JSON.parse(JSON.stringify(obj)) 来实现深拷贝
return Object.assign(Object.create(Object.getPrototypeOf(this)), this);
}
}
const originalDog = new Dog('Rufus', 'Golden Retriever');
const clonedDog = originalDog.clone();
clonedDog.name = 'Buddy';
clonedDog.bark(); // 输出: Buddy says Woof!
应用场景
- 对象克隆:当需要创建具有相同或相似属性和方法的新对象时,可以通过原型链复制一个对象的原型,然后修改或添加特定属性,快速生成新对象,节省创建时间。
- 继承和扩展:如上例所示,原型模式可以用来实现对象间的继承,通过原型链机制,子类可以继承父类的属性和方法,并可以覆盖或扩展这些行为。
- 动态添加方法:可以在运行时动态地给对象添加方法,这对于需要灵活扩展功能的系统特别有用。
- 优化性能:通过原型链共享方法,可以避免每个实例都拥有方法副本,减少内存占用,特别是在创建大量相似对象时。
- 框架和库:许多JavaScript框架和库(如jQuery、Underscore等)内部使用原型模式来组织代码,提供可复用的方法和属性给所有实例。
建造型模式对比(小结)
单例模式 | 工厂模式 | 建造者模式 | 原型模式 | |
视觉角度 | 强调对象本身的唯一性 | 着重描述工厂和商品的依赖关系和商品种类扩展 | 着重对象本身属性大量依赖外部环境 | 临时修改、扩展和升级 |
应用场景 | 系统配置及模块链接池 | 用户要使用业务A和业务B自带关联的业务 | 主流程统一、子流程和属性需要自定义的 | 需要保持原有形态、还需要当前特例执行 |
技巧使用 | 私有化构造方法 | 商品类的构造放进了工厂类中,使用继承和抽象 | 抽象方法,调用时使用单一入口类(导演类) | 用到了克隆对象的方式 |
性能 | 项目调用对象单一,节省内存的同时也方便管理 | 有益于业务扩展 | 有益于业务扩展 | 利用克隆原理,达到快速复制和设置对象 |