前端常用设计模式

SOLID设计原则

// 单一职责原则(Single Responsibility Principle,SRP)

// 负责显示用户信息的组件
class UserInfoComponent {
  render(user) {
    // 渲染用户信息到DOM
  }

  // 发送请求获取用户信息
  fetchUserInfo(userId) {
    // 发送Ajax请求获取用户信息
  }
}

在上述案例中,UserInfoComponent负责两个职责:显示用户信息和发送请求获取用户信息。如果我们将发送请求的逻辑提取到一个单独的服务或钩子函数中,可以更好地遵守单一职责原则。

// 单一职责原则(Single Responsibility Principle,SRP)

// 负责显示用户信息的组件
class UserInfoComponent {
  render(user) {
    // 渲染用户信息到DOM
  }
}

// 用户信息服务
class UserInfoService {
  fetchUserInfo(userId) {
    // 发送Ajax请求获取用户信息
  }
}
// 开放封闭原则(Open-Closed Principle,OCP)

// 主题切换功能
class ThemeSwitcher {
  constructor() {
    this.currentTheme = 'default';
  }

  // 切换主题
  switchTheme(theme) {
    // 切换应用的外观样式
  }
}

在上述案例中,主题切换功能违反了开放封闭原则,因为每次添加新的主题都需要修改切换主题的方法。更好的做法是通过使用CSS预处理器或主题配置文件来实现主题切换,而无需修改原有的代码。

// 开放封闭原则(Open-Closed Principle,OCP)

// 主题切换功能
class ThemeSwitcher {
  constructor() {
    this.currentTheme = 'default';
  }

  // 切换主题
  switchTheme(theme) {
    // 切换应用的外观样式
  }

  // 添加新的主题
  addTheme(theme) {
    // 添加新的主题文件或修改配置
  }
}
// 里氏替换原则(Liskov Substitution Principle,LSP)

// 基础页面组件
class BasePageComponent {
  render() {
    // 渲染页面
  }

  // 处理页面逻辑
  handlePageLogic() {
    // 处理页面逻辑
  }
}

// 子页面组件
class SubPageComponent extends BasePageComponent {
  render() {
    // 渲染子页面
  }

  // 重写父类的页面逻辑处理方法
  handlePageLogic() {
    // 处理子页面特定的逻辑
  }
}

在上述案例中,子页面组件继承自基础页面组件,并重写了页面逻辑处理方法。确保子组件可以无缝替换基础组件,而不会影响程序的正确性。

// 接口隔离原则(Interface Segregation Principle,ISP)

// 实用函数模块
const UtilityFunctions = {
  // 生成随机字符串
  generateRandomString() {
    // 生成随机字符串的逻辑
  },

  // 加密字符串
  encryptString(str) {
    // 加密字符串的逻辑
  }
};

在上述案例中,将实用函数拆分为独立的模块,每个模块只包含相关的函数,以便开发者可以选择性地引入和使用。

// 依赖倒置原则(Dependency Inversion Principle,DIP)

// 数据服务接口
class DataService {
  fetchData() {
    // 获取数据的逻辑
  }
}

// 前端组件
class MyComponent {
  constructor(dataService) {
    this.dataService = dataService;
  }

  fetchData() {
    this.dataService.fetchData();
  }
}

// 创建具体的数据服务实例
const dataService = new DataService();

// 创建前端组件实例并注入数据服务
const myComponent = new MyComponent(dataService);

在上述案例中,前端组件通过依赖注入方式接收一个数据服务的实例。这样,组件不直接依赖于具体的数据服务类,而是依赖于抽象的接口,从而减少了组件与具体实现的耦合,提高了代码的灵活性和可测试性。

单例模式

单例模式是一种设计模式,它确保一个类只有一个实例,并提供全局访问点。在前端开发中,单例模式常用于管理全局状态、资源共享和避免重复创建对象等场景。以下是一个前端的单例模式代码案例,附带注释说明:

// 前端单例模式案例

// 单例对象
class Singleton {
  constructor() {
    // 初始化操作
    // ...
  }

  // 单例方法
  someMethod() {
    // 方法逻辑
  }
}

// 单例实例
const singletonInstance = new Singleton();

// 全局访问点
const SingletonInstance = {
  getInstance() {
    return singletonInstance;
  }
};

// 使用单例对象
const instance = SingletonInstance.getInstance();
instance.someMethod();

在上述案例中,我们定义了一个Singleton类作为单例对象的实现。该类在构造函数中执行初始化操作,你可以根据具体需求进行扩展。
通过将单例对象的实例存储在全局访问点SingletonInstance中,我们可以在应用程序的任何地方获取该单例实例。
在使用单例对象时,我们首先通过SingletonInstance.getInstance() 获取单例实例,然后调用其方法进行操作。由于单例模式保证了只有一个实例存在,所以我们可以在不同的模块或组件中共享相同的实例。
需要注意的是,单例模式虽然可以方便地共享实例,但也可能带来全局状态管理的问题。使用时应慎重考虑,确保单例对象的使用符合设计需求,并避免滥用导致代码的可测试性和可维护性下降。

原型模式

原型模式是一种设计模式,它通过复制现有对象的原型来创建新的对象。在前端开发中,原型模式常用于创建可复制的对象,以减少重复创建和初始化的开销。以下是一个前端的原型模式代码案例,附带注释说明:

// 前端原型模式案例

// 原型对象
class Prototype {
  constructor() {
    // 属性初始化
    // ...
  }

  // 克隆方法
  clone() {
    return Object.create(Object.getPrototypeOf(this));
  }
}

// 创建原型对象实例
const prototypeInstance = new Prototype();

// 克隆对象
const clonedInstance = prototypeInstance.clone();

// 使用克隆的对象
clonedInstance.someMethod();

在上述案例中,我们定义了一个Prototype类作为原型对象的实现。该类具有一些属性和方法,可以根据具体需求进行扩展。
通过调用clone() 方法,我们可以使用现有的原型对象创建一个新的对象。在该方法中,我们使用Object.create() 来克隆原型对象,它会创建一个新对象,并将原型对象设置为该新对象的原型。
通过克隆对象,我们可以在不重新初始化属性的情况下创建新的对象,以减少重复创建和初始化的开销。
需要注意的是,克隆的对象是浅拷贝的,即它们共享相同的原型。如果在克隆后的对象中修改了某些属性,将会影响到原型对象和其他克隆对象。如果需要深拷贝对象,可以根据具体情况使用工具库或手动实现深拷贝逻辑。
原型模式可以在前端开发中用于创建可复制的对象,例如在原型对象上定义一些通用的方法或属性,然后通过克隆来创建具有相同行为的新对象。这样可以节省资源和提高性能,特别适用于创建大量相似对象的情况。

代理模式

代理模式是一种设计模式,它通过引入代理对象来控制对实际对象的访问。在前端开发中,代理模式常用于实现缓存、权限控制、延迟加载等功能。以下是一个前端的代理模式代码案例,附带注释说明:

// 前端代理模式案例

// 实际对象
class RealObject {
  // 一些操作和方法
  someMethod() {
    // 方法逻辑
  }
}

// 代理对象
class ProxyObject {
  constructor() {
    this.realObject = new RealObject();
  }

  // 代理方法
  someMethod() {
    // 在调用实际对象方法之前可以执行一些前置操作
    // ...

    // 调用实际对象的方法
    this.realObject.someMethod();

    // 在调用实际对象方法之后可以执行一些后置操作
    // ...
  }
}

// 使用代理对象
const proxy = new ProxyObject();
proxy.someMethod();

在上述案例中,我们有一个实际对象RealObject和一个代理对象ProxyObject。代理对象包含一个对实际对象的引用,并通过代理方法来控制对实际对象的访问。
在代理对象的 someMethod() 方法中,我们可以在调用实际对象方法之前执行一些前置操作,然后调用实际对象的方法,最后在调用之后执行一些后置操作。这样,代理对象可以在不修改实际对象的情况下添加额外的功能或控制访问权限。
代理模式可以应用于许多场景,例如:

  • 缓存代理:在访问实际对象之前,代理对象先检查缓存中是否存在结果,并返回缓存的结果,从而减少重复计算或请求。
  • 虚拟代理:延迟加载大型资源,例如在需要时才加载并显示图片。
  • 安全代理:控制对实际对象的访问权限,例如检查用户权限或身份验证。
  • 日志代理:记录实际对象的方法调用日志,用于调试或监控。

代理模式可以提供更灵活和可扩展的对象访问控制,同时保持代码的清晰性和可维护性。根据具体的应用场景,可以根据需要自定义代理对象的行为和操作。

观察者模式

观察者模式是一种设计模式,它建立了对象之间的一对多依赖关系,当一个对象状态发生改变时,其依赖的所有对象都会收到通知并自动更新。在前端开发中,观察者模式常用于实现事件订阅/发布系统或组件间的通信。以下是一个前端的观察者模式代码案例,附带注释说明:

// 前端观察者模式案例

// 观察者
class Observer {
  update(data) {
    // 当被观察者状态发生变化时,触发的更新操作
    // ...
  }
}

// 被观察者
class Observable {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index !== -1) {
      this.observers.splice(index, 1);
    }
  }

  notifyObservers(data) {
    this.observers.forEach(observer => {
      observer.update(data);
    });
  }

  // 改变状态的操作
  changeState(data) {
    // 执行状态改变的逻辑
    // ...

    // 通知所有观察者
    this.notifyObservers(data);
  }
}

// 创建观察者实例
const observer1 = new Observer();
const observer2 = new Observer();

// 创建被观察者实例
const observable = new Observable();

// 添加观察者
observable.addObserver(observer1);
observable.addObserver(observer2);

// 改变状态,触发观察者更新
observable.changeState(data);

在上述案例中,我们有一个观察者类Observer和一个被观察者类Observable。观察者可以订阅被观察者的状态变化,并在状态变化时执行相应的操作。
被观察者类维护了一个观察者列表,并提供了添加观察者、移除观察者和通知观察者的方法。当被观察者的状态发生改变时,它会遍历观察者列表,调用每个观察者的update() 方法,将变化的数据传递给观察者进行处理。
在使用观察者模式时,我们需要创建观察者实例和被观察者实例,并将观察者添加到被观察者的观察者列表中。当被观察者的状态发生改变时,通过调用 changeState() 方法来触发观察者的更新操作。
观察者模式可以帮助实现松耦合的组件间通信,使得组件之间更加灵活和可维护。通过使用观察者模式,可以将事件的订阅和发布的责任分离,提高代码的可扩展性和可重用性。

策略模式

策略模式是一种设计模式,它定义了一系列可相互替换的算法,并使得算法的选择与使用者分离。在前端开发中,策略模式常用于处理不同的业务规则或算法,并根据需要动态地选择和应用适当的策略。以下是一个前端的策略模式代码案例,附带注释说明:

// 前端策略模式案例

// 策略接口
class Strategy {
  execute(data) {
    // 执行策略的逻辑
    // ...
  }
}

// 策略实现:具体策略1
class ConcreteStrategy1 extends Strategy {
  execute(data) {
    // 具体策略1的执行逻辑
    // ...
  }
}

// 策略实现:具体策略2
class ConcreteStrategy2 extends Strategy {
  execute(data) {
    // 具体策略2的执行逻辑
    // ...
  }
}

// 环境类
class Context {
  constructor(strategy) {
    this.strategy = strategy;
  }

  setStrategy(strategy) {
    this.strategy = strategy;
  }

  executeStrategy(data) {
    this.strategy.execute(data);
  }
}

// 创建策略实例
const strategy1 = new ConcreteStrategy1();
const strategy2 = new ConcreteStrategy2();

// 创建环境实例
const context = new Context(strategy1);

// 使用具体策略1执行
context.executeStrategy(data);

// 切换策略为具体策略2
context.setStrategy(strategy2);

// 使用具体策略2执行
context.executeStrategy(data);

在上述案例中,我们有一个策略接口Strategy,它定义了策略类的执行方法。然后我们有两个具体的策略实现ConcreteStrategy1ConcreteStrategy2,它们分别实现了策略接口的执行方法,并提供了不同的执行逻辑。
接下来,我们有一个环境类Context,它包含了一个策略对象,并通过setStrategy()方法来动态地设置当前使用的策略。环境类的executeStrategy() 方法用于执行当前策略的逻辑。
在使用策略模式时,我们首先创建具体的策略实例。然后创建环境实例,并通过构造函数或setStrategy() 方法将具体策略实例设置为当前的策略。最后,调用环境实例的executeStrategy() 方法来执行当前策略的逻辑。
策略模式的优点在于可以根据需要动态地切换和应用不同的策略,使得算法的选择与使用者分离,提高代码的灵活性和可扩展性。策略模式适用于处理一组相关的算法或业务规则,并且这些算法或规则可以相互替换的情况。

你可能感兴趣的:(前端,设计模式,javascript)