目录
前言
在面向对象(OOP)程序设计中是一个非常重要的设计原则,这个原则可以帮助我们更好的描述现实世界中的对象以及其关系,也很好地展现了面向对象里面的封装、继承、多态个特性。在项目架构设计中,系统通过设计模式合理的约束来设计出低耦合的系统,使系统更加灵活、更加易于维护、更容易复用、更容易扩展。这个原则也应用EPC中,作为可读性设计的 原则,用于编码规范与设计架构的优化。
SOLID 设计模式的具体内容
单一职责原则(Single Responsibility Principle)
开闭原则(Open Closed Principle)
里氏替换原则(Liskov Substitution Principle)
接口分离原则(Interface Segregation Principle)
依赖倒置原则(Dependency Inversion Principle)
SOLID 原则详解
一、单一职责原则
就是一个对象、类、接口或模块的职责应该单一,不要承担过多的职责,类似与独立封装。
1、单一职责带来的好处
1、高内聚低耦合:职责划分清晰,功能的单一会促使整个模块内聚性比较高,减少对功能间的耦合。
2、稳定性:模块内部的职责单一功能,代码结构与逻辑清晰,利于模版整体的高稳定性。
3、可读性:代码结构清晰、注释合理,利于团队内部沟通交流。可读性更好,更利于构建大型项目。
4、可测试性:模块职责单一,其测试用例、数据也可以更好模拟,更利于维护和模块的自动化测试。
2、反规则的问题
1、如果职责不单一模块之间相互依赖比较严重,那就会导致这一个类或模块的文件会经常涉及到修改,团队研发里因为不知道前因后果,常常由于修改一个问题会引入更多的一个问题,最后导致修改越多问题越多。
2、职责不单一那整个文件的可读性和健壮性会相应的降低,整体的代码会变得更加脆弱。
3、是职责不单一,文件方法常年累积后,会变得难以维护,内容的组合复用行会变弱。
3、 应用示例
单一职责的示例:
目录与文件结构 vs 单类实例
二、开闭原则
原意是说:软件设计应该是对于扩展开放的,但是对于修改封闭的。当别人要修改软件功能的时候,使得他不能修改我们原有代码,只能新增或扩展实现软件功能修改的目的。简而言之就是:约束抽象的类、对封装类进行变化。
1、带来的好处
对基类、派生类都能很好的展现其稳定性,模块组件的可延续性好。
研发项目中,单向依赖清晰明了,易扩展和维护。
对于测试来说,基类足够抽象稳定,派生或子类的测试用力只需要写扩展部分的,好测试和维护。
2、可能带来的问题
抽象类在设计时候就有缺陷,不够完善、不够抽象,导致子类或实现类是不好扩展,会不停的返工重构改基类。
抽象类粒度拆分太细,导致维护的类太多,维护过程会太繁琐。
3、使用示例
trtc 在的基础类,在web 、electron 端等子类实现是,有子类来扩展实现细节。
export abstract class TrtcBase extends TModule {
abstract init(AppId: number, userId: string): Promise;
abstract unInit(): Promise;
...
}
export TrtcWeb extends TrtcBase {
abstract init(AppId: number, userId: string): Promise<void> {
};
abstract unInit(): Promise<void> { };
abstract startLocalVideo(dom: HTMLElement): Promise<void> {
};
abstract stopLocalVideo(): Promise<void> {
};
...
}
export TrtcElectron extends TrtcBase {
abstract init(AppId: number, userId: string): Promise<void> {
};
abstract unInit(): Promise<void> { };
abstract startLocalVideo(dom: HTMLElement): Promise<void> {
};
abstract stopLocalVideo(): Promise<void> {
};
...
}
三、里氏替换原则
原意是说:程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的。子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
里氏替换原则的特点:
1、子类可以拓展父类的功能,但不能修改父类已有的功能,如果修改了父类已有的功能,可能导致父类定义的功能在子类覆盖后不适用。
2、重载父类的方法时,子类的方法形参应该比父类更宽松。
3、实现父类的抽象方法时,子类的方法输出结果应该比父类更加严格。
4、子类可以有自己独特的私有方法,但是需要注意的是,如果子类不能完全实现父类的方法,或者父类的某一些方法在子类中已经不适用,这种情况则建议断开父子关系,使用组合等方式代替继承出现。
1、带来的好处
1、合理得用类的继承关系,提高了代码的复用性,但也增强了类与类之间的耦合性。
2、通过建立抽象,运行过程中具体实现取代抽象,保证了系统的可拓展性。
3、只要继承父类就拥有父类的全部属性和方法,这样减少了代码重复创建量共享了代码但也约束了子类的行为,降低了系统灵活性。
2、带来的问题
1、只要继承,就必须拥有父类的所有属性和方法,在实现过程中就会有冗余的问题。
3、使用示例
1、在实际的项目中,可以通过依赖、组合等方式来解决 里氏原则引入的问题。
四、接口分离原则
客户端不应该被迫依赖于它不使用的方法,一个类对另一个类的依赖应该建立在最小的接口上。多个特定客户端接口要好于一个宽泛用途的接口。
1、带来的好处
在实际的研发过程中更多的偏向与单向依赖来处理。
1、粒度与复用性:限制接口的粒度,包含的方法越少接口的粒度越小,粒度越小接口的可复用性越高。类是于GraphQL的思想。
2、稳定性:接口中包含的方法越少,接口的稳定性越高,系统的稳定性越高。由于限制了接口内方法的数量,变更的扩散能够被有效控制。
3、使用多个专用的接口更能体现对象的层次,提高了代码的可读性。提供仅包含调用方所需方法的接口,能最大程度降低调用方使用接口的代价。
2、带来的问题
1、粒度小之后管理的模块的数量会比较多,系统管理和维护难度增大,并且可能对整个系统稳定性会有影响。
2、通过通用接口的组合来替换专有接口。一个数据量会增大、二个是组合耗时需要时间,可能需要中间层来粘合数据、三是数据缓存量增加和性能会降低。
3、使用示例
原始的接口依赖如上,如果变成了下面的这个图。
如果有另外的一个类,有AntiTheft, Fireproof 的继承关系,这样就可以更好的实现了。
五、依赖倒置原则
一个方法应该遵从“依赖于抽象而不是一个实例”。高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
主要分为两个部分:依赖注入、控制反转。
依赖注入
一个类依赖另一个类的功能,那么就通过注入,如构造器、setter方法等,将这个类的实例引入。侧重于实现。
控制反转
创建实例的控制权由一个实例的代码剥离到IOC容器控制。原先是由类本身去创建另一个类,控制反转后变成了被动等待这个类的注入。侧重于原理。
1、带来的好处
1、帮助进行单元测试。
2、由于依赖关系的初始化是由注入器组件完成的,因此减少了样板代码。
3、扩展应用程序变得更加容易。
4、帮助实现松耦合,这在应用编程中很重要。。
2、带来的问题
1、学习起来有点复杂,如果使用过度会导致管理问题和其他问题。
2、许多编译时错误被推送到运行时。
3、依赖注入框架是通过反射或动态编程实现的。
3、使用示例
一个类不应静态配置其依赖项,而应由其他一些类从外部进行配置。
其他规则
1、迪米特法则
定义:一个对象应该对其他对象保持最少的了解,又叫最少知道原则。
问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
1、带来的好处
1、降低了类之间的耦合度,提高了模块的相对独立性。
2、由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
2、带来的问题
在系统里造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的商务逻辑无关。
遵循类之间的迪米特法则会是一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。
2、合成复用
在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用这些对象的目的。应首先使用合成/聚合,合成/聚合则使系统灵活,其次才考虑继承,达到复用的目的。而使用继承时,要严格遵循里氏代换原则。有效地使用继承会有助于对问题的理解,降低复杂度,而滥用继承会增加系统构建、维护时的难度及系统的复杂度。
1、带来的好处:
1、新的实现较为容易,因为超类的大部分功能可以通过继承的关系自动进入子类。
2、修改和扩展继承而来的实现较为容易。
2、带来的问题:
1、 继承复用破坏包装,因为继承将超类的实现细节暴露给子类。由于超类的内部细节常常是对于子类透明的,所以这种复用是透明的复用,又称“白箱”复用。
2、如果超类发生改变,那么子类的实现也不得不发生改变。
3、 从超类继承而来的实现是静态的,不可能在运行时间内发生改变,没有足够的灵活性。
4、继承只能在有限的环境中使用
总结
1、在SOLID 原则的目标都是在最求项目架构设计的合理行,并且大部分设计原则很多时候是在做平衡。一般情况是开始很美好,过程很痛苦,结果不太如意。然后迭代重复这个过程。平时在推行和设计的时候对这一块的监督和执行力上面还不够坚决。
2、面向对象的这种方式可以解决绝大部分问题。单针对不同的项目或实际问题中,选择或融合如面向过程或函数式编程等方式,可以更好的解决项目。