什么是设计模式
简单来说,设计模式就是解决一类问题通用且可复用的方案。它不是具体的代码,而是一种思想,一种经验总结。我们平时写代码时,设计模式就是我们写代码的结构和"地基",我们要按照这个特定模式来写。因此,在编程中,设计模式就是帮我们写出更健壮且易于维护的代码。
常见设计模式如下:
单例模式
- 核心思想:保证一个类只有一个实例,并提供一个全局访问点
试想,你有一个全局的配置管理器,你希望在整个应用中,无论在哪里都只创建一个实例,而不是每次调用都创建一个新的,这就是单例模式的用武之地。
-
怎么实现:用一个变量来缓存这个已经创建好的实例
javascriptclass Singleton { constructor() { // 假设这里有一些初始化逻辑 console.log('我被创建了!'); } // 核心:静态方法或属性来管理实例 static getInstance() { // 检查是否已经存在实例 if (!Singleton.instance) { // 如果不存在,就创建一个新的并缓存起来 Singleton.instance = new Singleton(); } // 返回缓存的实例 return Singleton.instance; } // 假设有一些方法 showMessage() { console.log('我是单例模式的实例!'); } } // 第一次获取实例 const instance1 = Singleton.getInstance(); // 输出: "我被创建了!" // 第二次获取实例 const instance2 = Singleton.getInstance(); // 不会再输出 "我被创建了!" // 比较两个实例,它们是同一个! console.log(instance1 === instance2); // 输出: true instance1.showMessage(); instance2.showMessage();
-
总结:单例模式就像一个独一无二的总司令,所有人只能通过一个固定的渠道(getInstance方法)来和他联系,确保不会有第二个总司令出现。
工厂模式
- 核心思想:定义一个创建对象的接口,但让子类决定实例化哪一个类。
这个模式就像一个工厂。你告诉工厂你想要一个什么产品,工厂就会帮你生产出来,但不需要知道具体的生产细节。
-
怎么实现:我们创建一个工厂函数或工厂类,专门负责创建不同类型的对象。
typescript// 假设我们有不同类型的用户 class RegularUser { constructor() { this.type = '普通用户'; } } class AdminUser { constructor() { this.type = '管理员'; } } // 工厂函数:根据传入的类型创建不同的用户对象 function UserFactory(type) { switch (type) { case 'regular': return new RegularUser(); case 'admin': return new AdminUser(); default: throw new Error('不支持的用户类型!'); } } // 生产一个普通用户 const user1 = UserFactory('regular'); console.log(user1.type); // 输出: "普通用户" // 生产一个管理员 const user2 = UserFactory('admin'); console.log(user2.type); // 输出: "管理员"
-
总结:工厂模式把创建对象的逻辑和使用对象的逻辑分开了。当你需要创建许多相似但又有点不同的对象时,它可以使代码更整洁的同时且易于扩展。
观察者模式
- 核心思想:定义了对象之间一对多的关系,当一个对象状态发生改变时,所有依赖它的对象都会得到通知并自动更新。
这个模式就像公众号推送文章。公众号(主题)推送一篇文章,所有订阅了这个公众号的读者(观察者)都会收到这篇文章。
- 怎么实现:需要一个主题来管理观察者,以及一些观察者。
主题:负责添加,移除,通知观察者。
观察者:订阅主题,并提供一个更新方法。
- 总结:
观察者模式在前端应用中非常常见,比如事件监听(addEventListener).DOM元素就是主题,你注册的回调函数就是观察者。当事件发生时,DOM元素会通知所有注册的函数执行。
代理模式
- 核心思想:为另一个对象提供一个替身或占位符,以控制对这个对象的访问。
类比于明星与经纪人,艺人所有事物都由经纪人处理,外部只和经纪人打交道,经纪人决定是否让外部接触到明星
-
怎么实现:创建一个代理对象,它和真实对象实现相同的接口,但在真实调用对象的方法前后,以增加额外的逻辑
javascript// 1. 定义真实对象 (明星) class Star { constructor(name) { this.name = name; } // 假设这是明星接广告的方法 receiveAd(ad) { console.log(`${this.name} 正在拍摄广告: ${ad}`); } } // 2. 定义代理对象 (经纪人) class Agent { constructor(star) { // 代理对象内部持有真实对象的引用 this.star = star; } // 代理方法:在调用真实对象的方法前后增加逻辑 receiveAd(ad) { // 前置逻辑:代理人先筛选广告 if (ad === '劣质广告') { console.log('经纪人拒绝了劣质广告!'); return; } console.log(`经纪人正在为 ${this.star.name} 接洽广告...`); // 调用真实对象的方法 this.star.receiveAd(ad); // 后置逻辑:广告拍摄后,代理人处理后续事务 console.log('经纪人处理了广告尾款和宣传事宜。'); } } // --- 使用代理模式 --- const jayChou = new Star('周杰伦'); const jayChouAgent = new Agent(jayChou); // 通过经纪人(代理)来接广告 jayChouAgent.receiveAd('优质饮品广告'); // 输出: // 经纪人正在为 周杰伦 接洽广告... // 周杰伦 正在拍摄广告: 优质饮品广告 // 经纪人处理了广告尾款和宣传事宜。 console.log('---') // 经纪人拒绝劣质广告 jayChouAgent.receiveAd('劣质广告'); // 输出: // 经纪人拒绝了劣质广告!
-
总结:代理模式关注的是对对象的控制和保护。我需要对一个对象的访问进行控制(在访问的前后做一些事),所以用一个代理包裹它。
装饰器模式
- 核心思想:动态地给一个对象添加额外的功能。
想象一下,你有一个基础的咖啡(被装饰对象)。你想给它加点东西,比如牛奶,糖,奶油,你不需要改变咖啡本身的结构,而是用装饰器来包裹它,从而动态地添加新功能
-
怎么实现:创建一系列装饰器,它们都继承自统一接口,并且都持有一个被装饰对象的引用。每个装饰器在调用被装饰对象的方法后,再添加自己的功能。
javascript// 1. 定义基础组件 (咖啡) class Coffee { cost() { return 10; } getDescription() { return '一杯纯咖啡'; } } // 2. 定义装饰器基类 (通常是抽象类或接口,JS中可以用普通类模拟) // 装饰器基类应该和Coffee有相同的接口 // class Decorator { // cost() { ... } // getDescription() { ... } // } // 3. 定义具体的装饰器 (牛奶, 糖, 奶油) // 牛奶装饰器 class MilkDecorator { constructor(coffee) { this.coffee = coffee; // 持有被装饰对象的引用 } cost() { // 在原有价格上加上牛奶的价格 return this.coffee.cost() + 3; } getDescription() { // 在原有描述上加上牛奶 return this.coffee.getDescription() + ', 加牛奶'; } } // 糖装饰器 class SugarDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 2; } getDescription() { return this.coffee.getDescription() + ', 加糖'; } } // --- 使用装饰器模式 --- let myCoffee = new Coffee(); // 制作一杯纯咖啡 console.log(myCoffee.getDescription(), myCoffee.cost()); // 输出: 一杯纯咖啡 10 // 给咖啡加上牛奶 myCoffee = new MilkDecorator(myCoffee); console.log(myCoffee.getDescription(), myCoffee.cost()); // 输出: 一杯纯咖啡, 加牛奶 13 // 再给这杯加了牛奶的咖啡加上糖 myCoffee = new SugarDecorator(myCoffee); console.log(myCoffee.getDescription(), myCoffee.cost()); // 输出: 一杯纯咖啡, 加牛奶, 加糖 15 // ES7+ 的 Decorator 语法 (更简洁,但目前仍是提案) // @milk // @sugar // class MyCoffee { // ... // }
-
总结:装饰器模式通过组合而非继承的方式来扩展对象的功能。它可以避免创建一个复杂的继承体系。这种方式非常灵活,可以动态地,一层一层地给对象添加功能。像react框架中,高阶组件就是一种典型地装饰器模式体现。
总结思考
设计模式的精髓在于解耦(降低模块间的耦合度)和扩展(让代码更容易添加新功能)。当你面对一个具体问题时,试着思考:"我可以用什么模式解决"。这样就可以把理论知识转为实际的编码能力。