您现在的位置是:网站首页 > 重构工具与设计模式转换文章详情
重构工具与设计模式转换
陈川
【
JavaScript
】
26808人已围观
14013字
重构工具与设计模式转换
重构工具与设计模式转换是提升代码质量的重要手段。当项目规模扩大或需求变更时,原有的代码结构可能变得难以维护,此时通过重构工具辅助设计模式转换能显著提高开发效率。JavaScript生态中有多种工具支持这一过程,结合设计模式的使用可以创建更灵活、可维护的代码结构。
重构工具的基本使用
现代IDE如VS Code、WebStorm等内置了强大的重构功能。以VS Code为例,其JavaScript/TypeScript支持提供了重命名符号、提取函数、提取变量等基础重构操作:
// 重构前
function calculateTotal(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i].price * items[i].quantity;
}
return total;
}
// 使用提取函数重构后
function calculateItemSubtotal(item) {
return item.price * item.quantity;
}
function calculateTotal(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
total += calculateItemSubtotal(items[i]);
}
return total;
}
ESLint等静态分析工具也能识别代码中的"坏味道",建议可能的重构方向。例如配置eslint-plugin-unicorn
可以检测出适合使用策略模式的条件分支:
// 重构前
function getSound(animal) {
if (animal === 'dog') return 'woof';
if (animal === 'cat') return 'meow';
if (animal === 'bird') return 'tweet';
throw new Error('Unknown animal');
}
// 策略模式重构后
const animalSounds = {
dog: () => 'woof',
cat: () => 'meow',
bird: () => 'tweet'
};
function getSound(animal) {
const soundMaker = animalSounds[animal];
if (!soundMaker) throw new Error('Unknown animal');
return soundMaker();
}
从过程式到面向对象的设计转换
当代码中存在大量全局函数和共享状态时,可以考虑转换为基于类的设计模式。重构工具可以帮助完成这种转换:
// 重构前
let counter = 0;
function increment() {
counter++;
}
function decrement() {
counter--;
}
// 使用重构工具转换为单例模式
class Counter {
static instance;
#count = 0;
constructor() {
if (Counter.instance) return Counter.instance;
Counter.instance = this;
}
increment() {
this.#count++;
}
decrement() {
this.#count--;
}
get count() {
return this.#count;
}
}
工厂模式的自动化重构
当遇到复杂的对象创建逻辑时,可以使用重构工具辅助转换为工厂模式。以下示例展示了如何将分散的对象创建统一管理:
// 重构前
function createAdminUser(name) {
const user = new User(name);
user.role = 'admin';
user.permissions = ['read', 'write', 'delete'];
return user;
}
function createGuestUser(name) {
const user = new User(name);
user.role = 'guest';
user.permissions = ['read'];
return user;
}
// 工厂模式重构后
class UserFactory {
static create(type, name) {
switch (type) {
case 'admin':
return new AdminUser(name);
case 'guest':
return new GuestUser(name);
default:
throw new Error('Invalid user type');
}
}
}
class AdminUser extends User {
constructor(name) {
super(name);
this.role = 'admin';
this.permissions = ['read', 'write', 'delete'];
}
}
观察者模式的重构实现
事件驱动的代码常常可以通过观察者模式进行重构。现代前端框架中的响应式系统大多基于此模式:
// 重构前
let data = { value: 0 };
const dependentFunctions = [];
function updateData(newValue) {
data.value = newValue;
dependentFunctions.forEach(fn => fn());
}
function watchData(fn) {
dependentFunctions.push(fn);
}
// 重构为通用观察者模式
class Observable {
constructor() {
this.observers = [];
}
subscribe(fn) {
this.observers.push(fn);
}
unsubscribe(fn) {
this.observers = this.observers.filter(observer => observer !== fn);
}
notify(data) {
this.observers.forEach(observer => observer(data));
}
}
class DataModel extends Observable {
#value = 0;
get value() {
return this.#value;
}
set value(newValue) {
this.#value = newValue;
this.notify(this.#value);
}
}
装饰器模式的应用转换
装饰器模式可以在不修改原有对象的情况下扩展功能。TypeScript的装饰器语法使这种模式更易实现:
// 重构前
class BasicCalculator {
add(a: number, b: number): number {
return a + b;
}
}
class LoggingCalculator {
calculator: BasicCalculator;
constructor(calculator: BasicCalculator) {
this.calculator = calculator;
}
add(a: number, b: number): number {
console.log(`Adding ${a} and ${b}`);
return this.calculator.add(a, b);
}
}
// 使用装饰器语法重构
function logOperation(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`Result: ${result}`);
return result;
};
return descriptor;
}
class Calculator {
@logOperation
add(a: number, b: number): number {
return a + b;
}
}
状态模式的重构过程
当对象行为随状态改变而改变时,状态模式可以消除大量的条件语句:
// 重构前
class Order {
state = 'pending';
next() {
if (this.state === 'pending') {
this.state = 'processing';
} else if (this.state === 'processing') {
this.state = 'shipped';
} else if (this.state === 'shipped') {
this.state = 'delivered';
}
}
cancel() {
if (this.state === 'pending' || this.state === 'processing') {
this.state = 'cancelled';
} else {
throw new Error('Cannot cancel order in current state');
}
}
}
// 状态模式重构后
class OrderState {
constructor(order) {
this.order = order;
}
next() {
throw new Error('Must be implemented by subclass');
}
cancel() {
throw new Error('Must be implemented by subclass');
}
}
class PendingState extends OrderState {
next() {
this.order.setState(new ProcessingState(this.order));
}
cancel() {
this.order.setState(new CancelledState(this.order));
}
}
class Order {
constructor() {
this.setState(new PendingState(this));
}
setState(state) {
this.state = state;
}
next() {
this.state.next();
}
cancel() {
this.state.cancel();
}
}
组合模式的重构示例
处理树形结构数据时,组合模式能提供一致的操作接口:
// 重构前
function renderMenu(menu) {
if (menu.items) {
let html = `<ul>`;
menu.items.forEach(item => {
html += renderMenu(item);
});
html += `</ul>`;
return html;
} else {
return `<li>${menu.name}</li>`;
}
}
// 组合模式重构后
class MenuComponent {
constructor(name) {
this.name = name;
}
add() {
throw new Error('Unsupported operation');
}
render() {
throw new Error('Must be implemented');
}
}
class MenuItem extends MenuComponent {
render() {
return `<li>${this.name}</li>`;
}
}
class Menu extends MenuComponent {
constructor(name) {
super(name);
this.children = [];
}
add(component) {
this.children.push(component);
}
render() {
return `
<ul>
${this.children.map(child => child.render()).join('')}
</ul>
`;
}
}
重构为模块模式
模块模式是JavaScript中常用的封装方式,重构工具可以帮助将分散的代码组织为模块:
// 重构前
const apiUrl = 'https://api.example.com';
let cache = {};
async function fetchUser(id) {
if (cache[id]) return cache[id];
const response = await fetch(`${apiUrl}/users/${id}`);
const user = await response.json();
cache[id] = user;
return user;
}
// 模块模式重构后
const UserService = (() => {
const apiUrl = 'https://api.example.com';
const cache = {};
async function fetchUser(id) {
if (cache[id]) return cache[id];
const response = await fetch(`${apiUrl}/users/${id}`);
const user = await response.json();
cache[id] = user;
return user;
}
return { fetchUser };
})();
命令模式的转换实现
将操作封装为对象可以实现撤销、重做等功能,重构工具可以帮助识别适合转换为命令模式的代码:
// 重构前
class TextEditor {
constructor() {
this.content = '';
}
insertText(text, position) {
this.content = this.content.slice(0, position) + text + this.content.slice(position);
}
deleteText(start, end) {
this.content = this.content.slice(0, start) + this.content.slice(end);
}
}
// 命令模式重构后
class Command {
execute() {
throw new Error('Must be implemented');
}
undo() {
throw new Error('Must be implemented');
}
}
class InsertCommand extends Command {
constructor(editor, text, position) {
super();
this.editor = editor;
this.text = text;
this.position = position;
this.previousContent = editor.content;
}
execute() {
this.editor.content = this.editor.content.slice(0, this.position) +
this.text +
this.editor.content.slice(this.position);
}
undo() {
this.editor.content = this.previousContent;
}
}
class TextEditor {
constructor() {
this.content = '';
this.commandHistory = [];
}
executeCommand(command) {
command.execute();
this.commandHistory.push(command);
}
undo() {
const command = this.commandHistory.pop();
if (command) command.undo();
}
}
适配器模式的重构应用
当接口不兼容时,适配器模式可以充当中间件。重构工具能帮助识别需要适配的场景:
// 重构前
class OldAPI {
request(data, callback) {
setTimeout(() => {
callback(null, { result: data });
}, 1000);
}
}
// 新代码期望使用Promise接口
async function newCodeNeedsPromiseAPI() {
// 直接使用OldAPI会导致代码不一致
}
// 适配器模式重构
class APIAdapter {
constructor(oldAPI) {
this.oldAPI = oldAPI;
}
async request(data) {
return new Promise((resolve, reject) => {
this.oldAPI.request(data, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
}
}
重构为代理模式
代理模式可以控制对原始对象的访问,重构工具能帮助识别需要添加控制逻辑的场景:
// 重构前
class ImageLoader {
constructor(url) {
this.url = url;
}
load() {
console.log(`Loading image from ${this.url}`);
return `Image data from ${this.url}`;
}
}
// 直接使用ImageLoader无法实现延迟加载
const images = [
new ImageLoader('image1.jpg'),
new ImageLoader('image2.jpg')
];
// 代理模式重构
class ImageProxy {
constructor(url) {
this.url = url;
this.realImage = null;
}
load() {
if (!this.realImage) {
this.realImage = new ImageLoader(this.url);
}
return this.realImage.load();
}
}
const images = [
new ImageProxy('image1.jpg'),
new ImageProxy('image2.jpg')
];
模板方法模式的重构
当多个算法有相似结构时,模板方法模式可以提取公共部分:
// 重构前
class ReportGenerator {
generatePDF(data) {
this.validateData(data);
const processed = this.processData(data);
const formatted = this.formatPDF(processed);
this.saveToFile(formatted, 'report.pdf');
}
generateHTML(data) {
this.validateData(data);
const processed = this.processData(data);
const formatted = this.formatHTML(processed);
this.saveToFile(formatted, 'report.html');
}
}
// 模板方法模式重构
class ReportGenerator {
generate(data, filename) {
this.validateData(data);
const processed = this.processData(data);
const formatted = this.format(processed);
this.saveToFile(formatted, filename);
}
format(data) {
throw new Error('Must be implemented by subclass');
}
}
class PDFReportGenerator extends ReportGenerator {
format(data) {
return `PDF formatted: ${JSON.stringify(data)}`;
}
}
class HTMLReportGenerator extends ReportGenerator {
format(data) {
return `<html><body>${JSON.stringify(data)}</body></html>`;
}
}
重构为享元模式
当系统存在大量相似对象时,享元模式可以共享相同部分以减少内存使用:
// 重构前
class Tree {
constructor(type, x, y) {
this.type = type;
this.x = x;
this.y = y;
this.texture = this.loadTexture(type); // 昂贵的操作
}
loadTexture(type) {
console.log(`Loading texture for ${type}`);
return `${type}_texture`;
}
render() {
console.log(`Rendering ${this.type} at (${this.x}, ${this.y})`);
}
}
// 创建1000棵树会重复加载纹理
const forest = [];
for (let i = 0; i < 1000; i++) {
forest.push(new Tree('Oak', Math.random() * 100, Math.random() * 100));
}
// 享元模式重构
class TreeType {
constructor(type) {
this.type = type;
this.texture = this.loadTexture(type);
}
loadTexture(type) {
console.log(`Loading texture for ${type}`);
return `${type}_texture`;
}
}
class TreeFactory {
static treeTypes = {};
static getTreeType(type) {
if (!this.treeTypes[type]) {
this.treeTypes[type] = new TreeType(type);
}
return this.treeTypes[type];
}
}
class Tree {
constructor(type, x, y) {
this.type = TreeFactory.getTreeType(type);
this.x = x;
this.y = y;
}
render() {
console.log(`Rendering ${this.type.type} at (${this.x}, ${this.y})`);
}
}
职责链模式的重构
当请求需要经过多个处理者时,职责链模式可以提供更灵活的解决方案:
// 重构前
function handleRequest(request) {
if (request.type === 'type1') {
return handleType1(request);
} else if (request.type === 'type2') {
return handleType2(request);
} else if (request.type === 'type3') {
return handleType3(request);
} else {
throw new Error('Unknown request type');
}
}
// 职责链模式重构
class Handler {
constructor() {
this.nextHandler = null;
}
setNext(handler) {
this.nextHandler = handler;
return handler;
}
handle(request) {
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
throw new Error('No handler found for request');
}
}
class Type1Handler extends Handler {
handle(request) {
if (request.type === 'type1') {
return `Handled type1: ${JSON.stringify(request)}`;
}
return super.handle(request);
}
}
const handlerChain = new Type1Handler()
.setNext(new Type2Handler())
.setNext(new Type3Handler());
function handleRequest(request) {
return handlerChain.handle(request);
}
备忘录模式的重构实现
备忘录模式可以捕获和恢复对象状态,重构工具能帮助识别需要状态保存的场景:
// 重构前
class Editor {
constructor() {
this.content = '';
this.history = [];
}
type(text) {
this.history.push(this.content);
this.content += text;
}
undo() {
if (this.history.length > 0) {
this.content = this.history.pop();
}
}
}
// 备忘录模式重构
class EditorMemento {
constructor(content) {
this.content = content;
}
}
class Editor {
constructor() {
this.content = '';
}
type(text) {
this.content += text;
}
save() {
return new EditorMemento(this.content);
}
restore(memento) {
this.content = memento.content;
}
}
class History {
constructor() {
this.states = [];
}
push(state) {
this.states.push(state);
}
pop() {
return this.states.pop();
}
}
const editor = new Editor();
const history = new History();
editor.type('Hello');
history.push(editor.save());
editor.type(' World');
editor.restore(history.pop());