作者: 不拔
面向对象是合乎人意识事物的根本办法
人是怎么意识事物的
在面向对象呈现之前,已有面向过程的分析方法,为什么面向对象被提出了呢?究其实质起因,人们发现面向过程并不是依照人失常意识事物的形式去剖析软件,那么人到底是怎么意识事物的呢,Yourdon 在《面向对象的剖析》一书中提到,人类意识事物是遵循分类学的原理,分类学次要蕴含三点: 辨别对象及其属性;辨别整体对象及其组成部分;不同对象类的造成及辨别。
咱们当初能够回忆下咱们意识事物的过程,是不是和分类学所提到的 3 个要点很类似,看到一个事物,大略会感知到它的组成构造是怎么的,形态是怎么的,属于什么分类。所以,人意识事物是以对象的视角切入的,而后赋于对象具体的概念,比方苹果、梨子、汽车等等概念名称。
分类与分层的两种思维
咱们面对的事实世界是非常复杂的,应答复杂事物的有一个重要的办法即是形象,形象在理论利用过程中,又体现在两种办法上: 分层和分类 。分类即是将有差别的事物归类到不同的分组中,正如咱们常听到的 ” 物以类聚、人以群分 ” 的情理一样,产生分类的起因有两点:一点是事物间的关联严密水平,不须要将所有的事物都耦合在一起;另一点是人把握事物是有局限的,只能把握大量的要点,比方 5~7 个要点,超过了容易遗记。
分层是通过不同的视角看事物,每一层的关注点是不一样的,这种关注点不同是由本人的视角造成的,比方咱们了解计算机,并不需要深刻到二进制电信号去了解计算机。档次个性在软件设计中咱们常常遇到,比方计算机体系结构、TCP 七层协定等,档次个性有一个特点:越往上越具体、越往下越形象,越往上的内容越不稳固,也即是容易变动。
问题域到解空间的映射
咱们把须要解决的问题称之为问题域,或者问题空间,把解决方案称之为解空间。正向上一大节中提到的事物有档次个性,不同的人了解的事物是站在各自了解的视角,这样大家的了解、沟通并不统一的。如果咱们看到的问题空间是表层的,那么基于浅层次了解设计进去的计划就会不稳固,可能下次有一个小变动导致计划须要从新设计。
咱们能够把一个软件划分成三层:场景、性能和实体,场景层是常常会变的,比方发放优惠券场景就十分多,比方有天降红包支付优惠、分享有礼支付优惠券、新人注册支付优惠券等,这种场景的更迭随着业务的调整变动得十分快,因而场景层是不稳固的。性能撑持某一些的场景汇合,比照场景,性能相对而言稳固些,就像后面提到的发放优惠券场景,实质就是给用户发放优惠券,只须要提供发放优惠券的性能即可,至于哪些场景来调用它并不关注,但性能还是基于场景的汇合形象进去的,如果场景场景类型变动了,性能也就随之变动,比方担保交易和预售交易就不一样。实体是稳固的,以担保交易和预售交易为例,它的订单模型大抵是一样的,只是新减少了一些信息而已。
因而,咱们心愿从问题空间到解空间,大家看到的、了解的是统一的,而且看到的是问题的实质而非表象,往往场景、性能是不稳固的,而面向过程又是以性能驱动的,所以在易变动的场景下,它面临的问题就比拟多。比较稳定的是问题空间中的实体对象,所以面向对象分析是事实的须要。面向过程和面向对象是两个不同的视角的分析方法: 面向过程是一种演绎的分析方法,由外到内的过程;面向对象是一种演绎的分析方法,由内到外的过程。
三个一致性
软件开发会经验须要剖析、概要设计、具体设计、编码、测试、上线次要阶段,咱们不心愿每块是割裂的,比方剖析做完之后,做设计阶段又要从新去做剖析的工作,那么这外面就波及到一致性的问题, 即需要到剖析的一致性、剖析到设计的一致性、设计到编码的一致性。 这样做的益处能够保障无信息失真,因而咱们急需要一种剖析设计办法能做到这一点,面向对象分析与设计就能做到,因而全流程是以对象作为剖析与设计的指标,在最终编码中也都是对象。
面向对象的底层逻辑
提到面向对象,有局部人会提到封装、继承、多态等个性,而后这些并不是面向对象的实质个性,比方封装,面向过程中也有封装,多态面向过程也有体现,这些个性算不上面向对象特有的个性。 面向对象的底层逻辑是基于事实事物做的形象映射 :事实事物对应软件中的对象,咱们探讨解空间能对应到问题空间中的对象,两者是一一间接映射的,其它的分析方法是问题空间到解空间的间接映射。
面向对象分析与设计的全景图
咱们面临的问题是什么
从顶层看,咱们要实现需要到编码的工作,然而从需要到编码又会通过多个阶段,如需要剖析、方案设计等,从大的层面讲,咱们次要遇到三个问题:
1. 做什么的问题
看似这是一个简略的问题,但在简单的业务场景下,对做什么的了解太重要了,因为不同的人对需要的了解是不同的,比方最近做了一个我的项目,有一个业务判断规定是只针对跨境订单计税,最开始开发同学的了解是判断卖家类型是否是跨境卖家,然而到了测试阶段,发现大家对这个业务规定判断了解是不统一的,跨境订单跟卖家类型是没有关系的,真正的跨境订单计税场景是 shipTo(收货地址)和 shipFrom(发货地址)国家地址是不一样的。在大项我的项目中,波及到多个团队之间的协同,这样的问题异样突出。而且从业务诉求到产品需要,再到技术计划,这其中是通过了 2 次变换,每次变换是不同的角色在外面,大家的意识也会不一样。
2. 怎么做的问题
落实到事件具体要怎么做时,往往大家并不会出大的问题,怎么做偏具体执行阶段,程序员往往在逻辑严密性上没多大的问题,往往出问题是在第一个问题上,相当于方向弄错了,所做的工作也是无用的。
3. 办法领导的问题
咱们往往心愿劳而不获失去一种万能的办法,可能应答所有的问题,同时又看不起低级的办法,比方大部分人对用例分析方法不屑一顾,想要能体现技术水平高大上的办法。其实自上世纪 70、80 年代,软件的剖析设计办法并没有太大的变动,而且在咱们大学期间都学过,只是大家并不认为它是一种高大上的办法而已。
剖析到设计的过程
在本节中,咱们推导软件剖析到设计的过程,由粗到细,最终落实到咱们接触到的 UML 常识上。从需要提出到编码实现,这两头有两个关键问题:一是界定指标,即是定义分明要做什么的问题,相当于是咱们做事的方向、指标;二是具体如何做的问题,即通过怎么具体的计划撑持需要指标实现。因而,咱们须要一种办法可能帮忙咱们界定指标和示意具体计划,而且是大家互认的一种通用的办法。
通过用例图能够帮咱们界定指标,用例中有三个要害因素:用户、场景和指标。比方交易下单是一个用例,它的用户是买家,场景蕴含下单胜利和下单失败两个场景,用例的指标是买家能够购买心仪的商品。当用例指标确定了,相当于界定了指标,晓得需要要做什么,这个过程要重复和业务方确认好,至到最终大家对指标的了解是统一的,方向对了,具体怎么做就好办了。
具体怎么做用时序图示意,画时序图须要留神的一点是顶层的对象档次要统一,不能有的对象示意具体的实体对象,有的示意零碎对象,即对象的层级是统一的,要么大家都是零碎,比方导购零碎调用交易系统,交易系统调用领取零碎,要么大家都是对象,比方商品、订单等。通过时序图能够看到一个残缺性能的执行步骤,它就蕴含具体执行的细节,如失常流程、异样流程。
其实在下面有一个问题,在画时序图时要确定好对象,那么这个对象是怎么来的呢?它是由健壮性图剖析进去的,它外面有三个要害的对象:一个是边界对象,这个比拟好了解,比方 UI 界面就是边界对象;另一个是管制对象,即是管制业务流程的对象,如下单服务就能够看作是管制对象;实体对象即是问题空间中的业务对象,比方订单。画健壮性图是有规定的,个别是边界对象调用管制对象,管制对象产生实体对象,比方用户下单界面是边界对象,下单服务是管制对象,订单就是实体对象。
寻找对象之路
对象从哪里来
在本文第一局部第三大节中曾经提到,问题空间到解空间是一一映射,咱们探讨解空间中的对象时,其实它映射到问题空间中的对象,而问题空间中的对象次要来源于业务概念、业务规定、要害事件。大部分的对象是浮现的,咱们通过了解业务能发现,有的对象是隐性的,须要咱们继续对业务有更深的了解能力发掘出来。好的对象模型是须要通过屡次迭代打磨进去的,并非一次就能设计得美中不足。
发现对象的办法
在本文第二局部第二大节中曾经提到寻找对象的办法,不过那还只是要害浮现的对象,在本节中次要讲述残缺对象发现的办法,次要办法分成四个步骤:
- 通过健壮性图找到要害的实体对象;
- 通过构造分析方法找出更多的实体对象;
- 将对象组成有机的对象模型;
- 最初通过用例走查对象模型是否齐备。
这里以一个案例来阐明发现对象的过程,案例是用户在下单时,在订单上展现税的金额。首先画出健壮性图,这里的边界对象是下单界面,管制对象有两个,一个是下单服务,另一个是计税服务,实体对象也有两个,一个是计税单,一个是订单。有了计税单和订单这两个实体对象后,接下来通过构造分析方法,剖析出更多的对象。
对象都是有构造的,只有咱们把握了对象的构造,基本上就能把握对象的概貌,因而咱们从对象的构造动手,去剖析对象外部的构造、对象关联的构造,本质上是从两个维度登程:一是从本身的角度登程,看本人外部还蕴含了哪些对象,如主订单蕴含了子订单;另一个是从内部的角度登程,看本人还与哪些对象相关联,如计税单与订单是有关联的。这种找对象的办法我称之为构造分析方法,因为自身构造又是事物本质的一种表达方式,比方化学分子结构决定化学景象。
为了更好地表白出对象的构造,我的一个教训是给对象下好定义,下定义能够从不同的维度,比方功能性维度、价值性维度、目的性维度、结构性维度等,这里能够从结构性的维度去给对象下定义。以计税单为例,能够给它下一个定义:计税单是将订单金额信息转成若干个标的物计税的单据模型,从这个定义中,咱们能够看到计税单是与订单有关联关系的,另一个是计税单是蕴含了若干个标的物,咱们能够画出计税单的对象模型。
当对象模型画进去后,后续咱们探讨业务基本上围绕这个对象模型去探讨业务问题的,比方商品标的物哪些金额要参加计税、计税金额的计算口径是怎么的,到这里,大家再领会下 ” 问题空间到解空间一一间接映射 ” 这句话,业务上的诉求也无非是哪些订单费用项要计税,计税的逻辑是怎么的,有可能在这个场景下要扣减金本位优惠,在另外一种场景下金本位优惠不须要扣减,基于对象模型与产品、测试同学探讨问题,大家都是处于同一个维度的视角看问题,沟通了解老本会少很多。
对象模型是一种可视化的表白,咱们大部分的沟通问题是不足显性表白造成的,这句话能够这样了解,也能够那样了解,导致大家了解有偏差,当初用模型的模式沟通问题,很多偏差、歧义就打消了。
组织对象构造
当咱们剖析出一堆的对象后,还须要通过肯定的组织,正如后面提到,人对事物了解是有局限的,不能一下子承受太多的事物,因而能够将它们分成一个个小的域,比方商品域、订单域、税务域等,这样当汇集一个问题时,能够只看某个子域里的对象模型即可。
如何调配职责
职责是怎么来的
面向对象最难的点有两个:一个是找出对象;另一个是调配职责。UML 把职责定义为 ” 类元的契约或任务 ”,因而职责的划分从实质来讲还是类元自身决定的,比方订单,它要提供订单渲染、订单创立、订单批改、订单查问的任务。
职责分为两类:一类是认知职责;另一类是行为职责。
- 认知职责蕴含:
<!—->
-
- 对公有数据封装的认知。
- 对相干对象的认知。
- 对其可能导出或计算的事物的意识。
- 行为职责蕴含:
<!—->
-
- 本人执行的行为,包含创建对象或计算。
- 初始化其它对象的动作。
- 管制或协调其它对象的流动。
调配职责的逻辑
上一大节中提到的职责有两类,认知职责是对象本身的认知范畴,即它只能基于本身属性实现相应的职责,举一个例子,如果一主多子的订单,要计算总的订单金额,怎么调配职责呢?首先商品只能查到本身价格的信息,它的意识是基于商品 price 属性,一个子订单能够有多个商品,那么它也只能计算出子订单的金额信息,它的认知是基于 item 和 quantity 两个属性,主订单蕴含所有子订单的信息,那么就能够计算出总的订单金额。
从下面的例子中咱们能够看出,认知职责是基于对象属性的,正所谓 ” 不在其位、不谋其政 ”,认知职责肯定不会超过它的意识范畴的。
行为职责是偏畛域服务的,有的时候一个职责不属于某一个对象,比方转账,就是一个行为,让其它的职责承当并不适合,这类行为职责往往是一个显著的业务流动,比方订单渲染、订单创立就是行为职责而非认知职责。
调配职责肯定要遵循 ” 信息专家 ” 模式,它的含意是将职责调配给具备实现该职责所须要信息的那个类,也即下面提到的意识产生职责。
验证职责调配的合理性
咱们冀望调配的职责满足 ” 高内聚、低耦合 ”,怎么测验呢?咱们再回过头来思考职责的定义:类元的契约或任务,换句话讲,职责是满足其它对象来调用的,这个就与咱们画时序图的目标是统一的,每次产生一次调用,即意味着其它的对象要提供一个职责进去,因而咱们能够在时序图中看对象间的调用频次,如果一个对象被调用得十分频繁,有可能这个对象承当了太多的职责,是不是能够对其拆分,把职责调配一部分进来。因而,对象职责调配并不是欲速不达的,须要一直扫视、测验。
调配职责是要遵循肯定的准则,如创建者模式、信息专家模式、纯虚构模式等,这些准则会在下一篇中独自去讲。
案例
案例背景
这里举一个例子,阐明面向过程和面向对象在剖析、编写代码的差异性,计税须要判断是否满足计税规定,比方虚构商品不计税(手机充值之类)、有些免税地址不计税、小 B 买家也不计税等,因而须要提供一个计税过滤判断逻辑。
惯例面向过程实现
面向过程的思路很简略,提供一个过滤办法顺次解决上面逻辑:过滤虚构商品计税申请、过滤免税地址计税申请、过滤小 B 买家计税申请。
public void filter(List<TaxCalculateRequest> request){
// 过滤虚构商品计税申请
filterVirtualItem(request);
// 过滤免税地址计税申请 (即外岛)
filterOuterIsland(request);
// 过滤小 B 买家计税申请
filterPurchaseType(reqeust);
}
面向对象实现
面向过程是从过程视角或者是性能视角剖析问题,而面向对象是从对象的视角剖析问题,过滤计税申请是计税过滤器判断计税申请是否满足计税规定,这里就蕴含了两个对象:计税过滤器和计税规定,判断是否满足计税要求这个职责应该是在具体的计税规定处理器中,比方是否是小 B 买家等,因而咱们能够画出对象模型。
要害代码如下:
public abstract class AbstractRuleHandler {
/**
* 形象的业务规定解决
*
* @param request
*/
public abstract void handler(TaxCalculateRequest request);
/**
* 构造函数里实现注册
*/
public AbstractRuleHandler() {TaxCaluclateFilter.register(this);
}
}
总结
在文章中提到,面向对象的底层逻辑是基于事实事物做的形象映射,重要的不是要面向对象具体技术的应用上,而是剖析问题的思维上,这是最难的,它最大的益处是问题空间到解空间是一一间接映射的,请留神是一一间接映射,它意味着咱们在探讨计划的时候,齐全能够映射到问题空间,如果是间接映射,也就意味着设计的计划前面会面临从新设计的可能性,因为它是基于场景或性能做出的演绎设计,而且是表层的设计。真正把握了面向对象分析和设计的办法,也领会到其中的好处,对了解业务、方案设计、编码开发都有益处。