重构:我最喜欢的 6 种模式
作为一名开发人员,重构代码已经成为我最喜欢做的事情之一。它可以极大地提升代码的整洁度、可读性和可维护性。
在这篇文章中,我将概述我发现的 6 种非常有用的重构模式,并分别提供示例。其中许多模式都受到 Martin Fowler 的《重构》一书的启发,如果你想更好地理解常见的重构模式,我强烈推荐这本书。
(附注:良好的测试覆盖率也是重构的关键部分,但超出了本文的讨论范围。)
虽然示例是用 JavaScript 编写的,但每个模式都应该适用于任何编程语言。
6. 引入对象参数
当函数具有多个参数时,您会遇到一些问题:
- 为了使函数正常工作,需要保持参数的顺序。
- 传递给函数的参数名称(实际值)可能不一定与参数名称相同,这使得搜索某些类型的数据/逻辑变得困难。
- 添加/删除参数是一件苦差事;每次使用该函数都需要进行检查。
为了使函数参数更易于管理,此模式涉及将参数列表转换为单个对象。这强制所有函数的参数命名保持一致,并且参数顺序变得无关紧要。
// 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