面向对象分析与设计的底层逻辑

简介: 真正掌握了面向对象分析和设计的方法,也体会到其中的益处,对理解业务、方案设计、编码开发都有好处。

作者:不拔

面向对象是符合人认识事物的基本方法


人是怎么认识事物的


在面向对象出现之前,已有面向过程的分析方法,为什么面向对象被提出了呢?究其本质原因,人们发现面向过程并不是按照人正常认识事物的方式去分析软件,那么人究竟是怎么认识事物的呢,Yourdon 在《面向对象的分析》一书中提到,人类认识事物是遵循分类学的原理,分类学主要包含三点:区分对象及其属性;区分整体对象及其组成部分;不同对象类的形成及区分。


我们现在可以回想下我们认识事物的过程,是不是和分类学所提到的 3 个要点很相似,看到一个事物,大概会感知到它的组成结构是怎样的,形状是怎样的,属于什么分类。所以,人认识事物是以对象的视角切入的,然后赋于对象具体的概念,比如苹果、梨子、汽车等等概念名称。


1.png


分类与分层的两种思维


我们面对的现实世界是非常复杂的,应对复杂事物的有一个重要的方法即是抽象,抽象在实际应用过程中,又体现在两种方法上:分层和分类。分类即是将有差异的事物归类到不同的分组中,正如我们常听到的"物以类聚、人以群分"的道理一样,产生分类的原因有两点:一点是事物间的关联紧密程度,不需要将所有的事物都耦合在一起;另一点是人掌握事物是有局限的,只能掌握少量的要点,比如 5~7 个要点,超过了容易忘记。


2.png


分层是通过不同的视角看事物,每一层的关注点是不一样的,这种关注点不同是由自己的视角造成的,比如我们理解计算机,并不需要深入到二进制电信号去理解计算机。层次特性在软件设计中我们经常遇到,比如计算机体系结构、TCP 七层协议等,层次特性有一个特点:越往上越具体、越往下越抽象,越往上的内容越不稳定,也即是容易变化。


3.png


问题域到解空间的映射


我们把需要解决的问题称之为问题域,或者问题空间,把解决方案称之为解空间。正向上一小节中提到的事物有层次特性,不同的人理解的事物是站在各自理解的视角,这样大家的理解、沟通并不一致的。如果我们看到的问题空间是表层的,那么基于浅层次理解设计出来的方案就会不稳定,可能下次有一个小变化导致方案需要重新设计。


4.png


我们可以把一个软件划分成三层:场景、功能和实体,场景层是经常会变的,比如发放优惠券场景就非常多,比如有天降红包领取优惠、分享有礼领取优惠券、新人注册领取优惠券等,这种场景的更迭随着业务的调整变化得非常快,因此场景层是不稳定的。功能支撑某一些的场景集合,对比场景,功能相对而言稳定些,就像前面提到的发放优惠券场景,本质就是给用户发放优惠券,只需要提供发放优惠券的功能即可,至于哪些场景来调用它并不关注,但功能还是基于场景的集合抽象出来的,如果场景场景类型变化了,功能也就随之变化,比如担保交易和预售交易就不一样。实体是稳定的,以担保交易和预售交易为例,它的订单模型大致是一样的,只是新增加了一些信息而已。


5.png


因此,我们希望从问题空间到解空间,大家看到的、理解的是一致的,而且看到的是问题的本质而非表象,往往场景、功能是不稳定的,而面向过程又是以功能驱动的,所以在易变化的场景下,它面临的问题就比较多。比较稳定的是问题空间中的实体对象,所以面向对象分析是现实的需要。面向过程和面向对象是两个不同的视角的分析方法:面向过程是一种归纳的分析方法,由外到内的过程;面向对象是一种演绎的分析方法,由内到外的过程。


三个一致性


软件开发会经历需要分析、概要设计、详细设计、编码、测试、上线主要阶段,我们不希望每块是割裂的,比如分析做完之后,做设计阶段又要重新去做分析的工作,那么这里面就涉及到一致性的问题,即需求到分析的一致性、分析到设计的一致性、设计到编码的一致性。这样做的好处可以保证无信息失真,因此我们急需求一种分析设计方法能做到这一点,面向对象分析与设计就能做到,因此全流程是以对象作为分析与设计的目标,在最终编码中也都是对象。


6.png


面向对象的底层逻辑


提到面向对象,有部分人会提到封装、继承、多态等特性,然后这些并不是面向对象的本质特性,比如封装,面向过程中也有封装,多态面向过程也有体现,这些特性算不上面向对象特有的特性。面向对象的底层逻辑是基于现实事物做的抽象映射:现实事物对应软件中的对象,我们讨论解空间能对应到问题空间中的对象,两者是一一直接映射的,其它的分析方法是问题空间到解空间的间接映射。


7.png

面向对象分析与设计的全景图


我们面临的问题是什么


从顶层看,我们要完成需求到编码的工作,然而从需求到编码又会经过多个阶段,如需求分析、方案设计等,从大的层面讲,我们主要遇到三个问题:


1. 做什么的问题


看似这是一个简单的问题,但在复杂的业务场景下,对做什么的理解太重要了,因为不同的人对需求的理解是不同的,比如最近做了一个项目,有一个业务判断规则是只针对跨境订单计税,最开始开发同学的理解是判断卖家类型是否是跨境卖家,然而到了测试阶段,发现大家对这个业务规则判断理解是不一致的,跨境订单跟卖家类型是没有关系的,真正的跨境订单计税场景是 shipTo(收货地址)和 shipFrom(发货地址)国家地址是不一样的。在大项项目中,涉及到多个团队之间的协同,这样的问题异常突出。而且从业务诉求到产品需求,再到技术方案,这其中是经过了 2 次变换,每次变换是不同的角色在里面,大家的认识也会不一样。


2. 怎么做的问题


落实到事情具体要怎么做时,往往大家并不会出大的问题,怎么做偏具体执行阶段,程序员往往在逻辑严密性上没多大的问题,往往出问题是在第一个问题上,相当于方向弄错了,所做的工作也是无用的。


3. 方法指导的问题


我们往往希望不劳而获得到一种万能的方法,能够应对所有的问题,同时又看不起低级的方法,比如大部分人对用例分析方法嗤之以鼻,想要能体现技术水平高大上的方法。其实自上世纪 70、80 年代,软件的分析设计方法并没有太大的变化,而且在我们大学期间都学过,只是大家并不认为它是一种高大上的方法而已。


8.png


分析到设计的过程


在本节中,我们推导软件分析到设计的过程,由粗到细,最终落实到我们接触到的 UML 知识上。从需求提出到编码实现,这中间有两个关键问题:一是界定目标,即是定义清楚要做什么的问题,相当于是我们做事的方向、目标;二是具体如何做的问题,即通过怎样具体的方案支撑需求目标实现。因此,我们需要一种方法能够帮助我们界定目标和表示具体方案,而且是大家互认的一种通用的方法。


9.png


通过用例图可以帮我们界定目标,用例中有三个关键要素:用户、场景和目标。比如交易下单是一个用例,它的用户是买家,场景包含下单成功和下单失败两个场景,用例的目标是买家可以购买心仪的商品。当用例目标确定了,相当于界定了目标,知道需求要做什么,这个过程要反复和业务方确认好,至到最终大家对目标的理解是一致的,方向对了,具体怎么做就好办了。


具体怎么做用时序图表示,画时序图需要注意的一点是顶层的对象层次要一致,不能有的对象表示具体的实体对象,有的表示系统对象,即对象的层级是一致的,要么大家都是系统,比如导购系统调用交易系统,交易系统调用支付系统,要么大家都是对象,比如商品、订单等。通过时序图可以看到一个完整功能的执行步骤,它就包含具体执行的细节,如正常流程、异常流程。


10.png


其实在上面有一个问题,在画时序图时要确定好对象,那么这个对象是怎么来的呢?它是由健壮性图分析出来的,它里面有三个关键的对象:一个是边界对象,这个比较好理解,比如UI界面就是边界对象;另一个是控制对象,即是控制业务流程的对象,如下单服务就可以看作是控制对象;实体对象即是问题空间中的业务对象,比如订单。画健壮性图是有规则的,一般是边界对象调用控制对象,控制对象产生实体对象,比如用户下单界面是边界对象,下单服务是控制对象,订单就是实体对象。


11.png

寻找对象之路


对象从哪里来


在本文第一部分第三小节中已经提到,问题空间到解空间是一一映射,我们讨论解空间中的对象时,其实它映射到问题空间中的对象,而问题空间中的对象主要来源于业务概念、业务规则、关键事件。大部分的对象是显现的,我们通过理解业务能发现,有的对象是隐性的,需要我们持续对业务有更深的理解才能发掘出来。好的对象模型是需要经过多次迭代打磨出来的,并非一次就能设计得十全十美。


发现对象的方法


在本文第二部分第二小节中已经提到寻找对象的方法,不过那还只是关键显现的对象,在本节中主要讲述完整对象发现的方法,主要方法分成四个步骤:


1. 通过健壮性图找到关键的实体对象;

2. 通过结构分析方法找出更多的实体对象;

3. 将对象组成有机的对象模型;

4. 最后通过用例走查对象模型是否完备。


12.png


这里以一个案例来说明发现对象的过程,案例是用户在下单时,在订单上展示税的金额。首先画出健壮性图,这里的边界对象是下单界面,控制对象有两个,一个是下单服务,另一个是计税服务,实体对象也有两个,一个是计税单,一个是订单。有了计税单和订单这两个实体对象后,接下来通过结构分析方法,分析出更多的对象。


13.png


对象都是有结构的,只要我们掌握了对象的结构,基本上就能掌握对象的概貌,因此我们从对象的结构入手,去分析对象内部的结构、对象关联的结构,实质上是从两个维度出发:一是从自身的角度出发,看自己内部还包含了哪些对象,如主订单包含了子订单;另一个是从外部的角度出发,看自己还与哪些对象相关联,如计税单与订单是有关联的。这种找对象的方法我称之为结构分析方法,因为本身结构又是事物本质的一种表达方式,比如化学分子结构决定化学现象。


为了更好地表达出对象的结构,我的一个经验是给对象下好定义,下定义可以从不同的维度,比如功能性维度、价值性维度、目的性维度、结构性维度等,这里可以从结构性的维度去给对象下定义。以计税单为例,可以给它下一个定义:计税单是将订单金额信息转成若干个标的物计税的单据模型,从这个定义中,我们可以看到计税单是与订单有关联关系的,另一个是计税单是包含了若干个标的物,我们可以画出计税单的对象模型。


14.png


当对象模型画出来后,后续我们讨论业务基本上围绕这个对象模型去讨论业务问题的,比如商品标的物哪些金额要参与计税、计税金额的计算口径是怎样的,到这里,大家再体会下"问题空间到解空间一一直接映射"这句话,业务上的诉求也无非是哪些订单费用项要计税,计税的逻辑是怎样的,有可能在这个场景下要扣减金本位优惠,在另外一种场景下金本位优惠不需要扣减,基于对象模型与产品、测试同学讨论问题,大家都是处于同一个维度的视角看问题,沟通理解成本会少很多。


对象模型是一种可视化的表达,我们大部分的沟通问题是缺乏显性表达造成的,这句话可以这样理解,也可以那样理解,导致大家理解有偏差,现在用模型的形式沟通问题,很多偏差、歧义就消除了。


组织对象结构


当我们分析出一堆的对象后,还需要经过一定的组织,正如前面提到,人对事物理解是有局限的,不能一下子接受太多的事物,因此可以将它们分成一个个小的域,比如商品域、订单域、税务域等,这样当聚集一个问题时,可以只看某个子域里的对象模型即可。


15.png


如何分配职责


职责是怎么来的


面向对象最难的点有两个:一个是找出对象;另一个是分配职责。UML 把职责定义为"类元的契约或义务",因此职责的划分从本质来讲还是类元本身决定的,比如订单,它要提供订单渲染、订单创建、订单修改、订单查询的义务。


职责分为两类:一类是认知职责;另一类是行为职责。


  • 认知职责包含:
  • 对私有数据封装的认知。
  • 对相关对象的认知。
  • 对其能够导出或计算的事物的认识。

 

  • 行为职责包含:
  • 自己执行的行为,包括创建对象或计算。
  • 初始化其它对象的动作。
  • 控制或协调其它对象的活动。


分配职责的逻辑


上一小节中提到的职责有两类,认知职责是对象自身的认知范围,即它只能基于自身属性完成相应的职责,举一个例子,假如一主多子的订单,要计算总的订单金额,怎么分配职责呢?首先商品只能查到自身价格的信息,它的认识是基于商品 price 属性,一个子订单可以有多个商品,那么它也只能计算出子订单的金额信息,它的认知是基于 item 和 quantity两个属性,主订单包含所有子订单的信息,那么就可以计算出总的订单金额。


16.png


从上面的例子中我们可以看出,认知职责是基于对象属性的,正所谓"不在其位、不谋其政",认知职责一定不会超过它的认识范围的。


行为职责是偏领域服务的,有的时候一个职责不属于某一个对象,比如转账,就是一个行为,让其它的职责承担并不合适,这类行为职责往往是一个显著的业务活动,比如订单渲染、订单创建就是行为职责而非认知职责。


分配职责一定要遵循"信息专家"模式,它的含义是将职责分配给具有完成该职责所需要信息的那个类,也即上面提到的认识产生职责。


验证职责分配的合理性


我们期望分配的职责满足"高内聚、低耦合",怎么检验呢?我们再回过头来思考职责的定义:类元的契约或义务,换句话讲,职责是满足其它对象来调用的,这个就与我们画时序图的目的是一致的,每次发生一次调用,即意味着其它的对象要提供一个职责出来,因此我们可以在时序图中看对象间的调用频次,如果一个对象被调用得非常频繁,有可能这个对象承担了太多的职责,是不是可以对其拆分,把职责分配一部分出去。因此,对象职责分配并不是一蹴而就的,需要不断审视、检验。


分配职责是要遵循一定的原则,如创建者模式、信息专家模式、纯虚构模式等,这些原则会在下一篇中单独去讲。


案例


案例背景


这里举一个例子,说明面向过程和面向对象在分析、编写代码的差异性,计税需要判断是否满足计税规则,比如虚拟商品不计税(手机充值之类)、有些免税地址不计税、小 B 买家也不计税等,因此需要提供一个计税过滤判断逻辑。


常规面向过程实现


面向过程的思路很简单,提供一个过滤方法依次处理下面逻辑:过滤虚拟商品计税请求、过滤免税地址计税请求、过滤小 B 买家计税请求。


17.png


public void filter(List<TaxCalculateRequest> request){
     // 过滤虚拟商品计税请求
     filterVirtualItem(request);
     // 过滤免税地址计税请求(即外岛)
     filterOuterIsland(request);
     // 过滤小B买家计税请求
     filterPurchaseType(reqeust);
}


面向对象实现


面向过程是从过程视角或者是功能视角分析问题,而面向对象是从对象的视角分析问题,过滤计税请求是计税过滤器判断计税请求是否满足计税规则,这里就包含了两个对象:计税过滤器和计税规则,判断是否满足计税要求这个职责应该是在具体的计税规则处理器中,比如是否是小 B 买家等,因此我们可以画出对象模型。


18.png


关键代码如下:


public abstract class AbstractRuleHandler {
    /**
     * 抽象的业务规则处理
     *
     * @param request
     */
    public abstract void handler(TaxCalculateRequest request);
    /**
     * 构造函数里完成注册
     */
    public AbstractRuleHandler() {
        TaxCaluclateFilter.register(this);
    }
}

总结


在文章中提到,面向对象的底层逻辑是基于现实事物做的抽象映射,重要的不是要面向对象具体技术的使用上,而是分析问题的思维上,这是最难的,它最大的好处是问题空间到解空间是一一直接映射的,请注意是一一直接映射,它意味着我们在讨论方案的时候,完全可以映射到问题空间,如果是间接映射,也就意味着设计的方案后面会面临重新设计的可能性,因为它是基于场景或功能做出的归纳设计,而且是表层的设计。真正掌握了面向对象分析和设计的方法,也体会到其中的益处,对理解业务、方案设计、编码开发都有好处。

相关文章
|
Cloud Native Dubbo 算法
微服务架构技术选型思考
在互联网+ 和新商业业态的冲击下,传统行业正处于技术架构转型的十字路口,随着业务的不断创新变化,服务架构也随之无时无刻地进行革新。从早期的单体应用架构、面向SOA架构以及现在的微服务架构,无不是随着业务场景的不同诉求而进行适应性架构变迁。基于当前行业的业务发展,天然基于云服务的云原生模式无疑能给出重要参考意义。然而如何落地云原生技术正逐步成为行业用户的焦点。作为云原生生态领域中的关键一员,微服务的一举一动牵动着整个生态的发展方向。
669 0
|
6月前
|
数据采集 调度 数据库
Python异步编程入门:asyncio让并发更简单
Python异步编程入门:asyncio让并发更简单
321 114
|
6月前
|
人工智能 监控 数据可视化
2025 年适合制造业、零售、汽车、互联网、电商的 BI 产品推荐
本文为 2025 年制造业、零售、汽车、互联网、电商行业 BI 产品选型指南,聚焦行业专属 BI 工具需求,基于 70 万 + 跨行业实战数据精选 5 款产品。核心介绍阿里云旗下瓴羊 Quick BI,其作为国内唯一连续 6 年入选 Gartner ABI 魔力象限的产品,服务超 5 万家企业,具备 “智能小 Q” AI 功能、40 + 可视化组件、0.3 秒 10 亿级数据处理能力,且有五大行业专项适配方案,搭配极氪、洋河股份等真实案例。同时介绍葡萄城 Wyn(制造业适配)、微软 Power BI(轻量通用)、Tableau(可视化强)、Sisense(嵌入式)四款产品。
|
11月前
|
容器
[HarmonyOS NEXT 实战案例十八] 日历日程视图网格布局(上)
日历是许多应用程序中常见的UI组件,用于展示日期和相关事件。在本教程中,我们将学习如何使用HarmonyOS NEXT的GridRow和GridCol组件实现一个简洁、美观的日历日程视图网格布局。
336 2
|
Python
Python pkgutil基础使用说明
pkgutil是Python的标准库中的一个模块,提供了一系列与包(Package)相关的工具函数,例如动态加载包、递归遍历包内的子模块等。在本篇教程中,我们将带你详细了解pkgutil模块的主要功能及使用方法。
502 2
2023年电赛---运动目标控制与自动追踪系统(E题)OpenART mini的代码移植到OpenMV
2023年电赛---运动目标控制与自动追踪系统(E题)OpenART mini的代码移植到OpenMV
498 0
|
项目管理
「软件项目管理」一文详解软件项目管理概述
该文章详细介绍了软件项目管理的关键概念、知识体系以及实施过程,涵盖了项目初始化、计划制定、执行控制到项目结束的全流程管理,并探讨了项目管理与过程管理在软件开发中的相互作用和应用。
「软件项目管理」一文详解软件项目管理概述
|
JavaScript 算法 前端开发
国标哈希算法基础:SHA1、SHA256、SHA512、MD5 和 HMAC,Python和JS实现、加盐、算法魔改
国标哈希算法基础:SHA1、SHA256、SHA512、MD5 和 HMAC,Python和JS实现、加盐、算法魔改
2019 1
|
项目管理 敏捷开发 Cloud Native
带你读《软件项目管理案例教程(第4版)》之一:软件项目管理概述
本书以案例形式讲述软件项目管理过程,借助路线图讲述项目管理的理论、方法及技巧,覆盖项目管理十大知识域的相关内容,重点介绍软件这个特殊领域的项目管理。本书综合了多个学科领域,包括范围计划、成本计划、进度计划、质量计划、配置管理计划、风险计划、团队计划、干系人计划、沟通计划、合同计划等的制定,以及项目实施过程中如何对项目计划进行跟踪控制。该书取材新颖,注重理论与实际的结合,通过案例分析帮助读者消化和理解所学内容,既适合作为高等院校计算机、软件及相关专业高年级本科生和研究生的教材,也适合作为广大软件技术人员和项目经理培训的教材,还可作为软件开发项目管理人员的参考书。
|
网络协议 算法 安全
【专栏】RIP是一种古老的内部网关协议,使用距离矢量算法,基于跳数更新路由表,最古老的距离矢量协议
【4月更文挑战第28天】RIP是一种古老的内部网关协议,使用距离矢量算法,基于跳数更新路由表。其工作原理包括周期性更新、度量标准、路由表更新和防止计数到无穷问题的技术。RIP简单易用,适合小规模网络,但在大规模网络中效率低且有限制。随着OSPF和EIGRP等协议的发展,RIP在大型网络中的应用减少,但在中小型网络和遗留系统中仍有其地位。RIPv2的改进提高了安全性与灵活性。尽管逐渐被替代,RIP在理解路由协议基本概念和历史中仍具价值。
743 1

热门文章

最新文章