面向对象编程和设计原则。
代码质量评判标准:易维护、易读、易扩展,灵活、简洁、可复用、可测试。
OO编程范式
面向对象设计和实现要做的事情,就是把合适的代码放到合适的类中。
面向对象设计中的最后一步是组装类并提供执行入口,也就是上帝类要做的事情。
可以将上帝类做得很轻量级,把核心逻辑都剥离出去,下沉形成独立的类。上帝类只负责组装类和串联执行流程。
面向对象
- 三/四要素: 抽象(基础),封装,继承,多态
- 封装:数据隐藏保护(可维护和易用性)
- 抽象:方法隐藏(具体实现),抽象类和接口类实现(可扩展性和维护行,降低复杂度)
- 继承:is-a(代码复用)
- 多态:子类可以替换父类,运行中调用子类方法实现(扩展性和复用)
- 实现:继承加方法重写,接口类,duck-typing(两个类具有相同的方法)
- 基于接口而非实现编程
- 多用组合少用继承
接口和抽象类
- 接口(协议,约定):behave-like,解决抽象行为(解耦并隔离接口和实现),表示具有某一组行为特征。自下而上。
- 抽象类:is-a,解决代码复用问题。自上而下。 可包含属性和方法(可实现,也可不实现),
面向对象使用误区
- 滥用setter,getter:
- 尽量不要给属性定义 setter 方法
- getter 方法如果返回的是集合容器, 要防范集合内部数据被修改的危险
- Costants和Utils类要职责单一,尽量归并到相关类
- 静态方法一般用来操作静态变量或者外部数据。常用于各种 Utils 类
- 静态方法将方法与数据分离,破坏了封装特性,是典型的面向过程风格
面向对象设计
通常的步骤:
-
- 划分职责进而识别出有哪些类
根据需求描述,我们把其中涉及的功能点,一个一个罗列出来,然后再去看哪些功能点职责相近,操作同样的属性,可否归为同一个类。
-
- 定义类及其属性和方法
我们识别出需求描述中的动词,作为候选的方法,再进一步过滤筛选出真正的方法,把功能点中涉及的名词,作为候选属性,然后同样再进行过滤筛选。
-
- 定义类与类之间的交互关系
-
- 将类组装起来并提供执行入口
对于复杂的需求开发和框架的设计:
首先进行模块划分,将需求先简单划分成几个小的、独立的功能模块,然后再在模块内部,应用以上的方法。
可以采用以下辅助方法:画产品线框图、聚焦简单应用场景、设计实现最小原型、画系统设计图等。
设计原则
- SOLID
- KISS: 尽量简单(如何做)
- YAGNI: 当前不需要就不要做(要不要做)
- DRY
- LOD法则
SOLID
- SRP
- OCP
- LSP
- ISP
- DIP(Dependency Inversion Principle)
组件构建原则
- 组件聚合
- 复用/发布等同原则
- 共同闭包原则
- 共同复用原则
- 组件耦合
- 无依赖环原则
- 稳定依赖原则
- 稳定抽象原则
SRP/单一职责原则
一个类/模块只完成一个职责或功能(有且只有一个需要被改变的理由);每个模块负责只满足一个业务功能需求。
根据职责即变化的原因分离。
任何一个软件模块都应该只对某一类行为者(Actor)负责。主要讨论的是函数和类之间的关系。
类职责不单一的常见表现:
- 代码行数、函数、属性过多
- 依赖的类或被依赖太多
- 私有方法太多
- 比较难起合适名字
- 大量方法都集中操作某几个属性
OCP/开放封闭原则
允许新增代码来修改系统行为,而非只能靠修改原来的代码。
- 对扩展开放(应对变化),对修改关闭(保证稳定性)(扩展和修改依粒度而言)
- 具备扩展、抽象、封装意识
- 常用提高扩展性的方法: 多态、依赖注入、基于接口而非实现
主要目标是让系统易于扩展,同时限制其每次修改影响的范围。
实现方法是将系统划分为一系列组件,并将组件间的依赖关系按层次结构进行组织,使高阶组件不会因低阶组件被修改而受到影响。
软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改的。(对于扩展开放,对于更改是封闭的)
基于抽象和多态机制(继承IS-A),封闭是建立在抽象的基础之上,仅对频繁变化的部分做出抽象,
涉及可维护性、可重用性、健壮性。
LSP/里氏替换原则
组件可以相互替换。
子类遵循父类约定/design by contract
最早用于指导如何使用继承,使得依赖于一种接口,接口实现类具有可替换性。
子类型必须能够替换掉它们的基类型。IS-A是针对行为而言。
违反LSP原则的解决方式: 用兄弟类的形式代替继承;提取公共部分为超类;然后继承
可能违反的信号:派生类函数退化;派生类抛出基类不会抛出的异常
LSP是使OCP成为可能的主要原则之一,因此可替换性要通过显式或隐式契约来定义。
ISP/接口隔离原则
在设计中避免不必要的依赖。
多重继承。
任何层次的软件设计如果依赖了它并不需要的东西,就会带来意料之外的麻烦。
DIP/依赖反转原则
高层策略性代码不应该依赖实现底层细节的代码,而应该是相反的依赖关系。(稳定的抽象层)
- 高层模块(策略)不应该依赖于底层模块(实现)。二者都应该依赖于抽象。
- 抽象不应该依赖于细节。细节应该依赖于抽象。
该原则是框架设计的核心原则。
控制反转(Inversion of Control)是依赖倒置原则的一种代码设计思路。具体采用的方法就是所谓的依赖注入(Dependency Injection).
三种实现方式:
- 构造函数传入:把底层类作为参数传入上层类,实现上层类对下层类的“控制”
- Setter传递
- 接口传递
IoC容器的好处:
- 通过Configuration自动对代码进行初始化
- 创建实例的时候不需要了解细节:深度优先查找依赖,之后从最底层创建并注入。
DRY
DRY不是只代码重复,而是“知识”的重复,意思是指业务逻辑。例如由于沟通不足,两个程序员用两种不同的方法实现同样功能的校验。
LOD/迪米特法则
为实现高内聚低耦合,以下设计原则的出发角度不一样:
- 单一职责是从自身提供的功能出发
- 迪米特法则是从关系出发
- 针对接口而非实现编程是使用者的角度
扩展阅读
- 极客时间: 设计模式之美