【设计模式】 面向对象六大设计原则(一)

一. 单一职责原则





1. 单一职责简介




单一职责定义 : 有且只有一个原因引起类的变化, 一个接口 或者 类 只有一个职责;




单一职责的好处 :


-- 复杂性 : 降低类的复杂性, 对类或接口的职责有清晰明确定义;


-- 可读性 : 提高可读性;


-- 维护 : 提高可维护性;


-- 变更风险 : 降低变更引起的风险, 接口改变只影响相应的实现类, 不影响其他类;







2. 单一职责示例






(1) 反面示例


【设计模式】 面向对象六大设计原则(一)


示例要求 : 创建一个绘图系统


-- 绘图 : 可以绘制圆形, 矩形;


-- 显示 : 显示绘制好的图形;




UML 图 : 明显下图不符合单一职责原则, 绘制圆形, 矩形, 显示圆形, 矩形 都集成在了一个接口中, 明显不符合单一职责原则;









(2) 正面示例




修改一下上面的示例, 我们可以得到下面的 UML 图 :


-- 说明 : 将 绘制 和 显示 分别封装在两个接口中, 让一个实现类同时继承这两个接口, 此时实现了接口的单一职责;


【设计模式】 面向对象六大设计原则(一)







3. 单一职责实践




单一职责适用 : 单一职责同时适用于接口, 类, 方法;


-- 接口 : 接口一定要做到单一职责;


-- 类 : 类的单一职责比较难以实现, 尽量做到只有一个原因引起变化;


-- 方法 : 一个方法尽可能做一件事, 能分解就分解, 分解到原子级别;










二. 里氏替换原则





1. 里氏替换简介



(1) 里氏替换定义


里氏替换 : 所有 引用基类的地方 必须能 透明地使用其子类的对象;


-- 子类替换父类 : 只要 父类出现的地方子类就可以出现, 替换为子类也不会产生任何错误, 使用者不需要知道父类还是子类;





(2) 继承的优缺点


继承优点 :


-- 代码共享 : 共享代码, 子类都拥有父类的方法和属性, 将 父类的代码共享给了子类;


-- 重用性 : 提高代码的重用性, 子类重用父类的代码;


-- 子父类异同 : 子类形似父类, 异于父类, 父子都不同;


-- 扩展性 : 提高代码的可扩展性, 子类就可以为所欲为了, 子类可以随意扩展父类;


-- 开放性 : 提高产品或项目的开放性, 父类随意扩展, 开放性随之增加了;




继承缺点 :


-- 侵入性 : 继承是侵入性的, 子类 强制继承 父类的方法和属性;


-- 灵活性 : 降低代码的灵活性, 子类必须拥有父类的属性和方法, 子类收到了父类的约束, 这是从子类的角度讲得;


-- 耦合性 : 增强了耦合性, 父类的属性和方法被修改时, 还需要顾及其子类, 可能会带来大量的重构, 这是从父类的角度讲的;








2. 里氏替换规范含义



(1) 子类完全实现父类方法


替换方法 : 定义一个接口或抽象类, 编码实现一个子类继承或实现该接口或抽象类, 调用类直接传入接口或抽象类;




示例说明 :


-- UML 图 :


【设计模式】 面向对象六大设计原则(一)


-- Client 场景类 : 在该场景中创建一个 Player 玩家, 然后为 Player 设置枪, 之后开枪 kill;


-- Player 类 : 玩家类, 该类有一个 IGun 抽象类成员变量;


-- IGun : 所有枪的基类, 所有的枪都要继承该类;


-- 说明 : 在 Player 中 IGun 类型的成员变量都可以使用 IGun 的三个实现类来替代;







(2) 子类个性




里氏替换单向性 : 子类可以有自己的方法和属性, 里氏替换可以正着用, 使用子类替换子类, 但是反过来不可以, 子类出现的地方, 父类不能使用;







(3) 覆盖方法参数放大




前置后置条件 : 子类方法中得前置条件必须与超类中被覆写的前置条件相同或者更宽松;


-- 前置条件 : 关于参数, 输入的参数必须符合要求, 才会执行, 必须满足的条件;


-- 后置条件 : 关于返回值, 方法执行完之后, 需要返回一个结果, 这个结果的标准;




重写与多态 : 如果子类的前置条件与父类相同, 那么是重写方法, 如果子类更宽松, 那么就是多态, 生成了新的方法;







(4) 覆盖方法返回值缩小




返回值 : 父类方法返回值类型 F, 子类方法返回值类型 S, 里氏替换原则是 S 范围必须小于 F;


-- 重写 : 父类子类参数相同, S 范围小于 F;


-- 重载 : 父类 子类 方法参数类型或者数量不同, 如果要符合里氏替换要求的话, 子类参数必须 >= 父类参数, 即不能让子类自己定义的方法被调用;







3. 里氏替换注意点




避免子类个性 : 如果想要使用里氏替换, 尽量避免让子类拥有自己单独的成员变量 或者 方法, 如果子类个性多了, 子类父类关系很难调和;


-- 里氏替换缺点 : 将子类当做父类用, 抹杀了子类的个性;


-- 里氏替换优点 : 将子类单独作为一个业务来使用, 会让代码间的耦合关系都复杂, 缺乏类替换标准;









三. 依赖倒置原则




1. 依赖倒置简介




(1) 依赖倒置定义


模块与抽象 :


-- 低层模块 : 不可分割的原子逻辑是低层模块;


-- 高层模块 : 原子逻辑组合成高层模块;


-- 抽象 : 接口或者抽象类, 不能被实例化;


-- 细节 : 实现类, 实现接口或继承抽象类就是细节, 可以被实例化;




依赖倒置定义 :


-- 模块依赖 : 高层模块不应该依赖底层模块, 两者都依赖其抽象, 实现类之间不发生依赖关系, 依赖关系通过接口或抽象类产生;


-- 抽象不依赖细节 : 抽象不依赖细节, 接口或抽象类不依赖与实现类;


-- 细节依赖抽象 : 实现类依赖接口或抽象类;





(2) 依赖倒置好处


依赖倒置好处 : 依赖倒置原则可以 减少类之间的耦合, 提高系统稳定性, 降低并发风险, 提高代码可读性 和 可维护性;


-- 耦合 :


-- 并发风险 :


-- 可读性 :


-- 可维护性 :







2. 依赖倒置注入实现





(1) 构造函数依赖对象


注入方法 : 通过 构造函数参数 声明依赖对象, 即构造函数注入;





(2) Setter 方法依赖对象


注入方法 : 通过 Setter 函数 参数 声明依赖对象, 即构造函数注入;





(3) 接口注入依赖对象


注入方法 : 在接口方法的参数中声明依赖对象, 即接口注入;






3. 依赖倒置遵循规则



依赖倒置本质 : 通过 抽象 即 接口或者抽象类, 使 各个类 和 模块实现彼此独立, 实现模块间 松耦合;





(1) 类有抽象


抽象所有的类 : 每个类尽量都有接口或者抽象类, 最好接口和抽象类都有;





(2) 变量类型抽象


变量类型抽象 : 变量的表面类型尽量都定义成抽象类;


-- 注意 : 不是绝对的, 一些工具类, 组件不必定义抽象;







(3) 类派生控制


派生控制 : 任何类不能从具体类派生;


-- 开发阶段 : 在开发阶段, 从具体类派生类是不允许的, 这样会增加继承的层次, 加大后期维护难度, 尽量将继承层次控制在 2 层以内;


-- 维护阶段 : 维护阶段可以出现从具体类派生的情况, 这样更有利于系统的稳定性;







(4) 不要覆盖方法


不要覆盖方法 : 尽量不要覆盖方法, 如果方法在抽象类中已经实现, 子类不要覆盖;


-- 覆盖缺点 : 会对系统稳定性产生影响;





(5) 结合里氏替换


里氏替换 : 父类出现的地方子类就能出现,




接口,抽象类,实现类规则 :


-- 接口 : 负责定义 public 属性和方法, 并声明与其它对象的依赖关系;


-- 抽象类 : 负责公共构造部分实现;


-- 实现类 : 准确地实现业务逻辑, 适当时候对父类进行细化;


上一篇:ENode 2.0 - 整体架构介绍


下一篇:说说nodejs里实用的模块