设计模式(创建型+结构型+行为型)
通过设计模式可以帮助增强代码的可重用性、可扩充性、 可维护性、灵活性好。使用设计模式最终的目的是实现代码的 高内聚 和 低耦合
工厂模式(创建型模式)
先来理解一个概念 —— 构造器模式
开了家动物园,只有两只动物,可能会这样录入系统:
const monkey = {
name: '悟空',
age: '1'
}
const tiger = {
name: '泰格伍兹',
age: '3'
}
如果动物越来越多,对象字面量也会越来越多,这个时候构造函数可以自动创建动物对象
function Animal(name, age) {
this.name = name;
this.age = age;
}
const animal = new Animal(name, age); //Animal 就是一个构造器
像 Animal 这样当新建对象的内存被分配后,用来初始化该对象的特殊函数,就叫做构造器。在 JavaScript 中,使用构造函数去初始化对象,就是应用了构造器模式。
可以看出每个实例化后 对象(animal)属性的 key (name,age) 是不变的,对应的 value(空空,泰格伍兹)是变的。所以构造器将赋值过程封装,确保了每个对象属性固定,开放了取值确保个性灵活。
简单工厂模式
动物园要求根据每个动物的食性喜好来分配不同的食物。这样之前封装的 Animal 就不能直接用了,重新封装的构造器。
function Vegetarian(name, age) {
this.name = name;
this.age = age;
this.favorite = "fruit";
this.food = [apple, banaba];
}
function Carnivore(name, age) {
this.name = name;
this.age = age;
this.favorite = "meat";
this.food = [beef, pork];
}
根据喜好可以分配相应的
function Factory(name, age, favorite) {
switch (career) {
case "fruit":
return new Vegetarian(name, age);
break;
case "meat":
return new Carnivore(name, age);
break;
// ...
}
}
总结
工厂模式:将创建对象的过程单独封装。
应用场景:有构造函数的地方、写了大量构造函数、调用了大量的 new 的情况下
单例模式(创建型模式)
保证仅有一个实例,并提供一个访问它的全局访问点,这样的模式就叫做单例模式。然后性能得到优化!
以下代码做一个弹窗 如果实例已经创建过 就无需再次创建 这就是单例!
<body>
<input type="button" id="btn1" value="成功" />
<input type="button" id="btn2" value="失败" />
<input type="button" id="btn3" value="警告" />
</body>
<script>
const obj = {
init:function(){
this.ele = document.createElement("dialog"),
document.body.appendChild(this.ele);
},
show:function(c, t){
// 每次显示之前先判断是否已经存在弹出框元素,如果不存在就创建,如果已经存在就不用重新创建,只需要修改样式和显示即可
if(!this.ele){
this.init();
}
this.ele.style.borderColor = c;
this.ele.style.color = c;
this.ele.innerHTML = t;
this.ele.style.display = "block";
clearTimeout(this.t);
this.t = setTimeout(()=>{
this.hide();
}, 2000);
},
hide:function(){
this.ele.style.display = "none";
}
}
const obtn1 = document.getElementById("btn1")
const obtn2 = document.getElementById("btn2")
const obtn3 = document.getElementById("btn3")
obtn1.onclick = function(){
obj.show("green", "成功");
}
obtn2.onclick = function(){
obj.show("red", "失败");
}
obtn3.onclick = function(){
obj.show("yellow", "警告");
}
</script>
总结
优点:适用于单一对象,只生成一个对象实例,避免频繁创建和销毁实例,减少内存占用。
缺点:不适用动态扩展对象,或需创建多个相似对象的场景。
建造者模式(创建型模式)
当一个构造函数的参数较多时使用,根据参数进行分类然后分别构建各个部分,最后挂载到对象的属性上。
实例:候选人
候选人对象具有人的基础属性,例如:姓名、年龄等,除了基础属性外还有工作属性。
工作属性还有其他的属性,例如:工作名称、种类、描述等。
为了简化候选人的构造,将其基础属性和工作属性分开,分别构建。以后需要扩展时也可以分别扩展互不影响。
function Candidate(param) {
let _candidate = new Person(param);
_candidate.work = new CreatedWork(param.work);
return _candidate;
}
function Person(param) {
this.name = param.name;
this.age = param.age;
}
function CreatedWork(work) {
switch (work) {
case "engineer":
this.name = "工程师";
this.description = "热爱编程";
break;
case "teacher":
this.name = "老师";
this.description = "乐于分享";
break;
default:
this.name = param.work;
this.description = "无";
break;
}
}
CreatedWork.prototype.changeWork = function (work) {
this.name = work;
};
CreatedWork.prototype.changeDes = function (des) {
this.description = des;
};
总结
优点: 建造者模式的封装性很好;相同的创建过程可以创建不同的产品对象;可以更加精细地控制产品的创建过程;建造者模式很容易进行扩展
缺点:建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制
装饰器模式(结构型模式)
当接手老代码,需要对它已有的功能做个拓展。
let horribleCode = function(){
console.log('是一堆看不懂的老逻辑')
}
// 改成:
let horribleCode = function(){
console.log('是一堆看不懂的老逻辑')
console.log('是新的逻辑')
}
这样做有很多的问题。直接去修改已有的函数体,违背了“开放封闭原则”;往一个函数体里塞这么多逻辑,违背了“单一职责原则”。
为了不被已有的业务逻辑干扰,将旧逻辑与新逻辑分离,把旧逻辑抽出去:
let horribleCode = function(){
console.log('是一堆看不懂的老逻辑')
}
let _horribleCode = horribleCode
horribleCode = function() {
_horribleCode()
console.log('是新的逻辑')
}
horribleCode()
适配器模式(结构型模式)
适配器模式(结构型)
应用举例: 点外卖的时候有美团,饿了么可以选择,同一家店如果要对比两个平台的价格来回切换 App 十分不方便,作为一个 Coder 能用代码解决的坚决不用人力。这个时候就想到写个小应用对比两家的价格。
在他们 openapi 里找到了对应的方法,发现请求不一样,入参不一样,返回的数据结构也不一样。翻译成伪代码就是如下的状态
class Eleme() {
getElePice() {
console.log('在饿了么上商品的价格')
return {elePrice:xx}
}
}
class Meituan() {
getMeiPice() {
console.log('在美团上商品的价格')
return {meiPrice:xx}
}
}
试想一下,如果再多增加一些其他平台,前端渲染的时候要写多少个 if else 去判断来源。这个时候可以通过引入适配器
class ElemeAdapter() {
getPrice () {
const e = new Eleme()
return { price:e.elePrice}
}
}
class MeituanAdapter() {
getPrice () {
const m = new Meituan()
return { price:m.meiPrice}
}
}
//通过适配器拿到的数据格式都是统一的 {price:xx}
//同样,入参也可以在适配器中统一处理
虽然这种模式很简单,但还有很多场景运用到了适配器模式。如 axios 抹平了 web 和 node 环境下 api 的调用差异、React 的高阶组件等。适配器不会去改变实现层,那不属于它的职责范围,它干涉了抽象的过程。外部接口的适配能够让同一个方法适用于多种系统。
总结
适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实 现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够 使它们协同作用。
代理模式(结构型模式)
代理,顾名思义就是帮助别人做事,GoF 对代理模式的定义如下:
代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。
代理模式使得代理对象控制具体对象的引用。代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西。
// 来举一个简单的例子,假如dudu要送酸奶小妹玫瑰花,却不知道她的联系方式或者不好意思,想委托大叔去送这些玫瑰,
// 那大叔就是个代理(其实挺好的,可以扣几朵给媳妇),那如何来做呢?
// 先声明美女对象
let girl = function (name) {
this.name = name;
};
// 这是dudu
let dudu = function (girl) {
this.girl = girl;
this.sendGift = function (gift) {
alert("Hi " + girl.name + ", dudu送一个礼物:" + gift);
}
};
// 大叔是代理
let proxyTom = function (girl) {
this.girl = girl;
this.sendGift = function (gift) {
(new dudu(girl)).sendGift(gift); // 替dudu送花咯
}
};
调用
let proxy = new proxyTom(new girl("酸奶小妹"));
proxy.sendGift("999朵玫瑰");
远程代理,也就是为了一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实,就像 web service 里的代理类一样。
虚拟代理,根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象,比如浏览器的渲染的时候先显示问题,
而图片可以慢慢显示(就是通过虚拟代理代替了真实的图片,此时虚 拟代理保存了真实图片的路径和尺寸。
安全代理,用来控制真实对象访问时的权限,一般用于对象应该有不同的访问权限。
发布订阅模式(行为型模式)
发布订阅模式的一般步骤
- 定义发布者
- 给发布者指定缓存列表,用于存储回调函数以便通知订阅者
- 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发回调函数
// 发布者 亚马逊
class Publisher() {
construct() {
this.observers = []
}
// 添加订阅者
add(oberver) {
this.observers.push(observer)
}
// 通知所有订阅者
notify() {
this.observers.forEach((observer) => {
//调用订阅者的函数
observer.update(this)
})
}
}
// 订阅者类 顾客
class Observer {
constructor() {
}
update() {
console.log('Observer buy buy buy')
}
}
const cunstomer = new CunstomerObserver() //创建订阅者:顾客
const amazon = new Publisher() //亚马逊
amazon.add(cunstomer) //订阅到货小修消息
amazon.notify() //货物到达通知顾客
广泛应用于异步编程中,先订阅某个请求的某个事件,当这个事件触发时就会执行订阅的事件处理函数
发布订阅模式可以取代通过硬编码的对象通知机制,不需要显式地调用另一个对象的接口,减少对象之间的耦合
中介者模式(行为型模式)
使得各对象不用显式地相互引用,将对象与对象之间紧密的耦合关系变得松散,从而可以独立地改变他们。核心是多个对象之间复杂交互的封装。
/* 男方 */ const ZhangXiaoShuai = {
name: "张小帅",
family: "张小帅家",
info: { age: 25, height: 171, salary: 5000 },
target: { age: [23, 27] },
};
/* 男方家长 */ const ZhangXiaoShuaiParent = {
name: "张小帅家长",
family: "张小帅家",
info: null,
target: { height: [160, 167] },
};
/* 女方 */ const LiXiaoMei = {
name: "李小美",
family: "李小美家",
info: { age: 23, height: 160 },
target: { age: [25, 27] },
};
/* 女方家长 */ const LiXiaoMeiParent = {
name: "李小美家长",
family: "李小美家",
info: null,
target: { salary: [10000, 20000] },
};
/* 媒人 */ const MatchMaker = {
matchBook: {
/* 注册各方 */
registPersons(...personList) {
personList.forEach((person) => {
if (this.matchBook[person.family]) {
this.matchBook[person.family].push(person);
} else this.matchBook[person.family] = [person];
});
},
/* 检查对方家庭的孩子对象是否满足要求 */
checkAllPurpose() {
Object.keys(this.matchBook) // 遍历名册中所有家庭
.forEach((familyName, idx, matchList) =>
matchList
.filter((match) => match !== familyName) // 对于其中一个家庭,过滤出名册中其他的家庭
.forEach((enemyFamily) =>
this.matchBook[enemyFamily] // 遍历该家庭中注册到名册上的所有成员
.forEach((enemy) =>
this.matchBook[familyName].forEach(
(
person // 逐项比较自己的条件和他们的要求
) => enemy.info && this.checkPurpose(person, enemy)
)
)
)
);
},
/* 检查对方是否满足自己的要求,并发信息 */
checkPurpose(person, enemy) {
const result = Object.keys(person.target) // 是否满足自己的要求
.every((key) => {
const [low, high] = person.target[key];
return low <= enemy.info[key] && enemy.info[key] <= high;
});
this.receiveResult(result, person, enemy); // 通知对方
},
/* 通知对方信息 */
receiveResult(result, person, enemy) {
result
? console.log(
`${person.name} 觉得合适~ \t(${enemy.name} 已经满足要求)`
)
: console.log(
`${person.name} 觉得不合适! \t(${enemy.name} 不能满足要求!)`
);
},
},
}; // 媒人的花名册
总结
优点:
松散耦合,降低了对象之间的相互依赖和耦合,不会像之前那样牵一发动全身;
将对象间的一对多关联转变为一对一的关联,符合最少知识原则,提高系统的灵活性,使得系统易于维护和扩展;
中介者在对象间起到了控制和协调的作用,因此可以结合代理模式那样,进行对象间的访问控制、功能扩展;
因为对象间不需要相互引用,因此也可以简化对象的设计和实现;
缺点:逻辑过度集中化,当对象太多时,中介者的职责将很重,逻辑变得复杂而庞大,以至于难以维护。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南