Clean Code Basic Notes
Basic Code Patterns
Software design is the art of managing dependencies and abstractions.
- Minimizing dependencies.
- Introduce fitting abstractions.
SOLID Principles
- Single Responsibility Principle: 单一功能原则.
- Open-closed Principle: 开闭原则.
- Liskov Substitution Principle: 里氏替换原则.
- Interface Segregation Principle: 接口隔离原则.
- Dependency Inversion Principle: 依赖反转原则.
- 单一职责是所有设计原则的基础.
- 开闭原则是设计的终极目标.
- 里氏替换原则强调的是子类替换父类后程序运行时的正确性, 它用来帮助实现开闭原则.
- 接口隔离原则用来帮助实现里氏替换原则, 同时它也体现了单一职责.
- 依赖倒置原则是过程式设计与面向对象设计的分水岭, 同时它也被用来指导接口隔离原则.
Single Responsibility Principle
Too much functionality is in one class and you modify a piece of it, it can be difficult to understand how that will affect other dependent modules:
- Singleton pattern.
- Decorator pattern.
- Proxy pattern.
- Iterator pattern.
- Visitor pattern.
// BAD
class Animal {
constructor(name) {
super(name)
}
getAnimalName() {
return this.name
}
saveAnimal(animal) {}
}
// GOOD
class Animal {
constructor(name) {
super(name)
}
getAnimalName() {
return this.name
}
}
class AnimalDB {
getAnimal(animal) {}
saveAnimal(animal) {}
}
异常处理视作单独职责,
抽离 try catch
代码块,
使之成为单独函数.
Open-Closed Principle
Allow users to add new functionalities without changing existing code, open for extension, close for modification:
- Polymorphism (多态性).
- Adapter pattern.
- Decorator pattern.
- Proxy pattern.
- Chain of responsibility pattern.
- Iterator pattern.
- Pub-Sub pattern.
- State pattern.
- Strategy pattern.
- Template Method Pattern.
class Coder {
constructor(fullName, language, hobby, education, workplace, position) {
this.fullName = fullName
this.language = language
this.hobby = hobby
this.education = education
this.workplace = workplace
this.position = position
}
}
// BAD: filter by any other new property have to change CodeFilter's code.
class CoderFilter {
filterByName(coders, fullName) {
return coders.filter(coder => coder.fullName === fullName)
}
filterByLang(coders, language) {
return coders.filter(coder => coder.language === language)
}
filterByHobby(coders, hobby) {
return coders.filter(coder => coder.hobby === hobby)
}
}
// GOOD
class CoderFilter {
filterByProp = (array, propName, value) =>
array.filter(element => element[propName] === value)
}
const animals: Array<Animal> = [new Animal('lion'), new Animal('mouse')]
function AnimalSound(a: Array<Animal>) {
for (let i = 0; i <= a.length; i++) {
if (a[i].name === 'lion')
log('roar')
if (a[i].name === 'mouse')
log('squeak')
}
}
AnimalSound(animals)
class Animal {
makeSound()
// ...
}
class Lion extends Animal {
makeSound() {
return 'roar'
}
}
class Squirrel extends Animal {
makeSound() {
return 'squeak'
}
}
class Snake extends Animal {
makeSound() {
return 'hiss'
}
}
function AnimalSound(a: Array<Animal>) {
for (let i = 0; i <= a.length; i++) log(a[i].makeSound())
}
AnimalSound(animals)
class Discount {
giveDiscount() {
if (this.customer === 'fav')
return this.price * 0.2
if (this.customer === 'vip')
return this.price * 0.4
}
}
class VIPDiscount extends Discount {
getDiscount() {
return super.getDiscount() * 2
}
}
class SuperVIPDiscount extends VIPDiscount {
getDiscount() {
return super.getDiscount() * 2
}
}
Liskov Substitution Principle
Objects of ParentType can be replaced with objects of SubType without altering. Altering shows that SubType should not be subtype of ParentType (break Open Closed Principle), you should re-design ParentType and SubType.
function AnimalLegCount(a: Array<Animal>) {
for (let i = 0; i <= a.length; i++) {
if (typeof a[i] === 'Lion') {
log(LionLegCount(a[i]));
}
if (typeof a[i] === 'Mouse') {
log(MouseLegCount(a[i]));
}
if (typeof a[i] === 'Snake') {
log(SnakeLegCount(a[i]));
}
}
}
AnimalLegCount(animals);
class Animal {
LegCount() {
return 2
}
}
class Lion extends Animal {
LegCount() {
return 4
}
}
function AnimalLegCount(a: Array<Animal>) {
for (let i = 0; i <= a.length; i++) a[i].LegCount()
}
AnimalLegCount(animals)
Interface Segregation Principle
- Make fine grained interfaces that are client specific.
- Clients should not be forced to depend upon interfaces that they do not use: 任何层次的软件设计如果依赖了它并不需要的东西, 就会带来意料之外的麻烦.
// BAD.
interface IShape {
drawCircle: () => any
drawSquare: () => any
drawRectangle: () => any
}
class Circle implements IShape {
drawCircle() {
// ...
}
drawSquare() {
// ...
}
drawRectangle() {
// ...
}
}
class Square implements IShape {
drawCircle() {
// ...
}
drawSquare() {
// ...
}
drawRectangle() {
// ...
}
}
class Rectangle implements IShape {
drawCircle() {
// ...
}
drawSquare() {
// ...
}
drawRectangle() {
// ...
}
}
// GOOD.
interface IShape {
draw: () => any
}
interface ICircle {
drawCircle: () => any
}
interface ISquare {
drawSquare: () => any
}
interface IRectangle {
drawRectangle: () => any
}
interface ITriangle {
drawTriangle: () => any
}
class Circle implements ICircle {
drawCircle() {
// ...
}
}
class Square implements ISquare {
drawSquare() {
// ...
}
}
class Rectangle implements IRectangle {
drawRectangle() {
// ...
}
}
class Triangle implements ITriangle {
drawTriangle() {
// ...
}
}
class CustomShape implements IShape {
draw() {
// ...
}
}
// GOOD.
class Circle implements IShape {
draw() {
// ...
}
}
class Triangle implements IShape {
draw() {
// ...
}
}
class Square implements IShape {
draw() {
// ...
}
}
class Rectangle implements IShape {
draw() {
// ...
}
}
Dependency Inversion Principle
Dependency should be on abstractions not concretions:
- High-level modules should not depend upon low-level modules. Both should depend upon abstractions
- Abstractions should not depend on details. Details should depend upon abstractions
- Pros:
- Loosely coupled modules.
- Better reusability.
- Better testability.
class XMLHttpService extends XMLHttpRequestService {}
class Http {
constructor(private xmlHttpService: XMLHttpService) {}
get(url: string, options: any) {
this.xmlHttpService.request(url, 'GET')
}
post() {
this.xmlHttpService.request(url, 'POST')
}
}
interface Connection {
request: (url: string, opts: any) => any
}
// Abstraction not upon on details (but upon on abstractions)
class Http {
constructor(private httpConnection: Connection) {}
get(url: string, options: any) {
this.httpConnection.request(url, 'GET')
}
post() {
this.httpConnection.request(url, 'POST')
}
}
class XMLHttpService implements Connection {
xhr = new XMLHttpRequest()
request(url: string, opts: any) {
xhr.open()
xhr.send()
}
}
class NodeHttpService implements Connection {
request(url: string, opts: any) {
// ...
}
}
class MockHttpService implements Connection {
request(url: string, opts: any) {
// ...
}
}
Least Knowledge Principle
最少知识原则 (Law of Demeter):
- 一个软件实体 (变量/对象/类/函数/模块/系统) 应当尽可能少地与其他实体发生相互作用.
- 若两个对象无需直接通信, 则两个对象不应直接相互联系, 引入一个第三者对象承担通信作用.
- Facade pattern.
- Mediator pattern.
Common Design Patterns
Patterns Classification
Creation Patterns
- Factory Method (工厂方法): 通过将数据和事件接口化来构建若干个子类.
- Abstract Factory (抽象工厂): 建立若干族类的一个实例, 这个实例不需要具体类的细节信息 (抽象类).
- Builder (建造者): 将对象的构建方法和其表现形式分离开来, 总是构建相同类型的对象.
- Prototype (原型): 一个完全初始化的实例, 用于拷贝或者克隆.
- Singleton (单例): 一个类只有唯一的一个实例, 这个实例在整个程序中有一个全局的访问点.
Structural Patterns
- Adapter (适配器模式): 将不同类的接口进行匹配与调整, 使得内部接口不兼容的类可以协同工作.
- Bridge (桥接模式): 将对象的接口从其实现中分离出来, 这样对象的实现和接口可以独立的变化.
- Composite (组合模式): 通过将简单可组合的对象组合起来, 构成一个完整的对象, 这个对象的能力将会超过这些组成部分的能力的总和, 产生新的能力.
- Decorator (装饰器): 动态给对象增加一些可替换的处理流程.
- Facade (外观模式): 一个类隐藏了内部子系统的复杂度, 只暴露出一些简单的接口.
- Flyweight (享元模式) 一个细粒度对象, 用于将包含在其它地方的信息 在不同对象之间高效地共享.
- Proxy (代理模式): 一个充当占位符的对象用来代表一个真实的对象.
Behavioral Patterns
- Chain of Responsibility (响应链): 一种将请求在一串对象中传递的方式, 寻找可以处理这个请求的对象.
- Command (命令): 封装命令请求为一个对象, 从而使记录日志, 队列缓存请求, 未处理请求进行错误处理 这些功能称为可能.
- Interpreter (解释器): 将语言元素包含在一个应用中的一种方式, 用于匹配目标语言的语法.
- Iterator (迭代器): 在不需要直到集合内部工作原理的情况下, 顺序访问一个集合里面的元素.
- Mediator (中介者模式): 在类之间定义简化的通信方式, 用于避免类之间显式的持有彼此的引用.
- Observer (观察者模式): 用于将变化通知给多个类的方式, 可以保证类之间的一致性.
- State (状态): 当对象状态改变时, 改变对象的行为.
- Strategy (策略): 将算法封装到类中, 将选择和实现分离开来.
- Template Method (模板方法): 在一个方法中为某个算法建立一层外壳, 将算法的具体步骤交付给子类去做.
- Visitor (访问者): 为类增加新的操作而不改变类本身.
Factory Method Pattern
Creating objects without specify exact object class: not calling a constructor directly.
Static Factory Method Pattern
CoordinateSystem = {
CARTESIAN: 0,
POLAR: 1,
}
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
static get factory() {
return new PointFactory()
}
}
class PointFactory {
static newCartesianPoint(x, y) {
return new Point(x, y)
}
static newPolarPoint(rho, theta) {
return new Point(rho * Math.cos(theta), rho * Math.sin(theta))
}
}
const point = PointFactory.newPolarPoint(5, Math.PI / 2)
const point2 = PointFactory.newCartesianPoint(5, 6)
Dynamic Factory Method Pattern
class Vehicle {
constructor({
type = 'vehicle',
state = 'brand new',
color = 'white',
speed = 0,
} = {}) {
this.type = type
this.state = state
this.color = color
this.speed = speed
}
run(...args) {
if (args.length === 0)
console.log(`${this.type} - run with: ${this.speed}km/s`)
else if (toString.apply(args[0]) === '[object Number]')
this.speed = args[0]
}
withColor(...args) {
if (args.length === 0)
console.log(`The color of this ${this.type} product is : ${this.color}`)
else if (toString.apply(args[0]) === '[object String]')
this.color = args[0]
}
reform(funcName, newFunc) {
if (
typeof this[funcName] === 'function'
|| typeof this.prototype[funcName] === 'function'
) {
delete this[funcName]
this.prototype[funcName] = newFunc
}
}
addFeature(funcName, newFunc) {
if (typeof this[funcName] === 'undefined') {
this[funcName] = newFunc
this.prototype[funcName] = newFunc
}
}
}
class Car extends Vehicle {
constructor({
type = 'car',
state = 'brand new',
color = 'silver',
speed = 10,
doors = 4,
} = {}) {
super({ type, state, color, speed })
this.doors = doors
}
}
class Truck extends Vehicle {
constructor({
type = 'truck',
state = 'used',
color = 'blue',
speed = 8,
wheelSize = 'large',
} = {}) {
super({ type, state, color, speed })
this.wheelSize = 'large'
}
}
class VehicleFactory {
constructor() {
this.VehicleClass = Car
}
createVehicle(options) {
switch (options.vehicleType) {
case 'car':
this.VehicleClass = Car
break
case 'truck':
this.VehicleClass = Truck
break
default:
break
}
return new this.VehicleClass(options)
}
}
class CarFactory extends VehicleFactory {
constructor() {
super()
this.VehicleClass = Car
}
}
class TruckFactory extends VehicleFactory {
constructor() {
super()
this.VehicleClass = Truck
}
}
const vehicleFactory = new VehicleFactory()
const car = vehicleFactory.createVehicle({
vehicleType: 'car',
color: 'yellow',
doors: 6,
})
const movingTruck = vehicleFactory.createVehicle({
vehicleType: 'truck',
state: 'like new',
color: 'red',
wheelSize: 'small',
})
const truckFactory = new TruckFactory()
const bigTruck = truckFactory.createVehicle({
state: 'bad.',
color: 'pink',
wheelSize: 'so big',
})
Asynchronous Factory Method Pattern
class DataContainer {
#data
#active = false
#init(data) {
this.#active = true
this.#data = data
return this
}
#check() {
if (!this.#active)
throw new TypeError('Not created by factory')
}
getData() {
this.#check()
return `DATA: ${this.#data}`
}
static async create() {
const data = await Promise.resolve('downloaded')
return new this().#init(data)
}
}
DataContainer.create().then(dc =>
assert.equal(dc.getData(), 'DATA: downloaded')
)
Abstract Factory Pattern
Encapsulate a group of individual factories that have a common theme without specifying their concrete classes.
class Drink {
consume() {}
}
class Tea extends Drink {
consume() {
console.log('This is Tea')
}
}
class Coffee extends Drink {
consume() {
console.log(`This is Coffee`)
}
}
class DrinkFactory {
prepare(amount) {}
}
class TeaFactory extends DrinkFactory {
makeTea() {
console.log(`Tea Created`)
return new Tea()
}
}
class CoffeeFactory extends DrinkFactory {
makeCoffee() {
console.log(`Coffee Created`)
return new Coffee()
}
}
const teaDrinkFactory = new TeaFactory()
const tea = teaDrinkFactory.makeTea()
tea.consume()
class AbstractVehicleFactory {
constructor() {
// Vehicle types
this.types = {}
}
getVehicle(type, customizations) {
const Vehicle = this.types[type]
return Vehicle ? new Vehicle(customizations) : null
}
registerVehicle(type, Vehicle) {
const proto = Vehicle.prototype
// Only register classes that fulfill the vehicle contract
if (proto.drive && proto.breakDown)
this.types[type] = Vehicle
return this
}
}
// Usage:
const abstractVehicleFactory = new AbstractVehicleFactory()
.registerVehicle('car', Car)
.registerVehicle('truck', Truck)
// Instantiate a new car based on the abstract vehicle type
const car = abstractVehicleFactory.getVehicle('car', {
color: 'lime green',
state: 'like new',
})
// Instantiate a new truck in a similar manner
const truck = abstractVehicleFactory.getVehicle('truck', {
wheelSize: 'medium',
color: 'neon yellow',
})
Builder Pattern
Flexible object creation with chain style calls.
class Person {
constructor() {
this.streetAddress = ''
this.postcode = ''
this.city = ''
this.companyName = ''
this.position = ''
this.annualIncome = 0
}
toString() {
return (
`Person lives at ${this.streetAddress}, ${this.city}, ${this.postcode}\n`
+ `and works at ${this.companyName} as a ${this.position} earning ${this.annualIncome}`
)
}
}
class PersonBuilder {
constructor(person = new Person()) {
this.person = person
}
get lives() {
return new PersonAddressBuilder(this.person)
}
get works() {
return new PersonJobBuilder(this.person)
}
build() {
return this.person
}
}
class PersonJobBuilder extends PersonBuilder {
constructor(person) {
super(person)
console.log('New')
}
at(companyName) {
this.person.companyName = companyName
return this
}
asA(position) {
this.person.position = position
return this
}
earning(annualIncome) {
this.person.annualIncome = annualIncome
return this
}
}
class PersonAddressBuilder extends PersonBuilder {
constructor(person) {
super(person)
console.log('New')
}
at(streetAddress) {
this.person.streetAddress = streetAddress
return this
}
withPostcode(postcode) {
this.person.postcode = postcode
return this
}
in(city) {
this.person.city = city
return this
}
}
const personBuilder = new PersonBuilder()
const person = personBuilder.lives
.at('ABC Road')
.in('Multan')
.withPostcode('66000')
.works
.at('Beijing')
.asA('Engineer')
.earning(10000)
.build()
Prototype Pattern
可以使用原型模式来减少创建新对象的成本:
Object.create()
.- Object shallow clone.
class Car {
constructor(name, model) {
this.name = name
this.model = model
}
SetName(name) {
console.log(`${name}`)
}
clone() {
return new Car(this.name, this.model)
}
}
const car = new Car()
car.SetName('Audi')
const car2 = car.clone()
car2.SetName('BMW')
Singleton Pattern
- Redux/VueX global store.
- Window 对象.
- 全局配置.
- 全局缓存.
- 线程池.
- 内存池.
Class Singleton Pattern
class Singleton {
constructor() {
const instance = this.constructor.instance
if (instance)
return instance
this.constructor.instance = this
}
say() {
console.log('Saying...')
}
}
class Singleton {
private static instance: Singleton
private constructor() {}
public static getInstance() {
if (!Singleton.instance)
Singleton.instance = new Singleton()
return Singleton.instance
}
someMethod() {}
}
const instance = Singleton.getInstance()
Closure Singleton Pattern
const createLoginLayer = (function (creator) {
let singleton
return function () {
if (!singleton)
singleton = creator()
return singleton
}
})(loginCreator)
Adapter Pattern
适配器通过内部使用新接口规定的属性/方法, 创建一个外观与旧接口一致的方法 (兼容旧代码):
old.method()
.adapter.method()
: 实现此 method 时, 使用了新接口规定的属性/方法.- 符合开放封闭原则.
- API adapter.
- 3rd-party code adapter.
- Legacy code adapter (version compatibility).
- Testing and mocking.
class Calculator1 {
constructor() {
this.operations = function (value1, value2, operation) {
switch (operation) {
case 'add':
return value1 + value2
case 'sub':
return value1 - value2
default:
throw new Error('Unsupported operations!')
}
}
}
}
class Calculator2 {
constructor() {
this.add = function (value1, value2) {
return value1 + value2
}
this.sub = function (value1, value2) {
return value1 - value2
}
}
}
class CalcAdapter {
constructor() {
const cal2 = new Calculator2()
this.operations = function (value1, value2, operation) {
switch (operation) {
case 'add':
return cal2.add(value1, value2)
case 'sub':
return cal2.sub(value1, value2)
default:
throw new Error('Unsupported operations!')
}
}
}
}
// old interface
function Shipping() {
this.request = function (zipStart, zipEnd, weight) {
// ...
return '$49.75'
}
}
// new interface
function AdvancedShipping() {
this.login = function (credentials) {
/* ... */
}
this.setStart = function (start) {
/* ... */
}
this.setDestination = function (destination) {
/* ... */
}
this.calculate = function (weight) {
return '$39.50'
}
}
// adapter interface
function AdapterShipping(credentials) {
const shipping = new AdvancedShipping()
shipping.login(credentials)
return {
request(zipStart, zipEnd, weight) {
shipping.setStart(zipStart)
shipping.setDestination(zipEnd)
return shipping.calculate(weight)
},
}
}
const shipping = new Shipping()
const adapterShipping = new AdapterShipping(credentials)
// original shipping object and interface
let cost = shipping.request('78701', '10010', '2 lbs')
log.add(`Old cost: ${cost}`)
// new shipping object with adapted interface
cost = adapter.request('78701', '10010', '2 lbs')
Bridge Pattern
Split large class or set of closely related classes into two separate hierarchies:
- 分离抽象和实现 (Separate abstracts and implements).
- 分离对象的两种不同属性.
e.g
从 2 个不同维度上扩展对象.
- Platform independence: e.g separate GUI frameworks from operating systems.
- Database drivers.
- Device drivers.
class VectorRenderer {
renderCircle(radius) {
console.log(`Drawing a circle of radius ${radius}`)
}
}
class RasterRenderer {
renderCircle(radius) {
console.log(`Drawing pixels for circle of radius ${radius}`)
}
}
class Shape {
constructor(renderer) {
this.renderer = renderer
}
}
class Circle extends Shape {
constructor(renderer, radius) {
super(renderer)
this.radius = radius
}
draw() {
this.renderer.renderCircle(this.radius)
}
resize(factor) {
this.radius *= factor
}
}
const raster = new RasterRenderer()
const vector = new VectorRenderer()
const circle = new Circle(vector, 5)
circle.draw()
circle.resize(2)
circle.draw()
Composite Pattern
- 封装: 组合模式将对象组合成树形结构, 以表示
部分+整体
的层次结构. - 多态: 组合模式通过对象的多态性表现, 使得用户对单个对象和组合对象的使用具有一致性.
- 扩展: 通过将简单可组合的对象组合起来, 构成一个完整的对象, 这个对象的能力将会超过这些组成部分的能力的总和, 产生新的能力.
- 组合模式是一种
HAS-A
(聚合) 的关系, 而不是IS-A
: 组合对象包含一组叶对象, 但 Leaf 并不是 Composite 的子类 (尽管称为父子节点). 组合对象把请求委托给它所包含的所有叶对象, 它们能够合作的关键是拥有相同的接口.
- Graphics and UI Frameworks: e.g DOM.
- File Systems: e.g directory tree.
- Organization Structures.
- AST.
Composite Pattern Implementation
树形结构:
- 根结点:
- Component 抽象对象/接口 采用最大宽接口,定义内点和叶点的操作.
- 将内点特有的操作集设为缺省操作集 (空实现).
- 内点:
- 持有父结点和子节点的引用 (可使用 Flyweight 模式实现共享).
- 操作集: 内点操作集 (可添加/删除组件).
- 叶点:
- 持有父结点引用.
- 操作集: 叶点操作集 (不可添加/删除组件).
interface Component {
add: (Component) => void
remove: (Component) => void
do: (Context) => void
}
class Composite implements Component {
add(child: Component) {
console.log('Add child')
}
remove(child: Component) {
console.log('Remove child')
}
do(context: Context) {
console.log('Do composite work')
for (const child of this.children) child.do(context)
}
}
class Leaf implements Component {
add(child: Component) {
throw new UnsupportedOperationException()
}
remove(child: Component) {
if (this.parent === null)
return
console.log('Remove self')
}
do(context: Context) {
console.log('Do leaf work')
}
}
const root = new Composite()
const c1 = new Composite()
const c2 = new Composite()
const leaf1 = new Leaf()
const leaf2 = new Leaf()
root.add(c1)
root.add(c2)
c1.add(leaf1)
c1.add(leaf2)
root.do()
Decorator Pattern
- 重写/重载/扩展对象原有的行为 (Methods), 但不改变对象原有属性.
- 可以添加新属性, 并围绕新属性扩展对象的原行为 e.g 原对象只会说中文, 装饰后同时说中文与英文.
- 避免了通过继承来为类型添加新职责, 通过继承的方式容易造成子类的膨胀.
- 保持接口的一致性, 动态改变对象的外观/职责.
- ConcreteDecorator 类:
private ClassName component
拥有一个对象引用. - 在 JS 中, 可以利用
Closure
(闭包) +Higher Order Function
(高阶函数) 快速实现装饰器模式. - 符合开放封闭原则和单一职责模式.
- React HOC Components.
- ES2016 and TypeScript
@decorator
. - Guard: form validator.
- Interceptor:
- HTTP request and response decorator.
- Web statistic tool.
- Transformer.
- AOP: Aspect Oriented Programming.
function __decorate(decorators, target) {
const decorateTarget = target
for (const decorator of decorators)
decorateTarget = decorator(decorateTarget) || decorateTarget
return decorateTarget
}
class MacBook {
constructor() {
this.cost = 997
this.screenSize = 11.6
}
getCost() {
return this.cost
}
getScreenSize() {
return this.screenSize
}
}
// Decorator 1
class Memory extends MacBook {
constructor(macBook) {
super()
this.macBook = macBook
}
getCost() {
return this.macBook.getCost() + 75
}
}
// Decorator 2
class Engraving extends MacBook {
constructor(macBook) {
super()
this.macBook = macBook
}
getCost() {
return this.macBook.getCost() + 200
}
}
// Decorator 3
class Insurance extends MacBook {
constructor(macBook) {
super()
this.macBook = macBook
}
getCost() {
return this.macBook.getCost() + 250
}
}
let mb = new MacBook()
mb = new Memory(mb)
mb = new Engraving(mb)
mb = new Insurance(mb)
console.log(mb.getCost())
// Outputs: 1522
console.log(mb.getScreenSize())
// Outputs: 11.6
Facade Pattern
封装复杂逻辑:
将多个复杂的子系统封装 + 合并
,
实现一个复杂功能,
但只暴露一个简单的接口.
class CPU {
freeze() {
console.log('Freezed....')
}
jump(position) {
console.log('Go....')
}
execute() {
console.log('Run....')
}
}
class Memory {
load(position, data) {
console.log('Load....')
}
}
class HardDrive {
read(lba, size) {
console.log('Read....')
}
}
class ComputerFacade {
constructor() {
this.processor = new CPU()
this.ram = new Memory()
this.hd = new HardDrive()
}
start() {
this.processor.freeze()
this.ram.load(
this.BOOT_ADDRESS,
this.hd.read(this.BOOT_SECTOR, this.SECTOR_SIZE)
)
this.processor.jump(this.BOOT_ADDRESS)
this.processor.execute()
}
}
const computer = new ComputerFacade()
computer.start()
sabertazimi.addMyEvent = function (el, ev, fn) {
if (el.addEventListener)
el.addEventListener(ev, fn, false)
else if (el.attachEvent)
el.attachEvent(`on${ev}`, fn)
else el[`on${ev}`] = fn
}
Flyweight Pattern
减小内存开销 (Performance Optimization):
- 内部信息: 对象中的内部方法所需信息/属性, 一个单独的享元可替代大量具有相同内在信息的对象.
- 外部状态: 作为方法参数, 使之适应不同的外部状态 (Context), 实例对象差异.
- 某个类型的对象有大量的实例, 对这些实例进行分类, 合并相同分类的对象, 只创建少量实例 (享元).
- 通过享元工厂来管理一组享元:
- 当所需享元已存在时, 返回已存在享元.
- 当所需享元不存在时, 创建新享元.
- Objects pool: e.g text processing.
- DOM nodes pool: e.g user interface.
- Event delegation.
- Reduce similar object instances.
- Caching.
Flyweight Factory Pattern
class Flyweight {
constructor(make, model, processor) {
this.make = make
this.model = model
this.processor = processor
}
}
class FlyweightFactory {
static flyweights = new Map()
static get(make, model, processor) {
const id = make + model
if (FlyweightFactory.flyweights.has(id))
return FlyweightFactory.flyweights.get(id)
const flyweight = new Flyweight(make, model, processor)
FlyweightFactory.flyweights.set(id, flyweight)
return flyweight
}
static getCount() {
return FlyweightFactory.flyweights.size
}
}
class Computer {
constructor(make, model, processor, memory, tag) {
this.flyweight = FlyweightFactory.get(make, model, processor)
this.memory = memory
this.tag = tag
this.getMake = function () {
return this.flyweight.make
}
}
}
class ComputerCollection {
computers = new Map()
add(make, model, processor, memory, tag) {
this.computers.set(tag, new Computer(make, model, processor, memory, tag))
}
get(tag) {
return this.computers.get(tag)
}
getCount() {
return this.computers.size
}
}
const computers = new ComputerCollection()
computers.add('Dell', 'Studio XPS', 'Intel', '5G', 'Y755P')
computers.add('Dell', 'Studio XPS', 'Intel', '6G', 'X997T')
computers.add('Dell', 'Studio XPS', 'Intel', '2G', 'NT777')
computers.add('Dell', 'Studio XPS', 'Intel', '2G', '0J88A')
computers.add('HP', 'Envy', 'Intel', '4G', 'CNU883701')
computers.add('HP', 'Envy', 'Intel', '2G', 'TXU003283')
console.log(`Computers: ${computers.getCount()}`) // 6.
console.log(`Flyweights: ${FlyweightFactory.getCount()}`) // 2.
Flyweight Pool Pattern
DOM pool:
class ObjectPool<T, P> {
objectFactory: () => T
objectPool: [T]
constructor(objectFactory: () => T) {
this.objectFactory = objectFactory
this.objectPool = []
}
create(...args: P) {
return objectPool.length === 0 ? objectFactory(args) : objectPool.shift()
}
recover(obj: T) {
objectPool.push(obj)
}
}
const iframeFactory = new ObjectPool(() => {
const iframe = document.createElement('iframe')
document.body.appendChild(iframe)
iframe.onload = function () {
iframe.onload = null
iframeFactory.recover(iframe)
}
return iframe
})
const iframe1 = iframeFactory.create()
iframe1.src = 'http:// baidu.com'
const iframe2 = iframeFactory.create()
iframe2.src = 'http:// QQ.com'
setTimeout(() => {
const iframe3 = iframeFactory.create()
iframe3.src = 'http:// 163.com'
}, 3000)
Proxy Pattern
通过一个代理对象:
- 临时存储原对象方法调用产生的一系列结果 (新建对象), 减少重复对象的产生.
- 代理对象可以为中间过程透明地增加额外逻辑: 预加载/缓存/合并/验证/转换等.
- 昂贵的对象创建或任务执行可以延迟到代理对象中执行.
- 代理类型: 远程/保护/虚拟/缓存代理.
- 符合开放封闭原则.
- 远程代理:
- 代理软件.
- 防火墙代理.
- 保护代理:
- 跨域处理.
- 路由保护代理.
- 虚拟代理:
- 图片预加载.
- 智能引用代理: C++ smart pointer, Rust
RefCell
, Linux file descriptor. - 写时拷贝代理: 内存页, DLL.
- 缓存代理:
- 缓存函数.
- 缓存服务器: React Query (cache data).
代理模式强调一种关系 (Proxy 与它的实体之间的关系), 这种关系可以静态地表达.
装饰者模式用于一开始不能确定对象的全部功能.
代理模式通常只有一层代理-本体
的引用,
装饰者模式经常会形成一条长长的装饰链.
class Percentage {
constructor(percent) {
this.percent = percent
}
toString() {
return `${this.percent}&`
}
valueOf() {
return this.percent / 100
}
}
const fivePercent = new Percentage(5)
console.log(fivePercent.toString())
console.log(`5% of 50 is ${50 * fivePercent}`)
function GeoCoder() {
this.getLatLng = function (address) {
if (address === 'Amsterdam')
return '52.3700° N, 4.8900° E'
else if (address === 'London')
return '51.5171° N, 0.1062° W'
else if (address === 'Paris')
return '48.8742° N, 2.3470° E'
else if (address === 'Berlin')
return '52.5233° N, 13.4127° E'
else return ''
}
}
function GeoProxy() {
const geocoder = new GeoCoder()
const geocache = {}
return {
getLatLng(address) {
if (!geocache[address])
geocache[address] = geocoder.getLatLng(address)
log.add(`${address}: ${geocache[address]}`)
return geocache[address]
},
getCount() {
let count = 0
for (const code in geocache) count++
return count
},
}
}
Proxy in Vue
:
const original = { name: 'jeff' }
const reactive = new Proxy(original, {
get(target, key) {
console.log('Tracking: ', key)
return target[key]
},
set(target, key, value) {
console.log('updating UI...')
return Reflect.set(target, key, value)
},
})
console.log(reactive.name) // 'Tracking: name'
reactive.name = 'bob' // 'updating UI...'
Chain of Responsibility Pattern
一种将请求在一串对象中传递的方式, 寻找可以处理这个请求的对象:
- 请求发送者只需知道链中的第一个节点, 从而降低发送者和一组接收者之间的强耦合.
- 请求发送者可以任意选择第一个节点, 从而减少请求在链中的传递次数.
- 职责链中的节点数量和顺序可以自由变化.
- 符合开放封闭原则.
- AOP: Aspect Oriented Programming.
- Middlewares:
- Redux.
- Express/Koa.
- NestJS.
- Request handling.
- Logging and error handling.
- Event handling: DOM event capture and bubble chian.
- Authorization and authentication.
- JavaScript Prototype chain.
- JavaScript Scope chain.
class Creature {
constructor(name, attack, defense) {
this.name = name
this.attack = attack
this.defense = defense
}
toString() {
return `${this.name} (${this.attack}/${this.defense})`
}
}
// Link Node.
class CreatureModifier {
constructor(creature) {
this.creature = creature
this.next = null
}
// Build chains.
add(modifier) {
if (this.next)
this.next.add(modifier)
else this.next = modifier
}
// Pass objects along to chains.
handle() {
if (this.next)
this.next.handle()
}
}
class NoBonusesModifier extends CreatureModifier {
constructor(creature) {
super(creature)
console.log('New')
}
handle() {
console.log('No bonuses for you!')
}
}
class DoubleAttackModifier extends CreatureModifier {
constructor(creature) {
super(creature)
console.log('New')
}
handle() {
console.log(`Doubling ${this.creature.name}'s attack`)
this.creature.attack *= 2
super.handle() // Call next();
}
}
class IncreaseDefenseModifier extends CreatureModifier {
constructor(creature) {
super(creature)
console.log('New')
}
handle() {
if (this.creature.attack <= 2) {
console.log(`Increasing ${this.creature.name}'s defense`)
this.creature.defense++
}
super.handle() // Call next();
}
}
const peekachu = new Creature('Peekachu', 1, 1)
console.log(peekachu.toString())
const root = new CreatureModifier(peekachu)
root.add(new DoubleAttackModifier(peekachu))
root.add(new IncreaseDefenseModifier(peekachu))
// Chain: creatureModifier -> doubleAttackModifier -> increaseDefenseModifier.
root.handle()
console.log(peekachu.toString())
import { Buffer } from 'node:buffer'
class Koa extends EventEmitter {
constructor() {
super()
this.middlewares = []
}
use(fn) {
this.middlewares.push(fn)
}
compose(middlewares, ctx) {
const dispatch = (index) => {
// End of chain.
if (index === middlewares.length)
return Promise.resolve()
// `next` function: call next middleware recursively.
const next = () => dispatch(index + 1)
// Call current middleware.
const middleware = middlewares[index]
return Promise.resolve(middleware(ctx, next))
}
return dispatch(0)
}
handleRequest(req, res) {
// When ctx.body doesn't change, statusCode contains '404'.
res.statusCode = 404
// Create context proxy for `req` and `res` operations.
const ctx = this.createContext(req, res)
// Middleware (open api for Koa users).
const fn = this.compose(this.middlewares, ctx)
fn.then(() => {
if (typeof ctx.body === 'object' && ctx.body !== null) {
res.setHeader('Content-Type', 'application/json;charset=utf8')
res.end(JSON.stringify(ctx.body))
} else if (ctx.body instanceof Stream) {
ctx.body.pipe(res)
} else if (typeof ctx.body === 'string' || Buffer.isBuffer(ctx.body)) {
res.setHeader('Content-Type', 'text/htmlCharset=utf8')
res.end(ctx.body)
} else {
res.end('Not Found')
}
}).catch((err) => {
this.emit('error', err)
res.statusCode = 500
res.end('Internal Server Error')
})
}
listen(...args) {
const server = http.createServer(this.handleRequest.bind(this))
server.listen(...args)
}
}
const app = new Koa()
app.use(async (ctx, next) => {
console.log(1)
await next()
console.log(2)
})
app.use(async (ctx, next) => {
console.log(3)
const p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('3.5')
resolve()
}, 1000)
})
await p.then()
await next()
console.log(4)
ctx.body = 'Hello Koa'
})
app.listen(2323, () => {
console.log('Koa server are listening to http://localhost:2323 ...')
})
Command Pattern
需要向某些对象发送请求:
- 解耦命令: 不清楚请求具体操作.
- 解耦接收者: 不清楚请求的接收者 (多个对 象中的某个随机对象).
此时希望用一种松耦合的方式来设计程序, 使得请求发送者和请求接收者能够消除彼此的耦合关系:
- 将方法/动作封装成对象, 使得外部通过唯一方法
execute()
/run()
调用内部方法/动作. - 解耦三者: 客户创建命令, 调用者执行该命令, 接收者在命令执行时执行相应操作.
- 客户通常被包装为一个命令对象.
- 调用者接过命令并将其保存下来, 它会在某个时候调用该命令对象的
Command.execute()
方法. - 调用者调用
Command.execute()
后, 最终将调用接收者方法Receiver.action()
.
- Decouple
Executor
andReceiver
. - GUI applications: bind
Command
to UI components. - Command sequences (store commands +
Composite
pattern) for batch processing:Macro
/Batch
/Undo
/Redo
feature. - Command queue (cache commands +
Observer
pattern) for transaction management:Redis
/RabbitMQ
/Kafka
.
在 JS 中, Closure
+ Callback
(Higher Order Function
) 可以实现隐式的命令模式:
Closure
捕获Receiver
(面向对象语言中,Command
对象需要持有Receiver
对象).Callback
函数实现具体逻辑 (面向对象语言中, 需要将其封装进Command.execute()
对象方法).
// Higher order function
const Command = receiver => () => receiver.action()
Bind Command
to UI components:
Executor
: UI components.Client
andReceiver
: background tasks or other UI components.Executor
->Client
Command.execute()
->Receiver.action()
: e.g clickbutton
-> refreshmenu
.
// Executor
class Button {
commands = new Set()
add(command) {
this.commands.set(command)
}
click() {
for (const command of this.commands) command.execute()
}
}
// Client: command object, `action` implemented
class Command {
constructor(receiver) {
this.receiver = receiver
}
execute() {
this.receiver.action()
}
}
// Receiver
class MenuBar {
action() {
this.refresh()
}
refresh() {
console.log('refresh menu pages')
}
}
const button = new Button()
const menuBar = new MenuBar()
const refreshMenuBarCommand = new Command(menuBar)
button.add(refreshMenuBarCommand)
button.click()
class MenuCommand {
constructor(action) {
this.action = action
}
execute() {
this.action()
}
}
// --------------
const appMenuBar = new MenuBar()
// --------------
const fileActions = new FileActions()
const EditActions = new EditActions()
const InsertActions = new InsertActions()
const HelpActions = new HelpActions()
// --------------
const openCommand = new MenuCommand(fileActions.open)
const closeCommand = new MenuCommand(fileActions.close)
const saveCommand = new MenuCommand(fileActions.save)
const saveAsCommand = new MenuCommand(fileActions.saveAs)
const fileMenu = new Menu('File')
fileMenu.add(new MenuItem('open', openCommand))
fileMenu.add(new MenuItem('Close', closeCommand))
fileMenu.add(new MenuItem('Save', saveCommand))
fileMenu.add(new MenuItem('Close', saveAsCommand))
appMenuBar.add(fileMenu)
// --------------
const cutCommand = new MenuCommand(EditActions.cut)
const copyCommand = new MenuCommand(EditActions.copy)
const pasteCommand = new MenuCommand(EditActions.paste)
const deleteCommand = new MenuCommand(EditActions.delete)
const editMenu = new Menu('Edit')
editMenu.add(new MenuItem('Cut', cutCommand))
editMenu.add(new MenuItem('Copy', copyCommand))
editMenu.add(new MenuItem('Paste', pasteCommand))
editMenu.add(new MenuItem('Delete', deleteCommand))
appMenuBar.add(editMenu)
// --------------
const textBlockCommand = new MenuCommand(InsertActions.textBlock)
const insertMenu = new Menu('Insert')
insertMenu.add(new MenuItem('Text Block', textBlockCommand))
appMenuBar.add(insertMenu)
// --------------
const showHelpCommand = new MenuCommand(HelpActions.showHelp())
const helpMenu = new Menu('Help')
helpMenu.add(new MenuItem('Show Help', showHelpCommand))
appMenuBar.add(helpMenu)
// --------------
document.getElementsByTagName('body')[0].appendChild(appMenuBar.getElement())
appMenuBar.show()
Command sequences to implement Macro
/Batch
/Undo
/Redo
feature:
class Cursor {
constructor(width, height, parent) {
this.commandStack = []
this.width = width
this.height = height
this.canvas = document.createElement('canvas')
this.canvas.width = this.width
this.canvas.height = this.height
parent.appendChild(this.canvas)
this.ctx = this.canvas.getContext('2d')
this.ctx.fillStyle = '#CCC000'
this.move(0, 0)
}
move(x, y) {
this.commandStack.push(() => {
// `this` point to `Cursor`.
this.lineTo(x, y)
})
}
lineTo(x, y) {
this.position.x += x
this.position.y += y
this.ctx.lineTo(this.position.x, this.position.y)
}
executeCommands() {
this.position = { x: this.width / 2, y: this.height / 2 }
this.ctx.clearRect(0, 0, this.width, this.height)
this.ctx.beginPath()
this.ctx.moveTo(this.position.x, this.position.y)
for (let i = 0; i < this.commandStack.length; i++) this.commandStack[i]()
this.ctx.stroke()
}
undo() {
this.commandStack.pop()
this.executeCommands()
}
}
Iterator Pattern
一个 Iterator 对象封装访问和遍历一个聚集对象中的各个构件的方法: