设计模式(创建型+结构型+行为型)

通过设计模式可以帮助增强代码的可重用性、可扩充性、 可维护性、灵活性好。使用设计模式最终的目的是实现代码的 高内聚 和 低耦合

工厂模式(创建型模式)

先来理解一个概念 —— 构造器模式
开了家动物园,只有两只动物,可能会这样录入系统:

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 里的代理类一样。
虚拟代理,根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象,比如浏览器的渲染的时候先显示问题,
而图片可以慢慢显示(就是通过虚拟代理代替了真实的图片,此时虚 拟代理保存了真实图片的路径和尺寸。
安全代理,用来控制真实对象访问时的权限,一般用于对象应该有不同的访问权限。

发布订阅模式(行为型模式)

发布订阅模式的一般步骤

  1. 定义发布者
  2. 给发布者指定缓存列表,用于存储回调函数以便通知订阅者
  3. 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发回调函数
// 发布者 亚马逊
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} 不能满足要求!)`
          );
    },
  },
}; // 媒人的花名册

总结
优点:
松散耦合,降低了对象之间的相互依赖和耦合,不会像之前那样牵一发动全身;
将对象间的一对多关联转变为一对一的关联,符合最少知识原则,提高系统的灵活性,使得系统易于维护和扩展;
中介者在对象间起到了控制和协调的作用,因此可以结合代理模式那样,进行对象间的访问控制、功能扩展;
因为对象间不需要相互引用,因此也可以简化对象的设计和实现;

缺点:逻辑过度集中化,当对象太多时,中介者的职责将很重,逻辑变得复杂而庞大,以至于难以维护。

posted @   柯基与佩奇  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
欢迎阅读『设计模式(创建型+结构型+行为型)』
点击右上角即可分享
微信分享提示