起
上周五面试官问我设计模式和设计原则,我没能答的很结构化,于是花了周末的时间梳理一下。虽然以前也做过比较口语化的相关的梳理。
设计模式是一种经过验证的、可重复使用的解决方案,用于解决特定的设计问题。它们分为三大类:创建型模式、结构型模式和行为型模式。每一种模式都提供了一种独特的方法来解决特定类型的问题。
设计原则是指导设计模式中应用的基本准则。可以使你进行程序设计时遵循最佳实践,确保程序代码的可读性、可维护性和扩展性。
设计原则剖析
单一职责原则 (SRP)
每一个「代码块儿」负责的功能要单一。
有关设计模式:
- 单例模式(Singleton)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 原型模式(Prototype)
- 适配器模式(Adapter)
- 装饰器模式(Decorator)
- 代理模式(Proxy)
- 组合模式(Composite)
- 外观模式(Facade)
- 享元模式(Flyweight)
- 桥接模式(Bridge)
- 策略模式(Strategy)
- 模板方法模式(Template Method)
- 责任链模式(Chain of Responsibility)
- 中介者模式(Mediator)
- 观察者模式(Observer)
- 访问者模式(Visitor)
- 备忘录模式(Memento)
- 状态模式(State)
- 解释器模式(Interpreter)
- 迭代器模式(Iterator)
- 命令模式(Command)
开放封闭原则 (OCP)
一个健壮的程序,它的扩展性会很不错,所以要对扩展开放。 一个优秀的程序,它的代码不应改来改去,所以要对修改封闭。
有关设计模式:
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 原型模式(Prototype)
- 适配器模式(Adapter)
- 装饰器模式(Decorator)
- 组合模式(Composite)
- 外观模式(Facade)
- 享元模式(Flyweight)
- 桥接模式(Bridge)
- 策略模式(Strategy)
- 模板方法模式(Template Method)
- 责任链模式(Chain of Responsibility)
- 观察者模式(Observer)
- 访问者模式(Visitor)
- 状态模式(State)
- 解释器模式(Interpreter)
- 命令模式(Command)
里氏替换原则 (LSP)
你实现的子程序可以替换父程序来进行扩展或切换全新的功能实现。
有关设计模式:
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 原型模式(Prototype)
- 装饰器模式(Decorator)
- 组合模式(Composite)
- 享元模式(Flyweight)
- 桥接模式(Bridge)
- 策略模式(Strategy)
- 模板方法模式(Template Method)
- 访问者模式(Visitor)
- 状态模式(State)
接口隔离原则 (ISP)
功能的设计,要根据具体场景进行划分,粒度要细一些,这样就可以像拼乐高积木一样,自由拼合。
有关设计模式:
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 策略模式(Strategy)
依赖倒置原则 (DIP)
面对复杂,先抽象、后具体。在设计阶段,具体细节的实现方式比较多变,而抽象相对来说较稳定。
有关设计模式:
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 装饰器模式(Decorator)
- 桥接模式(Bridge)
- 代理模式(Proxy)
- 中介者模式(Mediator)
- 观察者模式(Observer)
- 访问者模式(Visitor)
- 备忘录模式(Memento)
- 状态模式(State)
- 命令模式(Command)
迪米法则 (LOD) (最少知道原则)
强调功能的划分,从而减少功能与功能之间的耦合。减少模块之间的耦合,提高模块之间的独立性。
有关设计模式:
- 适配器模式(Adapter)
- 外观模式(Facade)
- 中介者模式(Mediator)
组合聚合复用原则 (CRP)
组合和聚合的方式相对继承而言,它们要灵活很多,想用什么就借用相应对象的功能即可,不需要关注该对象中不需要的功能。
有关设计模式:
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 原型模式(Prototype)
- 组合模式(Composite)
- 装饰器模式(Decorator)
- 桥接模式(Bridge)
不重复你自己 (DRY)
注重设计,重复代码写的越多改的也会越多,功能语义相同和代码逻辑重复的代码该合并封装的合并封装、该删除抽象的删除抽象。
有关设计模式:
- 单例模式(Singleton)
- 原型模式(Prototype)
- 享元模式(Flyweight)
- 桥接模式(Bridge)
- 模板方法模式(Template Method)
- 代理模式(Proxy)
尽量保持简单 (KISS)
做项目要考虑后续的可维护性,代码可读性好,是能够提升开发效率和提高可维护性的。 尽可能使用简单可读性高的方式去书写代码,不应该用逻辑重复、逻辑复杂、较偏门、可读性差的方式编写代码。「与防御型编程的设计原则完全相反」🐶
有关设计模式:
- 单例模式(Singleton)
- 适配器模式(Adapter)
- 装饰器模式(Decorator)
- 桥接模式(Bridge)
- 代理模式(Proxy)
- 外观模式(Facade)
- 策略模式(Strategy)
- 责任链模式(Chain of Responsibility)
- 中介者模式(Mediator)
- 观察者模式(Observer)
- 迭代器模式(Iterator)
- 命令模式(Command)
不过度设计 (YAGNI)
过度设计会增加一个程序的理解成本,如果这部分理解成本根本没必要,那么这样的设计就是浪费时间。
有关设计模式:
- 工厂方法模式(Factory Method)
- 装饰器模式(Decorator)
- 策略模式(Strategy)
- 模板方法模式(Template Method)
- 责任链模式(Chain of Responsibility)
- 代理模式(Proxy)
- 组合模式(Composite)
设计模式剖析
创建型模式:对象的创建和使用解耦了,灵活和多样的去创建对象,使得代码更加模块化和可扩展。
结构型模式:总结出了类、对象组合后的经典结构,将类、对象的结构和使用解耦了,简化代码的维护和扩展,灵活的借用对象,使它们的结构更加灵活和可扩展。
行为型模式:总结出了 类、对象之间的经典交互和职责分配方式,将类、对象的行为和使用解耦了,灵活的去使用对象的行为来完成特定场景下的功能,便于代码的维护和扩展。
1. 单例模式(Singleton)
示例:全局状态管理,如Redux Store的实例化。
class Store {
constructor() {
if (!Store.instance) {
this.state = {};
Store.instance = this;
}
return Store.instance;
}
}
const store = new Store();
有关设计原则:
- 单一职责原则 (SRP):只负责提供唯一实例。
- 不重复你自己 (DRY):确保全局唯一实例,避免重复创建。
- 尽量保持简单 (KISS):实现简单易懂,确保唯一实例。
2. 工厂方法模式(Factory Method)
示例:根据不同参数创建不同类型的组件实例。
class ButtonFactory {
createButton(type) {
if (type === 'Primary') return new PrimaryButton();
if (type === 'Secondary') return new SecondaryButton();
}
}
有关设计原则:
- 开放封闭原则 (OCP):可以通过扩展来添加新类型按钮。
- 依赖倒置原则 (DIP):依赖于抽象而不是具体类。
- 不过度设计 (YAGNI):只实现当前需要的按钮类型,避免过度设计。
3. 抽象工厂模式(Abstract Factory)
**示例 1 **:创建一组相关或依赖的对象,如创建一整套表单控件。
class FormControlFactory {
createTextInput() {
return new TextInput();
}
createCheckbox() {
return new Checkbox();
}
}
**示例 2 **:为不同平台(如 Web 和 Mobile)创建 UI 组件。
class WebUIFactory {
createButton() {
return new WebButton();
}
createModal() {
return new WebModal();
}
}
有关设计原则:
- 开放封闭原则 (OCP):可以通过扩展来添加新控件。
- 单一职责原则 (SRP):每个工厂类负责创建一组相关对象。
- 依赖倒置原则 (DIP):依赖于抽象接口而不是具体实现。
4. 建造者模式(Builder)
示例:逐步构建复杂的表单组件。
class FormBuilder {
constructor() {
this.form = new Form();
}
addTextInput(name) {
this.form.addInput(new TextInput(name));
return this;
}
addCheckbox(name) {
this.form.addInput(new Checkbox(name));
return this;
}
build() {
return this.form;
}
}
const form = new FormBuilder().addField('username', 'text').addField('password', 'password').build();
有关设计原则:
- 单一职责原则 (SRP):建造者类负责构建复杂对象。
- 开放封闭原则 (OCP):可以通过扩展建造者类来添加新组件。
- 组合聚合复用原则 (CRP):通过组合方式构建复杂对象。
5. 原型模式(Prototype)
示例:通过克隆已有对象来创建新对象,避免重复初始化。
const buttonPrototype = {
type: 'Default',
clone() {
return Object.assign({}, this);
}
};
const primaryButton = buttonPrototype.clone();
primaryButton.type = 'Primary';
有关设计原则:
- 单一职责原则 (SRP):原型类负责克隆自己。
- 不重复你自己 (DRY):通过克隆避免重复创建相同对象。
- 尽量保持简单 (KISS):克隆逻辑简单易懂。
6. 适配器模式(Adapter)
示例:适配不同API的数据格式,使其符合应用程序的数据接口。
class APIAdapter {
constructor(api) {
this.api = api;
}
getData() {
const rawData = this.api.fetchData();
return this.transformData(rawData);
}
transformData(data) {
// 转换数据格式
}
}
有关设计原则:
- 单一职责原则 (SRP):适配器类只负责数据转换。
- 不重复你自己 (DRY):数据转换逻辑集中在适配器中。
- 尽量保持简单 (KISS):转换逻辑简单易懂。
7. 装饰器模式(Decorator)
示例:为组件添加额外的功能,如在按钮上添加日志记录功能。
function logButtonClick(Component) {
return function WrappedComponent(props) {
const handleClick = () => {
console.log('Button clicked');
props.onClick();
};
return <Component {...props} onClick={handleClick} />;
};
}
有关设计原则:
- 开放封闭原则 (OCP):可以通过添加新装饰器扩展功能。
- 单一职责原则 (SRP):每个装饰器负责单一功能。
- 不过度设计 (YAGNI):只添加当前需要的装饰器。
8. 代理模式(Proxy)
示例:在数据请求前进行缓存或权限检查。
class APIProxy {
constructor(api) {
this.api = api;
this.cache = {};
}
fetchData(endpoint) {
if (!this.cache[endpoint]) {
this.cache[endpoint] = this.api.fetchData(endpoint);
}
return this.cache[endpoint];
}
}
有关设计原则:
- 单一职责原则 (SRP):代理类只负责缓存和权限检查。
- 不重复你自己 (DRY):缓存逻辑集中在代理类中。
- 尽量保持简单 (KISS):代理逻辑简单易懂。
9. 组合模式(Composite)
示例:处理树形结构的组件,如文件系统或菜单。
class MenuItem {
constructor(name) {
this.name = name;
this.children = [];
}
add(child) {
this.children.push(child);
}
display() {
console.log(this.name);
this.children.forEach(child => child.display());
}
}
有关设计原则:
- 组合聚合复用原则 (CRP):通过组合方式构建树形结构。
- 单一职责原则 (SRP):每个菜单项负责自己的显示和管理。
- 开放封闭原则 (OCP):可以通过扩展添加新类型的菜单项。
10. 外观模式(Facade)
示例 1:简化复杂的API调用,如简化DOM操作。
class DOMFacade {
static createElement(type, attributes) {
const element = document.createElement(type);
for (let key in attributes) {
element.setAttribute(key, attributes[key]);
}
return element;
}
}
示例 2:为复杂的操作提供简单的接口,如封装多个 API 调用。
class APIClient {
constructor() {
this.userAPI = new UserAPI();
this.orderAPI = new OrderAPI();
}
getUserOrder(userId) {
const user = this.userAPI.getUser(userId);
const orders = this.orderAPI.getOrders(userId);
return { user, orders };
}
}
有关设计原则:
- 单一职责原则 (SRP):外观类只负责简化接口。
- 尽量保持简单 (KISS):提供简单易用的接口。
- 不重复你自己 (DRY):统一复杂操作,避免重复代码。
11. 享元模式(Flyweight)
示例:在大批量创建对象时共享相同部分的数据,如图形绘制中的共享样式。
class Shape {
constructor(type) {
this.type = type;
}
}
const shapeFactory = (function() {
const shapes = {};
return {
getShape(type) {
if (!shapes[type]) {
shapes[type] = new Shape(type);
}
return shapes[type];
}
};
})();
示例:共享相同的数据对象,减少内存开销,如在表格中共享相同的样式对象。
class StyleFlyweight {
constructor() {
this.styles = {};
}
getStyle(style) {
if (!this.styles[style]) {
this.styles[style] = new Style(style);
}
return this.styles[style];
}
}
有关设计原则:
- 单一职责原则 (SRP):享元类负责共享对象。
- 不重复你自己 (DRY):通过共享避免重复创建相同对象。
- 尽量保持简单 (KISS):共享逻辑简单易懂。
12. 桥接模式(Bridge)
示例:在前端开发中使用桥接模式来分离视图与渲染逻辑,使得可以在不同的环境下使用不同的渲染方法。
// 渲染器接口
class Renderer {
render(content) {
throw new Error('Render method must be implemented');
}
}
// HTML 渲染器
class HTMLRenderer extends Renderer {
render(content) {
console.log(`<div>${content}</div>`);
}
}
// Markdown 渲染器
class MarkdownRenderer extends Renderer {
render(content) {
console.log(`**${content}**`);
}
}
// 抽象的视图
class View {
constructor(renderer) {
this.renderer = renderer;
}
display(content) {
this.renderer.render(content);
}
}
// 具体的视图实现
class ArticleView extends View {
display(content) {
console.log('Article View:');
super.display(content);
}
}
// 使用 HTML 渲染器
const htmlRenderer = new HTMLRenderer();
const articleViewWithHTML = new ArticleView(htmlRenderer);
articleViewWithHTML.display('Hello World');
// 使用 Markdown 渲染器
const markdownRenderer = new MarkdownRenderer();
const articleViewWithMarkdown = new ArticleView(markdownRenderer);
articleViewWithMarkdown.display('Hello World');
有关设计原则:
- 开放封闭原则 (OCP):可以通过添加新的渲染器扩展视图的渲染方式,而不修改现有代码。
- 单一职责原则 (SRP):视图类负责内容的显示逻辑,渲染器类负责具体的渲染实现。
- 依赖倒置原则 (DIP):视图类依赖于抽象的渲染器接口,而不是具体的渲染器实现。
13. 策略模式(Strategy)
示例:在表单验证中使用不同的验证策略。
class Validator {
constructor(strategy) {
this.strategy = strategy;
}
validate(value) {
return this.strategy(value);
}
}
const emailStrategy = (value) => /\S+@\S+\.\S+/.test(value);
const emailValidator = new Validator(emailStrategy);
有关设计原则:
- 开放封闭原则 (OCP):可以通过添加新策略扩展验证逻辑。
- 单一职责原则 (SRP):每个策略负责单一验证逻辑。
- 不过度设计 (YAGNI):只实现当前需要的验证策略。
14. 模板方法模式(Template Method)
示例 1:定义算法的骨架并将一些步骤延迟到子类实现,如表单提交处理。
class FormHandler {
submit() {
this.validate();
this.send();
this.confirm();
}
validate() {
// 子类实现
}
send() {
// 子类实现
}
confirm() {
// 子类实现
}
}
示例 2:在组件渲染中定义骨架流程,子组件实现具体细节。
class BaseComponent {
render() {
this.renderHeader();
this.renderBody();
this.renderFooter();
}
renderHeader() {
throw new Error('You have to implement the method renderHeader!');
}
renderBody() {
throw new Error('You have to implement the method renderBody!');
}
renderFooter() {
throw new Error('You have to implement the method renderFooter!');
}
}
class CustomComponent extends BaseComponent {
renderHeader() {
console.log('Rendering custom header');
}
renderBody() {
console.log('Rendering custom body');
}
renderFooter() {
console.log('Rendering custom footer');
}
}
有关设计原则:
- 单一职责原则 (SRP) :模板方法类负责算法骨架,具体实现由子类负责。
- 里氏替换原则 (LSP) :子类可以替换父类,实现其行为而不改变算法的整体结构。
- 不重复你自己 (DRY) :将通用的算法骨架定义在父类中,避免在子类中重复编写相同的逻辑。
- 开放封闭原则 (OCP) :可以通过扩展子类来添加新的行为,而无需修改已有的代码。
15. 责任链模式(Chain of Responsibility)
示例 1:事件处理链,如React中的事件传播机制。
class EventHandler {
setNext(handler) {
this.nextHandler = handler;
return handler;
}
handle(event) {
if (this.nextHandler) {
this.nextHandler.handle(event);
}
}
}
示例 2:在表单验证中设置多个验证步骤,逐个执行。
class Validator {
setNext(validator) {
this.nextValidator = validator;
return validator;
}
validate(request) {
if (this.nextValidator) {
return this.nextValidator.validate(request);
}
return true;
}
}
class EmailValidator extends Validator {
validate(request) {
if (!/\S+@\S+\.\S+/.test(request.email)) {
return false;
}
return super.validate(request);
}
}
class PasswordValidator extends Validator {
validate(request) {
if (request.password.length < 6) {
return false;
}
return super.validate(request);
}
}
const emailValidator = new EmailValidator();
const passwordValidator = new PasswordValidator();
emailValidator.setNext(passwordValidator);
const isValid = emailValidator.validate({ email: 'test@example.com', password: 'password123' });
有关设计原则:
- 单一职责原则 (SRP):每个处理器验证器负责处理自己的事件。
- 开闭原则 (OCP):可以通过添加新处理器验证器扩展事件处理链。
- 尽量保持简单 (KISS):处理链逻辑简单易懂。
16. 观察者模式(Observer)
示例:数据变化通知,如React的状态管理。
class Observable {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
有关设计原则:
- 单一职责原则 (SRP):被观察者负责通知变化。
- 开闭原则 (OCP):可以通过添加新观察者扩展通知逻辑。
- 尽量保持简单 (KISS):通知逻辑简单易懂。
17. 访问者模式(Visitor)
示例:操作复杂数据结构,如在 DOM 树上执行操作。
// 访问者接口
class Visitor {
visit(element) {
if (element instanceof DivElement) {
this.visitDiv(element);
} else if (element instanceof SpanElement) {
this.visitSpan(element);
}
}
visitDiv(element) {
// 处理 Div 元素
console.log(`Visiting Div: ${element.id}`);
}
visitSpan(element) {
// 处理 Span 元素
console.log(`Visiting Span: ${element.id}`);
}
}
// 元素接口
class Element {
accept(visitor) {
throw new Error('accept method must be implemented');
}
}
// 具体元素
class DivElement extends Element {
constructor(id) {
super();
this.id = id;
}
accept(visitor) {
visitor.visitDiv(this);
}
}
class SpanElement extends Element {
constructor(id) {
super();
this.id = id;
}
accept(visitor) {
visitor.visitSpan(this);
}
}
// 使用访问者模式
const elements = [new DivElement('div1'), new SpanElement('span1')];
const visitor = new Visitor();
elements.forEach(element => {
element.accept(visitor);
});
有关设计原则:
- 单一职责原则 (SRP):访问者类负责执行操作,将操作逻辑与数据结构分离。
- 开闭原则 (OCP):可以通过添加新访问者来扩展操作,而不修改现有的元素类。
- 里氏替换原则 (LSP):不同元素可以被相同操作访问,元素类的行为一致。
- 依赖倒置原则 (DIP):高层模块(操作逻辑)不应该依赖低层模块(具体元素实现),它们都应该依赖抽象(访问者接口和元素接口)。
18. 备忘录模式(Memento)
示例:保存和恢复对象的状态,如表单的撤销操作。
class Form {
constructor() {
this.state = {};
}
save() {
return { ...this.state };
}
restore(state) {
this.state = state;
}
}
有关设计原则:
- 单一职责原则 (SRP):备忘录类负责保存和恢复状态。
- 依赖倒置原则 (DIP):依赖于备忘录接口而不是具体实现。
- 不重复你自己 (DRY):通过备忘录避免重复实现保存和恢复逻辑。
19. 状态模式(State)
示例:根据状态变化执行不同的行为,如表单的验证状态。
class Form {
constructor() {
this.state = new InitialState(this);
}
setState(state) {
this.state = state;
}
submit() {
this.state.submit();
}
}
class InitialState {
constructor(form) {
this.form = form;
}
submit() {
// 初始状态下的提交逻辑
}
}
class ValidatedState {
constructor(form) {
this.form = form;
}
submit() {
// 验证通过后的提交逻辑
}
}
有关设计原则:
- 单一职责原则 (SRP):每个状态类负责处理自己的行为。
- 里氏替换原则 (LSP):不同状态可以替换彼此,确保行为一致。
- 尽量保持简单 (KISS):状态转换逻辑简单易懂。
20. 解释器模式(Interpreter)
示例:解析和执行DSL(领域特定语言),如模板引擎。
class Interpreter {
interpret(context) {
const expressions = context.split(' ');
expressions.forEach(expression => {
// 解析并执行表达式
});
}
}
有关设计原则:
- 单一职责原则 (SRP):解释器类负责解析和执行表达式。
- 开闭原则 (OCP):可以通过扩展解析新表达式。
- 尽量保持简单 (KISS):解析逻辑简单易懂。
21. 迭代器模式(Iterator)
示例:遍历集合,如遍历DOM节点或数组。
class Iterator {
constructor(collection) {
this.collection = collection;
this.index = 0;
}
next() {
return this.collection[this.index++];
}
hasNext() {
return this.index < this.collection.length;
}
}
有关设计原则:
- 单一职责原则 (SRP):迭代器类负责遍历集合。
- 开闭原则 (OCP):可以通过扩展支持新集合类型。
- 尽量保持简单 (KISS):遍历逻辑简单易懂。
22. 命令模式(Command)
示例:将请求封装为对象,如按钮点击事件的处理。
class Command {
execute() {
// 执行命令
}
}
class ClickCommand extends Command {
execute() {
console.log('Button clicked');
}
}
有关设计原则:
- 单一职责原则 (SRP):命令类负责封装请求。
- 开闭原则 (OCP):可以通过扩展添加新命令。
- 依赖倒置原则 (DIP):依赖于命令接口而不是具体实现。
23. 中介者模式(Mediator)
示例:组件间通信的中心,如Redux中的store。
class EventBus {
constructor() {
this.listeners = {};
}
subscribe(event, listener) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(listener);
}
publish(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach(listener => listener(data));
}
}
}
有关设计原则:
- 单一职责原则 (SRP):中介者类负责协调对象之间的通信。
- 迪米特法则 (LOD):通过中介者减少对象间的直接依赖。
- 依赖倒置原则 (DIP):依赖于中介者接口而不是具体实现。
终
通过设计模式和设计原则可以创建高效、可维护和扩展性强的前端应用。在前端开发中也提供了许多有价值的方法来组织和管理代码。应用这些模式和原则,理解这些模式的基本原理和适用场景,才能在实际开发中灵活运用,最终可以显著提升代码质量和开发效率。