重构:我最喜欢的 6 种模式

2025-05-24

重构:我最喜欢的 6 种模式

作为一名开发人员,重构代码已经成为我最喜欢做的事情之一。它可以极大地提升代码的整洁度、可读性和可维护性。

在这篇文章中,我将概述我发现的 6 种非常有用的重构模式,并分别提供示例。其中许多模式都受到 Martin Fowler 的《重构》一书的启发,如果你想更好地理解常见的重构模式,我强烈推荐这本书。

(附注:良好的测试覆盖率也是重构的关键部分,但超出了本文的讨论范围。)

虽然示例是用 JavaScript 编写的,但每个模式都应该适用于任何编程语言。

6. 引入对象参数

当函数具有多个参数时,您会遇到一些问题:

  1. 为了使函数正常工作,需要保持参数的顺序。
  2. 传递给函数的参数名称(实际值)可能不一定与参数名称相同,这使得搜索某些类型的数据/逻辑变得困难。
  3. 添加/删除参数是一件苦差事;每次使用该函数都需要进行检查。

为了使函数参数更易于管理,此模式涉及将参数列表转换为单个对象。这强制所有函数的参数命名保持一致,并且参数顺序变得无关紧要。

// Before

function sayHello(toName, punctuation, fromName) {
  return `Hello, ${toName}${punctuation} From, ${fromName}.`
} 

sayHello(customerName, end, myName);

// After

function sayHello({ toName, punctuation, fromName }) {
  return `Hello, ${toName}${punctuation} From, ${fromName}.`
} 

sayHello({ toName, punctuation, fromName });

5. 用表达式替换匿名函数

.map在 JavaScript 中,将匿名函数传递给数组方法(例如、.reduce或)是一种常见的做法.filter。我经常看到的一个问题是,这些匿名函数变得复杂且难以解析;而且由于函数没有名称,因此很难快速理解代码的意图。

相反,我发现将这些匿名函数提取到函数表达式中很有帮助,这使得理解意图变得更加容易(这也类似于“无点风格”又名“隐性编程”)。

// Before

const activeUsers = users.filter((user) => {
  if(user.lastPayment >= moment().startOf('week').toDate()) {
    return true;
  }

  return false;
});

// After

const activeUsers = users.filter(hasUserPaidThisWeek);

function hasUserPaidThisWeek(user) {
  if(user.lastPayment > moment().startOf('week').toDate() ) {
    return true;
  }

  return false;
}

4. 用对象代替原始类型

在许多编程语言中,使用字符串、数字或布尔值等原始值是一种常见的做法。但是,当围绕这些原始值的要求和/或规则变得更加复杂时,就会出现问题。

一种有用的做法是将这些原语包装在对象中,而不是使用不受控制的原始值,这将使您能够更好地控制如何使用和修改值。

// Before

let isLoading = true;
// some code...
loading = false;

const phone = '1 617 484-4049';

const price = 11;

// After

class LoadingStatus {
  constructor(initialStatus) {
    if(!this.statusSet.has(initialStatus)) {
      throw new Error('Invalid status');
    } 

    this._status = initialStatus;
  }

  statusSet = new Set(['loading', 'success', 'error', 'idle'])

  get status() {
    return this._status;
  }

  set status(status) {
    if(!this.statusSet.has(status)) {
      throw new Error('Invalid status');
    } 

    this._status = status;
  }
}

class Phone {
  constructor(phone) {
    this._phone = this.parsePhone(phone);
  }

  parsePhone(phone) {
    const trimmedPhone = phone.trim();

    if(phone.length !== 10) {
      throw new Error('Invalid phone format');
    }

    const areaCode = trimmedPhone.slice(0,3);
    const prefix = trimmedPhone.slice(3,7);
    const lineNumber = trimmedPhone.slice(7, 10);

    return { areaCode, prefix, lineNumber };
  }

  get areaCode() {
    return this._phone.areaCode;
  }

  get formatted() {
    const { areaCode, prefix, lineNumber } = this._phone;

    return `${areaCode} ${prefix}-${lineNumber}` 
  }

  ...
}

class Price {
  constructor(price) {
    if(typeof price !== 'string') {
      throw new Error('Invalid price');
    }

    if(!(price).match(/^[0-9]*$/)) {
      throw new Error('Invalid price');
    }

    this._price = price;
  }

  get price() {
    this._price;
  }
}

3. 分解条件

if/else在向程序中添加逻辑时,语句可能是一个强大的工具。但它们也可能很快变得笨重且令人困惑。解决这个问题的一种方法是将条件逻辑提取到描述意图的表达式中,使其更易于理解。

// Before

if(user.hasEmail() && user.subscriptions.includes('email')) {
  sendEmail(user);
}

// After

const isSubscribed = user.hasEmail() && user.subscriptions.includes('email');

if(isSubscribed) {
  sendEmail(user);
}

2.封装记录(桥接模式)

大多数情况下,构建软件涉及使用现有 API 和/或提供自己的 API。如果您的组件与其他 API 耦合,并且该 API 发生变化,您可能也需要更改您的组件;这有时会非常耗时。

我发现,与其耦合各种 API,不如为每个组件提供一个最符合其功能的 API,并在组件和与其交互的任何其他 API 之间添加一个层,这样会很有帮助。

封装记录重构模式提供了一种很好的方法。这种理念也与桥接模式相呼应,您可以在“设计模式:可复用面向对象软件的元素”中了解更多信息。

// Before

const user = {
  name: 'A Name', 
  favorites: { 
    color: 'blue',
    food: 'pizza'
  }
}

const UserComponent = (user) => (
  <div>Name: {user.name} - Food: {user.favorites.food}</div>
);

UserComponent(user);

// After

const user = {
  name: 'A Name', 
  favorites: { 
    color: 'blue',
    food: 'pizza'
  }
}

class User {
  constructor(user) {
    this._user = user;
  }

  get name() {
    return this._user.name;
  }

  get food() {
    return this._user.favorites.food;
  }
}

const UserComponent = ({ name, food }) => (
  <div>Name: {name} - Food: {food}</div>
);

UserComponent(new User(user));

1. 用多态性代替条件

这或许是我最喜欢的重构模式了。它曾多次帮助我简化了令人困惑的条件逻辑,使其更具可读性和可维护性。一旦将逻辑封装到对象中,你就可以灵活地利用其他 OOP 设计模式来实现你的目标。

这里的思路是,与其在代码中使用一堆嵌套的if语句,不如创建代表不同“类型”的对象,并为每个类型赋予负责执行特定操作的方法。然后,应用程序只需对每种类型调用相同的方法,并由类型决定以正确的方式执行操作。

// Before

if(user.favorites.food === 'pizza') {
  sendPizzaEmail(user);
}

if(user.favorites.food === 'ice cream') {
  sendIceCreamEmail(user);
}

// After

class PizzaUser {
  constructor(user) {
    this._user = user;
  }

  sendEmail() {
    sendPizzaEmail(this._user);
  }
}

class IceCreamUser {
  constructor(user) {
    this._user = user;
  }

  sendEmail() {
    sendIceCreamEmail(this._user);
  }
}

// this would create the appropriate user using the above classes
const user = getUser(userData); 

user.sendEmail()

就这样!重构愉快!

文章来源:https://dev.to/brycedooley/refactoring-my-6-favorite-patterns-p13
PREV
JavaScript 安全检查表摘要
NEXT
教程:如何使用 React Native、react-native-web 和 monorepo 在 iOS、Android 和 Web 之间共享代码