1、开闭原则
特征:对扩展开放,对更改关闭
1、在我们最初编写代码时,假设变化不会发生;当变化发生时,我们就创建抽象来隔离以后发生的同类变化。
2、面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。
注意:开发人员应该仅对程序中呈现出频繁变化的那些部分做出抽象,然而,对于应用呈现中的每个部分都刻意地进行抽象同样不是一个好主意;
例子:
/** * 接口,电脑的接口 */ export interface IComputer { getId(): number; getBrand(): string; getPrice(): number; } /** * 实现电脑的接口,建立电脑类 */ export class Computer implements IComputer { private id: number; private brand: string; private price: number; public constructor(id: number, brand: string, price: number){ this.id = id; this.brand = brand; this.price = price; } public getId(): number { return this.id; } public getBrand(): string { return this.brand; } public getPrice(): number { return this.price; } }
现在有需求,对电脑进行打折处理,即打8折优惠,如果这个类已经在项目中使用,如果冒然去更改接口或类,那么产生BUG的概率将大大的提升,但是又要实现打折的需要,那么根据开闭原则可以建立个子类,继承父类,然后实现打折处理,具体如下:
export class DisMComputer extends Computer { public constructor(id: number, brand: string, price: number) { super(id, brand, price); } public getOriginPrice(): number { return super.getPrice(); } public getDiscountPrice(): number { return this.getOriginPrice() * 0.8; } }
以上代码在实现不更改父类的情况下,实现新的需求,调用如下
import {DisMComputer, IComputer} from "./tools/builder"; let com: IComputer = new DisMComputer(1, 'xiaoMi', 4500); console.log(`这个电脑的id是${com.getId()}, 电脑的品牌是${com.getBrand()},原价是${com.getPrice()},折后价是${(<DisMComputer>com).getDiscountPrice()}`);
2、依赖倒置原则
定义: 高层模块不应该依赖低层模块,二者都应该依赖其抽象
1、抽象不应该依赖细节;细节应该依赖抽象
2、针对接口编程,不要针对现实编程
优点:可以减少类之间的耦合,提高系统稳定性,提高代码的可读性和可维护性,可降低修改程序所造成的风险
注意: 这里的高层与低层的概念, 比如A调用B, B调用C,那么相对于B来讲,A是高层,C是低层
有问题的例子:
export class Coder { public studyJs(): void { console.log('coder is learning Javascript'); } public studyPhp(): void { console.log('coder is learning Php'); } }
调用:
import {Coder} from "./tools/product"; let c = new Coder(); c.studyJs(); c.studyPhp();
这个例子中的调用即高层,要依赖低层,当想新增新的学习内容的时候就要更改Coder来达到添加学习的内容,这个耦合度很高,根据依赖倒置原因是不鼓励这样操作的,要实现面向接口编程,而不是面向现实编程:具体更改如下:
创建接口,以及创建study的对象:
//总的接口 export interface IStudy { study(): string } export class StudyJavascript implements IStudy{ public study(): string { return 'is learning javascript'; } } export class StudyPhp implements IStudy { public study(): string { return 'is learning Php'; } } export class StudyJava implements IStudy { public study(): string { return 'is learning Java'; } }
创建学习者:
import {IStudy} from "./builder"; export class Coder { private name: string = 'coder'; private _course: IStudy; public constructor(course: IStudy) { this._course = course; } public study(): void { console.log(`${this.name} ${this._course.study()}`); } /** * 实现set方法 * @param course */ public set course(course: IStudy) { this._course = course; } }
实现高层调用:
import {Coder} from "./tools/product"; import {StudyJava, StudyJavascript, StudyPhp} from "./tools/builder"; class Test { /** * 调用方式一 */ public learnCoderPath1(): void { let c1 = new Coder(new StudyJavascript()); c1.study(); let c2 = new Coder(new StudyPhp()); c2.study(); let c3 = new Coder(new StudyJava()); c3.study(); } /** * 调用方式二 */ public learnCoderPath2(): void { let c1 = new Coder(new StudyJavascript()); c1.study(); c1.course = new StudyPhp(); c1.study(); c1.course = new StudyJava(); c1.study(); } } let t = new Test(); t.learnCoderPath1(); t.learnCoderPath2();
以上例子把学习的课程变成的对象,当需要学习内容的时候把学习内容的对象当作参数传入到coder中,便可实现学习的,当需要添加新的学习课程的时候,只需要添加学习内容的类,作为参数传入coder中便可,上面test实现的两种调用方法: 第一种方法是实现多个类的创建,第二个方法是只实现一个类,通过更改里面的类的参数达到;
3、单一职责原则
定义: 不要存在多于一个导致类变更的原因
一个类/接口/方法只负责一项职责
优点: 降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险
有问题的例子:
export class Electrical { public getFunc(type: string): void { if(type === 'computer') { console.log('play computer'); } else if(type === 'TV') { console.log('watching TV'); } } }
调用:
import {Electrical} from "./tools/product"; new Electrical().getFunc('TV');
以上的例子把所有功能集中在一个类里面,这样会造成一个类的臃肿,复杂,不利于阅读和维护,那么可以分别建立不同的类,把功能分开,但是注意要对接口编程,这样才不会类的爆炸
export interface IElectrical { getFunc(): string; } export class Computer implements IElectrical { public getFunc(): string { return "play computer"; } } export class Tv implements IElectrical { public getFunc(): string { return "watching Tv"; } }
可以改进到上面的情况,根据不同的需求调用所对应的类
注意:单一职责主要体现在对接口的单一职责(把不同接口进行分开处理), 对类的单一职责(把不同功能的类分离出来), 对方法的单一职责(即一个方法对应一个功能)这样可以实现代码之前的解耦,也有利于优化代码
4、接口隔离原则(主要针对接口)
定义: 用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口
1、一个类对一个类的依赖应该建立在最小的接口上;
2、建立单一的接口,不要建立庞大臃肿的接口;
3、尽量细化接口,接口中的方法尽量少;
4、注意适度原则,一定要适度(否则会造成接口过多,不易管理)
优点:符合我们常说的高内聚低耦合的设计思想从而使类具有很好的可读性,可扩展性和可维护性
有问题的例子
export interface IAnimalAction { eat(): void ; fly(): void ; swim(): void ; } export class Dog implements IAnimalAction { public eat(): void { } public fly(): void { //无需实现 } public swim(): void { } } export class Bird implements IAnimalAction { public eat(): void { } public fly(): void { } public swim(): void { //无需实现 } }
上面的代码一个接口实现了多个功能,但是这些功能在类的实现中有些是不需要实现的,这样就违背了接口隔离的设计原则,因些要对以上代码进行修正
export interface IFlyAction { fly(): void ; } export interface IEatAction { eat(): void ; } export interface ISwimAction { swim(): void ; } export class Dog implements IEatAction, ISwimAction { public eat(): void { } public swim(): void { } } export class Bird implements IEatAction, IFlyAction { public eat(): void { } public fly(): void { } }
以上代码重新整理完后就实现报接口的隔离
6、迪米特原则(又叫最少知道原则)
定义:一个对象应该对其他对象保持最少的了解,又叫最少知道原则
尽量降低类之间的耦合
优点:降低类之间的耦合
//产品类 export class Product { }
import {Product} from "./product"; //建造者类 export class Builder { private list: Array<Product> = []; public getNumber (): number { for(let i = 0; i< 18; i++) { this.list.push(new Product()); } return this.list.length; } }
import {Builder} from "./builder"; //老板类 export class Boss { public commandGetNumber(): void { let B = new Builder(); console.log(`产品的数量是${B.getNumber()}`); } }
import {Boss} from "./tools/boss"; //调用 new Boss().commandGetNumber();
以上规则是老板无需知道产品管理者如何实现计算产品数量的方法,只需要调用其对外公开出来的方法即可,无需知道内容实现的方法
6、里氏替换原则
定义:子类可以扩展父类的功能,但不能改变原有父类的功能;
优点(目的:增强程序的健壮性)实际项目中,每个子类对应不同的业务含义,使父类作为参数,传递不同的子类完成不同的业务逻辑。