编程范式
过程编程
数据,关注算法。
结构编程
数据,算法,关注函数,自上而下。
对象编程
数据,算法,关注数据(类),自下而上。
面向对象
以对象为中心。以对象概念为中心。
我们应该创建,高内聚,松耦合的对象。
- 对象,是具有责任的东西。
- 对象的数据,能告诉对象当前的状态。
- 对象的方法,能让对象履行自己的责任。
- 对象非常清楚自己的责任,对自己的行为负责,对自己负责。
传统观点
认为对象是具有方法的数据,对象有状态数据,有处理数据的方法。这种观点是从实现的角度理解。
现代观点
对象是具有责任的实体,关注对象的意图行为。这种观点是从概念的层次进行理解。
设计对象时,避免过早的思考细节,将细节隐藏,关注对象的公开接口。
考虑有一个XXX对象,能做什么,还能做什么,写出来。
类型
常见的继承关系。继承关系,体现的是IS-A的关系。男人是人,女人也是人。
封装,隐藏
封装,体现的是一种隐藏的哲学。
- 将复杂的东西隐藏,给外界简单的东西。
- 将不关心的东西隐藏,只提供抽象的接口给外界。
- 将保密的东西隐藏,不让外界看见。
- 面向对象,将不关心的数据或行为,进行隐藏。
- 封装,隐藏数据
- 封装,隐藏实现细节
- 封装,隐藏派生类
- 封装,隐藏设计细节
- 封装,隐藏实例化规则
- 数据的封装,对象状态
- 方法的封装,算法逻辑
- 对象的封装,如适配器模式,对被适配对象的封装
类型的封装,使用父类作为接口参数,对具体子类进行隐藏,
抽象类,接口就是隐藏。调用者根本不关心你具体实现是哪个类型。
所以,从概念层次思考,我们常说,我不关心你怎么实现,我只需要调这个接口就可以了。
这就是隐藏,通过抽象类,或接口。
软件功能
请记住,我们的软件要完成什么样的任务?把任务写下来分析。
分析什么?
- 分析可能的对象,和职责。
- 分析问题领域的术语,列一个表。
- 分析重要特征,列一个表。
- 分析 IS-A 继承关系
- 分析 HAS-A 关联,包含(聚合,组合)
- 分析依赖关系,A使用了B,A依赖B
- 不要过早的关注细节
- 针对接口编程
- 使用聚合代替继承
- 把会变化的部分取出并封装抽象类或接口
- 面向对象分析,按概念分析得到对象,按功能分解得到对象的方法。
构造函数
设置对象默认状态,设置与其他对象关系。
UML
用例图,活动图,交互图,类图,状态图,部署图。
外观模式 facade
从封装,隐藏角度思考
- 为多个子系统提供简单统一的调用接口
- 为某个系列操作创建一个简单接口
比如,读数据库最后返回数据模型,这个就可以用泛型写一个简单T GetTable 的接口。
外观,就像人传衣服一样,要好看,就是接口要简单,清晰,漂亮。
思考
- 希望封装,隐藏原系统
- 添加新的功能
- 只使用部分功能
- 原系统调用太复杂
Adapter 适配器
在类内部进行转换,侧重点是接口不兼容,需要接口转换。
为什么会接口不兼容,因为设计者不一样。
适配器和外观比较
- 是否存在既有的类?(是,是)
- 是否必须按某个接口设计?(是,否)
- 多态行为?(可能,否)
- 需要更简单的接口(否,是)
- 说白了,一个是兼容接口,一个是简化接口。
里氏替代原则
说的是子类可以替代父类。
说白了,就是要求针对抽象编程,针对接口编程。
典型的就是方法的参数,尽量用父类,不要用具体子类。
继承
- 从概念层次,看继承,五角星是形状。
- 从复用的层次,看继承,虚线边框的五角星,是五角星。
- 复用层次的继承问题很明显,仅仅为了复用而创造一个子类型。
思考
复用的目地,意图是什么?
反思,如果更多的类似的目地,意图,你是不是为了复用而需要创造出更多的子类型?
那么问题来了,子类多了,内聚低,重复代码多,维护就难了。
行为职责
注意思考,一个对象字段,和一个整型字段。
其实面向对象中一切都是对象,这没啥区别。例如,x+y 实际上可能是对象 x.Add(y),对象x的行为责任。
但是,如果是一个我们写的对象字段,字段对象可以扩展,包含更多的对象字段或整型字段,进一步再包含更多对象字段和整型字段。
但是,请记住,对象字段,需要从概念层次思考自己的行为职责。整型字段的行为职责实际上是语言帮你完成了。
问题领域
-
找到变化的地点,共性分析。
-
找到如何变化的,变性分析。
-
编程实践,一次且仅一次规则,小方法,方法粒度小些。
-
极限编程,测试驱动开发,重构,这些都是围绕,以前的开发模式,提出的位于,过度设计,和,不设计,之间的边开发边设计的开发模式。
-
针对接口编程,而不是针对实现。
-
优先使用组合,而不是继承。
-
用组合替代继承,本质是让你在该使用继承的地方才使用继承。
-
如果为了复用的目地,盲目的使用继承,是非常糟糕的。
-
但是,如果从概念层次去思考继承。原本可能从一个大类继承复用,现在这个大类包含一个对象,也就是使用组合。这个对象,是概念层次的变化点,对变化点进行共性分析,变性分析,形成变化点,小范围的继承体系结构。
这样构造的系统,才是职责分离,职责明确的。
OO三大特性
封装,继承,多态
OO五大原则
SOLID
单一职责 SRP
一个类仅有一个引起他变化的原因。
开闭原则 OCP
对扩展开放,对修改封闭。
里氏替换 LSP
子类可以替换父类。
接口隔离 ISP
采用多个与特定客户类有关的接口比采用一个通用的涵盖多个业务方法的接口要好。
依赖倒置 DIP
具体实现类依赖抽象类和接口。
面向对象的理念
面向对象是设计与问题相对应的数据格式。
- 类 类,用来描述这种数据格式。
- 对象 对象,是这种数据格式构造的特定数据结构。
- 关系 类相对于对象,就像类型相对于变量。
泛型
强调独立于类型的逻辑算法代码。
函数
函数原型对于函数,就是变量声明对于变量。
广义的委托
自己处理
调用者根据被调用者的返回,自己执行操作。
委托处理
调用者自己不去执行操作,委托其他对象去执行操作。调用者,就是指结构化编程中的主控程序。其他对象,就是指其他类。
委托随处可见
类A委托类B,类B可能进一步委托类C,这就形成了分层设计。
分层设计
类A可能在第一层,第二层是类B,第三层是类C。分层设计也体现了单一职责,减少关注点,降低耦合度,单一职责。
再谈委托
委托,把责任给其他类去处理。
- 请记住,责任的转移 生活中这样想,能不能把这烦恼的事抛出去。
狭义委托
就是封装了数据和操作数据的方法。
寻找对象间的关系
我们解决问题,通常是大问题化小问题,小问题化更小问题,最后就解决了。
可是,这会带来问题。这个问题就是,我们一上来就去考虑问题的解决方案。准确的说,一上来就陷入了解决方案的实现。很少有人会去抽象问题,泛化问题,考虑问题的抽象本质。
模式
- 面向对象的真正威力并不是来自继承,而是来自封装。
- 模式背后的基本原则和动机。
- 设计模式并不是孤立存在的。
我们是否有过这样的体验,我们创建了很多类来隔离问题域,但是,当我们需要把这些类优雅的组合起来,却困难重重?
我曾经喜欢将问题域都隔离到单独各自的service类。虽然问题被隔离了,可是设计却是一塌糊涂,因为这些service类,要组合起来形成一个软件系统,组合过程困难重重,设计糟糕至极。
- 模式本是天然形成的,绝非生搬硬套。
- 不要过早的开始实现。
- 软件最重要的是什么,是模型。
- 编程最重要的是什么,抽象的能力。
类,接口
-
类定义了相似对象的一般特性。
-
抽象类定义了相似类的一般特性。
-
接口,只是单纯的确保类(可以相似,可以不相似)的特性。
-
抽象类,出发点是重用,内在意义是隐藏,思考则应符合概念。
-
父类,接口,内在意义,都有隐藏的意思。
-
隐藏的目的是什么,是封装。封装好了,就是高内聚,外部通信就简单。
特化,泛化,is a,can do
数据库
- 数据库,对象状态的持久化。
- 有了对象,对象与数据库交互,数据层只是对象状态的持久化。
在写代码时,类,对象,概念层次。正在写的这行代码,是否造成代码宿主类对你写的代码的强依赖?
写抽象的程序,抽象的代码。
虚方法不应该访问到私有成员,因为重写就可能破坏。
代码依赖性
方法高內聚,可重用的片段,单一职责。
高内聚,低耦合,单一职责
低耦合,模块之间肯定需要通信,不过通信,应该依赖于设计良好的不易变化的抽象的接口。
每个模块都不应该了解另一个模块的实现细节而与其通信。
一个有助于实现高内聚,低耦合的原则是,分离关注点。
面向对象,让你把程序想象成一系列相互交互的对象,对象有自己的数据和行为。
首先关注的是概念,也就是抽象,而不是一开始就去关注实现细节。
依赖抽象,依赖接口。
基于接口编程,而不是依赖实现编程。
里氏替换,是指我们在设计子类型时,要考虑子类型可以替换父类型。
数据迁移对象 DTO
领域业务对象 domainmodel
数据库映射实体 entity
设计对象,首先要区分,公共接口方法,私有成员方法。
封装其实就是信息隐藏。
继承
继承应该从概念层次出发,而不是从重用的角度。
dry原则,do not repeat yourself
回调,委托,事件,action,func,泛型
DRY原则
完。