JavaScript中的设计模式之三--外观模式(门面模式)

处理设计模式时,最重要的是利用它们来改进我们的代码库。这意味着我们有时可以使它们适应我们的需求。外观模式通常与面向对象编程联系在一起。这并不能改变我们可以将其基本原理应用于其他事物的事实。在本文中,我们将通过常规设置和 React 钩子中探索外观模式。

外观模式的基本原理

外观模式旨在通过创建单个 API 来提供一种与多个组件交互的简化方式。通过屏蔽底层交互,它可以帮助我们保持代码更具可读性。立面模式还可以帮助我们将通用功能分组到更具体的上下文中。当我们的系统非常复杂并且我们可以看到与它交互的方式中的一些模式时,它特别有用。

假设我们有一组要使用的类和方法。

javascript 复制代码
class Bed {
  makeTheBed() {
    console.log('The bed is ready');
  }
}
class AirFreshener {
  spray() {
    console.log('A nice smell spreads through the air')
  }
}
class TrashCan {
  takeOutTrash() {
    console.log('The trash is taken out')
  }
}
class Dishwasher {
  fill() {
    console.log('The dishwasher is filled');
  }
  wash() {
    console.log('The dishwasher is working');
    return new Promise((resolve) => {
      resolve();
    });
  }
  empty() {
    console.log('The dishwasher is empty');
  }
}

我们可能经常会发现自己想要以特定的顺序执行上述方法。在我们的代码库中多次这样做会违反Don 't Repeat Yourself (DRY) 原则。为了避免上述情况,我们可以创建一个Facade类。它可以跟踪依赖关系并以特定的顺序执行我们的方法。

javascript 复制代码
class HouseCleaningFacade {
  constructor(bed, trashCan, airFreshener, dishwasher) {
    this.bed = bed;
    this.trashCan = trashCan;
    this.airFreshener = airFreshener;
    this.dishwasher = dishwasher;
  }
  cleanTheHouse() {
    this.bed.makeTheBed();
    this.trashCan.takeOutTrash();
    this.airFreshener.spray();
    this.dishwasher.fill();
    this.dishwasher.wash()
      .then(this.dishwasher.empty)
  }
}

现在我们有了一种简单的方法来使用一组包含许多不同部分的更复杂的操作。我们可以直接使用它,并保持我们的应用程序更具可读性。

javascript 复制代码
const houseCleaning = new HouseCleaningFacade(
  new Bed(),
  new TrashCan(),
  new AirFreshener(),
  new Dishwasher()
);
houseCleaning.cleanTheHouse();

与单独执行每个方法相比,它有局限性,但它在整个应用程序中提供了一致性。如果在某个时候我们决定更改Facade中的逻辑,它将影响我们使用它的应用程序的每个部分。

在第三方功能中使用外观模式

使用Facade与第三方库或一些复杂的本地功能进行交互也是一种常见的方法。上面的一个很好的例子是Fetch API。与axios等库相比,它可以被认为是低级别的。让我们创建一个简化的界面来处理它的一些不足之处。我们想要:

  • 自动注入认证令牌
  • 解析响应的主体,这样我们就不需要每次都调用response.json()
  • 如果请求不成功,抛出错误
javascript 复制代码
class API {
  constructor(authToken) {
    this.authToken = authToken;
  }
  constructHeaders() {
    const headers = new Headers();
    headers.set('Authorization', this.authToken);
    return headers;
  }
  handleResponse(response) {
    if (response.ok) {
      return response.json();
    } else {
      return Promise.reject({
        status: response.status,
        statusText: response.statusText
      });
    }
  }
  get(url, options) {
    return fetch(url, {
      headers: this.constructHeaders(),
      ...options,
    })
      .then(this.handleResponse);
  }
  post(url, options) {
    return fetch(url, {
      method: 'POST',
      headers: this.constructHeaders(),
      ...options,
    })
      .then(this.handleResponse);
  }
  put(url, options) {
    return fetch(url, {
      method: 'PUT',
      headers: this.constructHeaders(),
      ...options,
    })
      .then(this.handleResponse);
  }
  delete(url, options) {
    return fetch(url, {
      method: 'DELETE',
      headers: this.constructHeaders(),
      ...options,
    })
      .then(this.handleResponse);
  }
}

现在我们有了一个用额外的逻辑包装现有原生功能的类。如果你需要一些额外的功能,或者需要根据你使用的API调整它,你可以轻松地扩展它。

javascript 复制代码
const api = new API('my-auth-token');
api.get('https://jsonplaceholder.typicode.com/users/1')
  .then(data => {
    console.log('User data', data);
  })
  .catch(error => {
    console.error(error);
  });

将外观模式应用于React钩子(React Hooks)

尽管外观模式通常在使用类时被引用,但我们可以在设计自定义React Hooks时使用它的原则。首先,我们来看一个可能需要重构的例子。然后,我们将尝试通过应用外观模式来修复它。

来看一个需要重构的例子:

javascript 复制代码
import React, { useState } from 'react';
import AddUserModal from './AddUserModal';
import UsersTable from './UsersTable';
const Users = () => {
  const [users, setUsers] = useState([]);
  const [isAddUserModalOpened, setAddUserModalVisibility] = useState(false);
  function openAddUserModal() {
    setAddUserModalVisibility(true);
  }
  function closeAddUserModal() {
    setAddUserModalVisibility(false);
  }
  function addUser(user) {
    setUsers([
      ...users,
      user
    ])
  }
  function deleteUser(userId) {
    const userIndex = users.findIndex(user => user.id === userId);
    if (userIndex > -1) {
      const newUsers = [...users];
      newUsers.splice(userIndex, 1);
      setUsers(
        newUsers
      );
    }
  }
  return (
    <>
      <button onClick={openAddUserModal}>Add user</button>
      <UsersTable
        users={users}
        onDelete={deleteUser}
      />
      <AddUserModal
        isOpened={isAddUserModalOpened}
        onClose={closeAddUserModal}
        onAddUser={addUser}
      />
    </>
  )
};
export default Users;

我们在上面注意到的第一件事是许多不同的hooks。它的可读性不高,当然也不能重用。尽管useState钩子非常通用,但我们将它放在特定的上下文中。所有hooks结合起来,为我们提供了管理用户表的可能性。综上所述,这是一个使用外观模式的一些原则的完美地方。

我们可以轻松地将上述功能分组为自定义hooks:

javascript 复制代码
function useUsersManagement() {
  const [users, setUsers] = useState([]);
  function addUser(user) {
    setUsers([
      ...users,
      user
    ])
  }
  function deleteUser(userId) {
    const userIndex = users.findIndex(user => user.id === userId);
    if (userIndex > -1) {
      const newUsers = [...users];
      newUsers.splice(userIndex, 1);
      setUsers(
        newUsers
      );
    }
  }
  return {
    users,
    addUser,
    deleteUser
  }
}
javascript 复制代码
function useAddUserModalManagement() {
  const [isAddUserModalOpened, setAddUserModalVisibility] = useState(false);
  function openAddUserModal() {
    setAddUserModalVisibility(true);
  }
  function closeAddUserModal() {
    setAddUserModalVisibility(false);
  }
  return {
    isAddUserModalOpened,
    openAddUserModal,
    closeAddUserModal
  }
}

唯一剩下的就是使用新创建的Facade了。通过这样做,我们使组件更轻。

我们还通过将逻辑移出组件使其更具可测试性。如今,像react-testing-library这样的包有一组用于测试hooks的工具

javascript 复制代码
import React from 'react';
import AddUserModal from './AddUserModal';
import UsersTable from './UsersTable';
import useUsersManagement from "./useUsersManagement";
import useAddUserModalManagement from "./useAddUserModalManagement";
const Users = () => {
  const {
    users,
    addUser,
    deleteUser
  } = useUsersManagement();
  const {
    isAddUserModalOpened,
    openAddUserModal,
    closeAddUserModal
  } = useAddUserModalManagement();
  return (
    <>
      <button onClick={openAddUserModal}>Add user</button>
      <UsersTable
        users={users}
        onDelete={deleteUser}
      />
      <AddUserModal
        isOpened={isAddUserModalOpened}
        onClose={closeAddUserModal}
        onAddUser={addUser}
      />
    </>
  )
};
export default Users;

以上这些在很多方面帮助了我们。它使我们的代码高度可重用,遵循DRY原则。我们也可以轻松地修改它。如果我们决定实现一些状态管理,例如Redux,我们只需要调整我们的自定义hooks。将组件的逻辑放在Facade之后还可以大大简化代码,使其更具可读性。

总结

外观模式被证明是非常有用的。有了它,我们可以使我们的代码更干净,更可重用。在本文中,我们已经了解了外观模式的基本概念。我们还写了一些例子来说明它可能是有用的。除了常规使用外,我们还在React Hooks中实现了外观模式的概念。通过上述操作,我们已经证明,如果我们想提高代码质量,外观模式是一个值得研究的概念。

相关推荐
前端小趴菜0518 分钟前
React-React.memo-props比较机制
前端·javascript·react.js
RadiumAg3 小时前
记一道有趣的面试题
前端·javascript
yangzhi_emo3 小时前
ES6笔记2
开发语言·前端·javascript
yanlele3 小时前
我用爬虫抓取了 25 年 5 月掘金热门面试文章
前端·javascript·面试
收破烂的小熊猫~4 小时前
《Java修仙传:从凡胎到码帝》第四章:设计模式破万法
java·开发语言·设计模式
烛阴5 小时前
void 0 的奥秘:解锁 JavaScript 中 undefined 的正确打开方式
前端·javascript
初遇你时动了情5 小时前
腾讯地图 vue3 使用 封装 地图组件
javascript·vue.js·腾讯地图
dssxyz5 小时前
uniapp打包微信小程序主包过大问题_uniapp 微信小程序时主包太大和vendor.js过大
javascript·微信小程序·uni-app
佛祖让我来巡山7 小时前
【工厂和策略设计模式妙用】解决接口选择与多重if-else 问题
设计模式·策略模式·工厂模式
ohMyGod_1237 小时前
React16,17,18,19新特性更新对比
前端·javascript·react.js