likes
comments
collection
share

JavaScript常用的3种设计模式

作者站长头像
站长
· 阅读数 22

1.JS常用设计模式分类

1.创建型模式

定义:创建型模式关注对象的创建过程,系统可以更加灵活、可配置,并且在创建对象时不必关心具体的类或构造过程。

  • 1.单例模式 ⭐

  • 2.抽象工厂模式

  • 3.原型模式

2.结构型模式

定义:结构型模式关注如何组合对象和类来形成较大的结构,而不仅仅是组合它们的简单方式。结构型模式可以帮助确保系统中的各个部分能够清晰、有序地一起工作,同时也能为将来的改变或扩展提供更好的灵活性。

  • 1.适配器模式

  • 2.装饰器模式 ⭐

  • 3.代理模式

3.行为型模式

定义:关注于对象之间的职责分配和它们之间的通信。描述了不同的对象和类如何协作以实现更大的功能。行为型模式关注的是类和对象的交互以及职责。

  • 1.观察者模式 ⭐

  • 2.策略模式

  • 3.职责链模式

  • 4.迭代器模式

4.结构型模式和行为型模式的区别

总的来说,结构型模式和行为型模式分别解决软件设计中的不同方面的问题。结构型模式关注于对象和类的构造和组合,而行为型模式关注于对象间的交互和职责分配。

2.创建型模式

1.单例模式

1.定义

  • 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2.JS代码实现

class Singleton {
  constructor() {
    if (!Singleton.instance) {
      Singleton.instance = this
    }
    return Singleton.instance
  }
}

const obj1 = new Singleton()
const obj2 = new Singleton()
console.log(obj1 === obj2) // true

上面的代码中,你通过 new Singleton() 创建一个实例,后续再创建实例的话返回的还是第一个创建的实例,这就保证了这个 Singleton 类仅有一个实例,然后访问它的全局访问点就是 new Singleton()

3.优点

  • 1.唯一实例:单例模式确保一个类只有一个实例,避免重复创建资源。

  • 2.全局访问点:单例模式为唯一的实例提供了一个全局访问点,使其他对象可以直接访问。

  • 3.共享资源:由于只存在一个实例,所以它可以方便的共享数据,使得数据的访问和操作更为集中和一致。

4.缺点

  • 可能导致代码之间的过度耦合,并可能导致不需要的长时间生存周期的对象。

5.应用

  • 1.全局配置:如果你的应用程序需要一个集中的地方来保存和管理配置信息(例如API端点、主题颜色、功能标志等),单例模式可以帮助确保整个应用程序使用同一组配置数据。
class AppConfig {
  // 静态属性存储单例实例
  static instance;
  config = {
    apiEndpoint: "https://api.example.com",
    themeColor: "blue",
    featureFlag: true
  };

  constructor() {
    if (!AppConfig.instance) {
      AppConfig.instance = this;
    }
    return AppConfig.instance;
  }

  setConfig(key, value) {
    this.config[key] = value;
  }

  getConfig(key) {
    return this.config[key];
  }

  getAllConfig() {
    return this.config;
  }
}

// 使用配置单例
const appConfig1 = new AppConfig();
const appConfig2 = new AppConfig();

console.log(appConfig1 === appConfig2);  // true,确保是同一个实例

// 获取API端点
console.log(appConfig1.getConfig("apiEndpoint"));  // "https://api.example.com"

// 修改主题颜色
appConfig1.setConfig("themeColor", "red");

// 从另一个地方确认修改的效果
console.log(appConfig2.getConfig("themeColor"));  // "red"

// 获取所有配置
console.log(appConfig1.getAllConfig());
  • 2.全局缓存
class Cache {
    constructor(){
      if(!Cache.instance){
        this.cacheObject = {}
        Cache.instance = this
      }
      return Cache.instance
    }

    set(key,value){
      this.cacheObject[key] = value
    }

    get(key){
      return this.cacheObject[key]
    }

    remove(key){
      delete this.cacheObject[key]
    }

    clear(){
      this.cacheObject = {}
    }
}

const cacheInstance = new Cache()
cacheInstance.set('user_1',{name:'星空海绵',age:18})
console.log(cacheInstance.get('user_1'));

const anotherCacheInstance = new Cache()
console.log(cacheInstance.get('user_1'));
console.log(cacheInstance === anotherCacheInstance); // true
  • 3.网页登录浮窗
class LoginModal {
  constructor() {
    if (LoginModal.instance) {
      return LoginModal.instance;
    }

    this.modal = this.createModal();
    LoginModal.instance = this;
  }

  createModal() {
    const modal = document.createElement('div');
    modal.innerHTML = `
      <div class="login-modal">
        <div class="login-content">
          <h2>Login</h2>
          <input type="text" placeholder="Username" />
          <input type="password" placeholder="Password" />
          <button>Submit</button>
        </div>
      </div>
    `;
    modal.style.display = 'none'; // Initially hidden
    document.body.appendChild(modal);
    return modal;
  }

  show() {
    this.modal.style.display = 'block';
  }

  hide() {
    this.modal.style.display = 'none';
  }
}

// 使用
const login1 = new LoginModal();
login1.show();

const login2 = new LoginModal();
console.log(login1 === login2); // true
  • 4.线程池
// 创建一个内联的Web Worker
const workerScript = `
    self.onmessage = function(event) {
        const result = event.data * 2;
        self.postMessage(result);
    };
`;

const workerBlob = new Blob([workerScript], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(workerBlob);

class ThreadPool {
  constructor(size) {
    this.size = size;
    this.tasks = [];
    this.workers = [];

    for (let i = 0; i < this.size; i++) {
      const worker = new Worker(workerUrl);
      worker.onmessage = (event) => {
        if (this.tasks.length > 0) {
          const nextTask = this.tasks.shift();
          worker.postMessage(nextTask.data);
          nextTask.resolve(event.data);
        } else {
          this.workers.push(worker);
        }
      };
      this.workers.push(worker);
    }
  }

  execute(data) {
    return new Promise((resolve) => {
      const worker = this.workers.pop();

      if (worker) {
        worker.postMessage(data);
        worker.onmessage = (event) => {
          this.workers.push(worker);
          resolve(event.data);
        };
      } else {
        this.tasks.push({ data, resolve });
      }
    });
  }
}

const threadPoolSingleton = (function () {
  let instance = null;
  return {
    getThreadPool: function (size) {
      if (!instance) {
        instance = new ThreadPool(size);
      }
      return instance;
    }
  };
})();

// 使用线程池
const pool = threadPoolSingleton.getThreadPool(4);
pool.execute(5).then(result => console.log(result)); // 10
pool.execute(7).then(result => console.log(result)); // 14

3.结构型模式

1.装饰器模式

1.定义

  • 在运行时向对象动态地添加新的行为或职责,而不修改其结构。

2.JS代码实现

class Coff {
  cost() {
    return 5
  }
}

class Moka {
  constructor(coff) {
    this.coff = coff
  }
  cost() {
    return this.coff.cost() + 7
  }
}

class Latte {
  constructor(coff) {
    this.coff = coff
  }
  cost() {
    return this.coff.cost() + 10
  }
}

const coff = new Coff()
const moka = new Moka(coff)
const latte = new Latte(coff)
console.log(moka.cost());
console.log(latte.cost());

3.优点

  • 1.更好的分离和职责划分:每个装饰器都只关心自己的功能,使得每个装饰器都遵循单一职责原则。

  • 2.增强的扩展性:可以在运行时动态地、透明地添加功能,而不是通过继承来做到这一点。

4.缺点

  • 1.可能导致大量小类:对于每种装饰功能,你都需要一个新的具体装饰器类,这可能导致系统中有很多小的类。

5.应用

  • 权限系统:可以通过装饰器来为用户添加不同的权限或角色,从而实现细粒度的权限控制。

4.行为型模式

1.观察者模式

1.定义

  • 观察者模式用于在对象之间建立一种一对多的依赖关系,这样当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。

2.JS代码实现

class Subject {
  constructor() {
    this._observers = []
  }

  subscribe(observer) {
    this._observers.push(observer)
  }

  unsubscribe(observer) {
    const index = this._observers.indexOf(observer)
    if (index !== -1) {
      this._observers.splice(index, 1)
    }
  }

  notify(data) {
    for (let observer of this._observers) {
      observer.update(data)
    }
  }
}

class Observer {
  constructor(name) {
    this.name = name
  }
  update(data) {
    console.log(`${this.name}收到数据${data}`);
  }
}

const subject = new Subject()
const observer1 = new Observer('Jack')
const observer2 = new Observer('Marry')
subject.subscribe(observer1)
subject.subscribe(observer2)
subject.notify('Hello!')
subject.unsubscribe(observer2)
subject.notify('你好!')

3.优点

  • 1.开放/封闭原则:允许你在不修改主题的情况下增加新的观察者。观察者可以任意增减,主题无需关心。

  • 2.建立动态关系:主题和观察者之间的关系是动态建立的,可以在运行时增加或删除观察者,使得系统更加灵活。

  • 3.解耦:观察者和被观察对象是抽象耦合的,增加、删除、修改观察者或者被观察对象都很方便,并且不会影响对方。

  • 4.支持广播通信:一个主题可以有任意数量的观察者。只需要增加一次就可以通知所有观察者对象,减少了系统的复杂性。

4.缺点

  • 1.可能导致性能问题:如果一个主题有大量的直接和间接观察者,通知所有观察者可能会花费大量时间,从而影响系统的性能。

  • 2.难于追踪和调试:由于观察者模式提供了一种比较抽象和隐蔽的消息传递机制,因此在出现问题时可能很难追踪和调试。

  • 3.意外的更新:某些情况下,观察者可能收到并不需要的更新,从而导致不必要的操作。

5.应用

  • 1.模型-视图-控制器(MVC)架构:模型(Model)是主题,视图(View)是观察者。当模型发生变化时,通知视图进行更新。

  • 2.实时数据展示:例如股票或其他实时数据流的显示,当数据发生变化时,相关的展示组件需要得到及时的更新。

2.发布订阅模式

1.定义

  • 发布-订阅模式(Pub-Sub)是一种消息通信模式,用于在系统的不同部分之间传递特定的事件信息。这种模式类似于广播:一个组件(发布者)发送消息,但并不知道谁将接收它;其他组件(订阅者)可以订阅这些消息并对它们作出响应,而无需知道是哪个组件发布了这些消息。

2.JS代码实现

class EventBus {
  constructor() {
    this.subscribers = {}
  }

  // 注册事件和回调
  subscribe(event, callback) {
    if (!this.subscribers[event]) {
      this.subscribers[event] = []
    }
    this.subscribers[event].push(callback)
  }

  // 取消订阅
  unsubscribe(event, callback) {
    if (!this.subscribers[event]) return
    const index = this.subscribers[event].indexOf(callback)
    if (index !== -1) {
      this.subscribers[event].splice(index, 1)
    }
  }

  // 发布事件
  publish(event, data) {
    if (!this.subscribers[event]) return
    for (let callback of this.subscribers[event]) {
      callback(data)
    }
  }
}
const eventBus = new EventBus()
function displayData(data) {
  console.log(`收到数据:${data}`);
}
eventBus.subscribe('dataReceived', displayData)
eventBus.publish('dataReceived', 'Hello!')

3.优点

  • 1.解耦:发布者和订阅者之间没有直接的依赖关系。这提高了系统的灵活性和可扩展性。

  • 2.动态:你可以在运行时动态地添加或删除订阅者,或改变发布者发布的消息。

  • 3.多对多关系:多个发布者可以发送消息给多个订阅者。

4.缺点

  • 1.调试困难:由于发布者和订阅者之间的解耦,可能导致在出现问题时难以找到错误的源头。

观察者模式和发布订阅模式的区别?

  • 观察者模式:在此模式中,有两种主要的参与者:主题和观察者。主题维护了一组观察者,当主题的状态发生改变时,它会通知所有注册的观察者。

  • 发布-订阅模式:此模式使用一个中介对象,通常被称为“调度中心”或“事件通道”。订阅者订阅事件,发布者发布事件,但它们不直接互相通知。而是通过调度中心来管理通知。

参考资料