设计模式原则
设计模式遵循SOLID原则,分别是:
- 单一职责原则
- 开放封闭原则
- 里式替换原则
- 接口依赖原则
- 依赖反转原则
- 最少知识原则
开放封闭原则
开放封闭原则指的就是对扩展开放,对修改关闭。编写代码的时候不可避免地会碰到修改的情况,而遵循开闭原则就意味着当代码需要修改时,可以通过编写新的代码来扩展已有的代码,而不是直接修改已有代码本身。
下面的伪代码是一个常见的表单校验功能,校验内容包括用户名、密码、验证码,每个校验项都通过判断语句 if-else 来控制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function validate(){ if(!username){ ... }else{ ... } if(!pswd){ ... }else{ ... } if(!captcha){ ... }else{ ... } }
|
这么写看似没有问题,但是可扩展性不好,如果此时增加一个校验条件,就要修改validate()函数内容。
可以将校验规则抽出来,实现共同的接口IValidateHandler,同时将函数validate()改成Validation类,通过addValidateHandler()函数添加校验规则,通过validate()函数校验表单。这样每当有新的校验规则出现时,只要实现IValidateHandler接口并调用addValidateHandler()函数即可,不需要修改Validation的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| interface IValidateHandler { validate(): boolean } class Validation { private validateHandlers: ValidateHandler[] = [] public addValidateHandler(handler: IValidateHandler){ this.validateHandlers.push(handler) } public validate(){ for(let i = 0;i < this.validateHandlers.length;i++){ this.validateHandlers[i].validate() } } } class UsernameValidateHandler implements IValidateHandler { public validate() { } } class PwdValidateHandler implements IValidateHandler { public validate() { } }! class CaptchaValidateHandler implements IValidateHandler { public validate() { } }
|
单一职责原则
应该有且仅有一个原因引起类的变更。这个职责很好理解,一个类代码量越多,功能就越复杂,维护成本就越高,遵循单一职责原则可以有效控制类的复杂度。
里式替换原则
里式替换原则是指在使用父类的地方可以用它的任意子类进行替换,是对类的继承复用作出的要求,要求子类可以随时替换其父类,同时功能不被破坏,父类的方法仍然能够使用。
1 2 3 4 5 6 7 8 9 10
| class Bird { getFood(){ return "虫子" } } class Sparrow extends Bird { getFood(){ return ['虫子','稻谷'] } }
|
这里子类重载类父类的函数,但返回值发生了修改,那么如果使用Bird类实例的地方改成Sparrow类实例就会报错。
对于这种需要重载的类,正确的做法应该是让子类和父类共同实现一个抽象类或者接口,比如下面的代码:
1 2 3 4 5 6 7 8 9 10 11
| interface IBird { getFood() : string[] } class Bird implements IBird { getFood(){ return ['虫子'] } } class Sparrow implements IBird { return ['虫子','稻谷'] }
|
接口隔离原则
不应该依赖它不需要的接口,也就是说一个类对另一个类的依赖应该建立在最小的接口上,目的就是为了降低代码之间的耦合性,方便后续代码修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| interface IAnimal { eat(): void swim(): void } class Dog implements IAnimal { eat(){ ... } swim(){ ... } } class Bird implements IAnimal { eat(){ ... } swim(){ } }
|
这里例子就是一个违反接口隔离原则的反例,两个类都继承了接口IAnimal,但是Bird类并没有swim函数,只能实现一个空函数swim()
依赖倒置原则
应该避免依赖倒置,好的依赖关系应该是类依赖于抽象接口,不应该依赖于具体实现。这样设计的好处就是当依赖发生变化时,只需要传入对应的具体实例即可。
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Bike { run(){ console.log("Bike run") } } class Passenger { constructor(Bike: bike){ this.tool = bike } public start(){ tihs.tools.run() } }
|
以上例子中,Passenger的构造函数传入了一个Bike实例,然后在start函数中调用这个实例的run函数,此时两个类的耦合非常紧,如果现在要支持一个Car实例需要修改Passenger实例。
如果遵循依赖倒置原则,可以声明一个接口ITransportation,让Passenger类的构造函数改为ITransportation类型,从而做到Passenger类和Bike类解耦,这样当Passenger需要支持Car类的时候,只需要新增Car类即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| interface ITransportation { run(): void } class Bike implements ITransportation { run(){ console.log("Bike run") } } class Car implements ITransportation { run(){ console.log("Car run") } } class Passenger { constructor(ITransportation: transportation){ this.tools = transportation } public start(){ this.tool.run() } }
|
最少知识原则
一个类对于其他类知道得越少越好,就是说一个对象应当对其他对象尽可能少的了解。这一条原则要求任何一个对象或者方法只能调用对象本身和内部创建的对象实例,如果要调用外部的对象,只能通过参数的形式传递进来。
1 2 3 4 5
| class Store { set(key,value){ window.localStorage.setItem(key,value) } }
|
以上例子就是违反了最少知识原则,类内部使用了全局变量。
一种改造方式是在初始化的时候将window.localStorage作为参数传递给Store实例。
1 2 3 4 5 6 7 8 9
| class Store { constructor(s){ this._store = s } set(key,value){ this._store.setItem(key,value) } } new Store(window.localStorage)
|
设计模式核心思想
将变与不变分离,确保变化的部分灵活,不变的部分稳定。
前端常用的设计模式
- 单例模式
- 工厂模式
- 原型模式
- 状态模式
- 策略模式
- 代理模式
- 装饰器模式
- 适配器模式
- 迭代器模式
- 观察者模式/发布订阅模式