面向对象七大原则(丢了一个合成复用)
单一原则:
单一原则,只得就是,所有的类,文件,接口... 单一,一个文件,一个类,一个文件,只干这一件事
遵循单一职责原的优点有:
- 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
- 提高类的可读性,提高系统的可维护性;
- 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
开闭原则:
原理:对修改关闭,对扩展开放
基类的子扩展。
例子:element时间组件的使用
<div class="block"> <span class="demonstration">日期:</span> <el-date-picker v-model="timeSpan" type="daterange" :picker-options="pickerOptions2" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" align="right"> </el-date-picker> </div> <script> export default { data() { return { // 时间范围选择 pickerOptions2: { shortcuts: [ { text: "最近一周", onClick(picker) { const end = new Date(); const start = new Date(); start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); picker.$emit("pick", [start, end]); } }, { text: "最近一个月", onClick(picker) { const end = new Date(); const start = new Date(); start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); picker.$emit("pick", [start, end]); } }, { text: "最近三个月", onClick(picker) { const end = new Date(); const start = new Date(); start.setTime(start.getTime() - 3600 * 1000 * 24 * 90); picker.$emit("pick", [start, end]); } } ], } } } </script>
原因:软件系统的功能上的可扩展性要求模块是扩展开放的,软件系统的功能上的稳定性,持续性要求是修改关闭的。根本控制需求变动风险,缩小维护成本。
核心:用抽象构建框架,用实现类实现扩展,在不修改原有模块的基础上能扩展其功能。
优点: 增加稳定性、可扩展性高。
里氏替换:
子类能够替换父类,出现在父类能够出现的任何地方,子类必须完全实现父类的方法。在类中调用其他类时务必要使用父类,如果不能使用父类,则说明类的设计已经违背了原则。覆盖或实现父类的方法时输入参数可以被放大。即子类可以重载父类的方法,但输入参数不比父类方法中的小,这样在子类代替父类的时候,调用的仍然是父类的方法。
里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的方法应该保持不变,不被子类重新定义。如果继承是为了多态那么,而多态的前提是子类覆盖父类的方法所以将父类定义为抽象类,抽象类不能够实例化对象也就不存在替换这一说。
“子类可以扩展父类的功能,但不能改变父类原有的功能!!!!”
百度百科定义:
里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为
继承机制的优点:
代码共享,减少创建类的工作量
提高代码的重用性;
子类可以形似父类,又异于父类;
提高父类的扩展性,实现父类的方法即可随意而为;
继承机制的缺点:
继承是入侵性的(只要继承,就必须拥有父类的所有属性与方法);
降低了代码的灵活性(子类拥有了父类的属性方法,会增多约束);
增强了耦合性(当父类的常量、变量、方法被修改时,必需要考虑子类的修改)。
class Shape { constructor() { this.ant='蚁群' } } class Rectangle extends Shape { constructor() { super(); this.ant='蚁后' this.action='啥也不干' } setactions(dance) { this.action=dance } getArea() { return this.ant + this.action; } } class Square extends Shape { constructor() { super(); this.ant='蚁后' this.action='啥也不干' } setaction(high) { this.action = high; } getArea() { return this.ant + this.action; } } function renderLargeShapes(shapes) { shapes.forEach((shape) => { console.log(shape) switch (shape.constructor.name) { case 'Square': shape.setaction('跳高'); break; case 'Rectangle': shape.setactions('跳舞'); break; } let area = shape.getArea(); console.log(area) }) } let shapes = [new Rectangle(), new Square()]; renderLargeShapes(shapes);
依赖倒转原则:
我的理解:以前的依赖都是单对单的,当你修改其中一个将影响到下一的东西的使用。依赖倒转的原则就是,多对多的原则,由以前两层模式,更改成现在的三层模式,上层,依赖抽象层,在连接到下层。
上层的马,牛,鸡,都依赖于跑的这个抽象类,下层的跑的方式同样依赖于中层的抽象层。
上层的每一项都可以去调用到下层的每一项(多对多的关系),当想添加上层事物,或添加下层的方式,都不会影响到其他的事件,能够快速的的进行开发,减少更改代码的周期。
以上是我的理解
以下是系统解释:
依赖倒置原则的优点
可以通过抽象使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合(也是本质)
可以规避一些非技术因素引起的问题(项目大时,需求变化的概率也越大,通过采用依赖倒置原则设计的接口或抽象类对实现类进行约束,可以减少需求变化引起的工作量剧增情况。同时,发生人员变动,只要文档完善,也可让维护人员轻松地扩展和维护)
可以促进并行开发(如,两个类之间有依赖关系,只要制定出两者之间的接口(或抽象类)就可以独立开发了,规范已经定好了,而且项目之间的单元测试也可以独立地运行,而TDD开发模式更是DIP的*应用(特别适合项目人员整体水平较低时使用))
1. 模块间的依赖通过抽象类或接口发生,实现类之间的依赖关系也是通过抽象类或接口产生(实现类之间不应发生直接的依赖关系),降低系统的耦合性
2. 接口或抽象不依赖于实现类,但实现类依赖接口或抽象类,实现类对系统需要的功能具体实现,提高类的内聚程度
上段代码,来理解下,不知道我理解的对不对了
<html> <head></head> <body> </body> <script> class Shape { constructor() { this.ant='蚁群' } } class sheep { constructor() { this.ant='羊群' } } class Rectangle extends Shape { constructor() { super(); this.ant='蚁后' this.action='啥也不干' } getArea() { return this.ant + this.action; } } class Square extends sheep { constructor() { super(); this.ant='羊群' this.action='吃草' } getArea() { return this.ant + this.action; } } function renderLargeShapes(shapes) { shapes.forEach((shape) => { console.log(shape) switch (shape.constructor.name) { case 'Square': break; case 'Rectangle':; break; } let area = shape.getArea(); console.log(area) }) } let shapes = [new Rectangle(), new Square()]; renderLargeShapes(shapes); </script> </html>
接口隔离:
1、官方定义
接口隔离原则,英文缩写ISP,全称Interface Segregation Principle。
原始定义:Clients should not be forced to depend upon interfaces that they don’t use,还有一种定义是The dependency of one class to another one should depend on the smallest possible interface。
官方翻译:其一是不应该强行要求客户端依赖于它们不用的接口;其二是类之间的依赖应该建立在最小的接口上面。简单点说,客户端需要什么功能,就提供什么接口,对于客户端不需要的接口不应该强行要求其依赖;类之间的依赖应该建立在最小的接口上面,这里最小的粒度取决于单一职责原则的划分。
接口隔离原则和单一职责原则
从功能上来看,接口隔离和单一职责两个原则具有一定的相似性。其实如果我们仔细想想还是有区别的。
(1)从原则约束的侧重点来说,接口隔离原则更关注的是接口依赖程度的隔离,更加关注接口的“高内聚”;而单一职责原则更加注重的是接口职责的划分。
(2)从接口的细化程度来说,单一职责原则对接口的划分更加精细,而接口隔离原则注重的是相同功能的接口的隔离。接口隔离里面的最小接口有时可以是多个单一职责的公共接口。
(3)单一职责原则更加偏向对业务的约束,接口隔离原则更加偏向设计架构的约束。这个应该好理解,职责是根据业务功能来划分的,所以单一原则更加偏向业务;而接口隔离更多是为了“高内聚”,偏向架构的设计。
以上官方定义
以下自己理解:
我认为,接口隔离,就是把以前的一个接口调3,4个东西,完咯给分割一下,把多个东西拆成安区域,功能区分割一下它。如图
没有进行隔离的图样:一个事件,把所有的都给调出来了
经过隔离后的样事:简单分离下(但不符合单一原则)
其实我感觉吧,当你使用了 单一原则后,你所谓的接口隔离原则,好像就没有什么用了,毕竟单一原则上面讲了,一个接口调用一件事,就不存在一调多了
迪米特法则:
自己理解:
也可以理解成三成架构这样,迪米特法则,上层触发事件,中层接收事件,底层去完成事件,这样说感觉,好像关联了,举个例子:
我想让人去给我买盒饼干,我把钱给了甲,甲把这事交给了乙,乙把饼干买回来了,感觉举的例子不是太好,反正就是,我最后拿到了饼干,我不知道乙去哪里买的,可能给我买了一盒过期的饼干我也不知道,但我交代的事办成了。大概就是这个意思,
看图理解比较好
官方定义:
定义:一个对象应该对其他对象保持最少的了解
迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个“中介”来发生联系,例如本例中,总公司就是通过分公司这个“中介”来与分公司的员工发生联系的。过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。
实际案例:
vue项目中的dispatch,你在页面中调接口的时候,不知道stort文件中怎么去给你请求数据的,怎么去调接口的。
合成复用原则:
官方定义:
要尽量使用合成和聚合,尽量不要使用继承
为什么要使用合成复用:
第一,继承复用破坏包装,它把父类的实现细节直接暴露给了子类,这违背了信息隐藏的原则;
第二:如果父类发生了改变,那么子类也要发生相应的改变,这就直接导致了类与类之间的高耦合,不利于类的扩展、复用、维护等,也带来了系统僵硬和脆弱的设计。而用合成和聚合的时候新对象和已有对象的交互往往是通过接口或者抽象类进行的,就可以很好的避免上面的不足,而且这也可以让每一个新的类专注于实现自己的任务,符合单一职责原则。
这俩图就很明显了