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

news2024/11/16 9:43:48

作者:高福来   阿里全球化业务平台团队

在面向对象出现之前,已有面向过程的分析方法,那为什么面向对象被提出了呢?究其本质,人们发现面向过程并非按照人正常认识事物的方式去分析软件。面向过程是一种归纳的分析方法,由外到内的过程;面向对象是一种演绎的分析方法,由内到外的过程。本文将为大家分享面向对象分析与设计的底层逻辑。

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

1.1 人是怎么认识事物的

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

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

1.2 分类与分层的两种思维

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

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

1.3 问题域到解空间的映射

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

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

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

1.4 三个一致性

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

1.5 面向对象的底层逻辑

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

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

2.1 我们面临的问题是什么

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

1)做什么的问题

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

2)怎么做的问题

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

3)方法指导的问题

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

2.2 分析到设计的过程

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

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

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

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

三、寻找对象之路

3.1 对象从哪里来

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

3.2 发现对象的方法

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

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

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

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

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

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

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

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

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

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

3.3 组织对象结构

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

四、如何分配职责

4.1 职责是怎么来的

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

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

认知职责包含:

  • 对私有数据封装的认知;

  • 对相关对象的认知;

  • 对其能够导出或计算的事物的认识。

行为职责包含:

  • 自己执行的行为,包括创建对象或计算;

  • 初始化其它对象的动作;

  • 控制或协调其它对象的活动。

4.2 分配职责的逻辑

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

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

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

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

4.3 验证职责分配的合理性

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

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

五、案例

5.1 案例背景

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

5.2 常规面向过程实现

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

public void filter(List<TaxCalculateRequest> request){
     
     // 过滤虚拟商品计税请求
     filterVirtualItem(request);

     // 过滤免税地址计税请求(即外岛)
     filterOuterIsland(request);

     // 过滤小B买家计税请求
     filterPurchaseType(reqeust);

}

5.3 面向对象实现

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

关键代码如下:


public abstract class AbstractRuleHandler {

    /**
     * 抽象的业务规则处理
     *
     * @param request
     */
    public abstract void handler(TaxCalculateRequest request);

    /**
     * 构造函数里完成注册
     */
    public AbstractRuleHandler() {
        TaxCaluclateFilter.register(this);
    }
}

六、总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/129266.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

virtio技术(3)virtqueue机制

virtio技术&#xff08;3&#xff09;virtqueue机制 virtio的关键技术是virtqueue机制&#xff0c;其提供了一套统一的用于virito前端和后端的通信机制。virtqueue的核心数据结构是vring&#xff0c;这是virtio前端驱动和后端Hypervisor虚拟设备之间传输数据的载体。 vring数…

Word处理控件Aspose.Words功能演示:使用 Java 将 RTF 转换为 PDF

Aspose.Words 是一种高级Word文档处理API&#xff0c;用于执行各种文档管理和操作任务。API支持生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现和打印文档&#xff0c;而无需在跨平台应用程序中直接使用Microsoft Word。此外&#xff0c; Aspose API支持流行文件格式处…

改变podman的存储路径

使用podman容器时&#xff0c;podman会默认使用/var/lib/containers路径作为存储路径&#xff0c;可能会导致根磁盘空间占用过大&#xff0c;那如何修改podman的存储路径呢&#xff1f;本文将带你一起来探讨。 前几天公司的服务器根目录磁盘空间不足了&#xff0c;经过查找问题…

JAVA - fastjson 中 JSONObject 的顺序问题

目录 1. JSONObject 存在的默认排序问题一 1.1. 解决方案一 1.2. 解决方案二 2. JSONObject 存在的默认排序问题二 2.1. 解决方案一 2.2. 解决方案二 在使用 fastjson 中的 JSONObject 有时候会遇到数据顺序发生了变化&#xff0c;而实际需求中需要保持原有的顺序。 1…

【软件测试】测试人的巅峰?测试专家?

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 经常有人谈到&#…

如何实现全网置灰?CSS3来解决

文章目录前言正文网站示例核心代码前言 当发生大事时&#xff0c;部分小伙伴会发现&#xff1a;“怎么某APP是灰的&#xff1f;不会是手机出问题了吗&#xff1f;” 然后再打开其他APP&#xff0c;发现&#xff1a;“都是灰的啊&#xff01;明白了&#xff0c;看来是有大事发生…

UKF 无迹卡尔曼滤波

目录参考&#xff1a;UKF数学原理&#xff1a;UKF的基本非线性系统描述&#xff1a;计算sigma point和权重参数UKF的基本预测步和更新步&#xff1a;UKF代码实现&#xff1a;参考&#xff1a; UKF数学原理&#xff1a; UKF的基本非线性系统描述&#xff1a; The UKF takes i…

vue element-ui 手机号校验 验证码校验 获取验证码倒数60秒无样式实现模板

上一篇其实发过了。。。 但是实在真的是太丑了 丑到自己看不下去了 加个对话框好看很多&#xff0c;再发一次 原链接为&#xff1a;https://blog.csdn.net/ZZDT099/article/details/128496693?spm1001.2014.3001.5502 <template><el-dialog title"校验手机号…

算法:反转图像(旋转的矩阵)

前言 今天要介绍的是一个较为经典的算法题&#xff1a;反转图像或者旋转矩阵。这道题的原题是Leetcode上的一道题&#xff0c;在题库序号为48。具体内容粘贴如下&#xff1a; 这种题目就是一个典型的倒置矩阵的思路&#xff0c;大体内容就是将一个矩阵逆向反转90度。首先针对…

Spring Boot学习篇(四)

Spring Boot学习篇(四) 1 BLOB(二进制大类型) 1.1 创建tb_blob表,其sql语句如下所示 CREATE TABLE tb_blob(id number primary key,fname VARCHAR2(50) NOT NULL,f blob )1.2 在entity包下面创建TbBlob实体类 package com.zlz.entity;import lombok.AllArgsConstructor; im…

【嵌入式】NXP/LPC使用GPIO+定时器模拟UART串口接收

目录 一 项目背景 二 原理说明 三 设计实现--GPIO部分 四 设计实现--定时器部分 五 总结 一 项目背景 项目需要使用485串口编码器&#xff0c;编码器的数据以波特率9600持续向外发送。接收端计划使用485转换芯片MCU串口。但是片上的外设资源已经被占用了&#xff0c;没有多…

19.删除链表的倒数第N个结点

给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 示例 3&#…

车辆未冲洗抓拍识别 工地车辆冲洗监测 opencv

车辆未冲洗抓拍识别 工地车辆冲洗监测系统t通过opencvpython可以对进出车辆冲洗情况进行自动识别&#xff0c;发现冲洗不合格自动进行抓拍存档。OpenCV基于C实现&#xff0c;同时提供python, Ruby, Matlab等语言的接口。OpenCV-Python是OpenCV的Python API&#xff0c;结合了Op…

如何对美国服务器响应速度进行优化

决定一个网站加载速度的最大因素之一是服务器的响应时间。服务器响应时间是你的服务器响应用户请求的速度&#xff0c;它可以大大影响你网站的用户体验。本文中&#xff0c;我们将讨论如何确定美国服务器响应时间慢的原因&#xff0c;尤其是如何对美国服务器响应速度进行优化。…

初探Lua脚本

1、什么是Lua Lua脚本是一个由C语言编写的小巧脚本语言&#xff0c;在所有脚本引擎中&#xff0c;Lua的速度是最快的。Lua的核心代码不过一万多行&#xff0c;因为是C语言编写的&#xff0c;因此Lua可以在几乎所有的操作系统和平台进行编译运行 2、Lua适用场景 1&#xff09;…

minio分布式集群部署

minio分布式集群部署 分布式 Minio 可以让你将多块硬盘或者多台服务器组成一个对象存储服务。由于硬盘分布在不同的节点上&#xff0c;分布式 Minio 避免了单点故障。MinioMinio分布式模式可以帮助你搭建一个高可用的对象存储服务&#xff0c;你可以使用这些存储设备&#xff…

七种分布式系统的解决方案,一次性讲给你听!

V-xin&#xff1a;ruyuan0330 获得600页原创精品文章汇总PDF 目录 TB级数据放在一台机器上&#xff1a;难啊&#xff01;到底啥是分布式存储&#xff1f;那啥又是分布式存储系统呢&#xff1f;天哪&#xff01;某台机器宕机了咋办&#xff1f;Master节点如何感知到数据副本消失…

nps内网穿透

nps服务端: linux, 公网ip npc客户端: windows, 内网 文件提取 链接&#xff1a;https://pan.baidu.com/s/1HgujpVoXpLxQ-IgAnI2Izg 提取码&#xff1a;8hyl nps安装 1.上传压缩包到服务器, 解压 2.修改conf文件夹下nps.conf文件 #HTTP(S) proxy port, no startup if em…

vue3 antd项目实战——Form表单使用【v-model数据的双向绑定,form表单嵌套input输入框、Radio单选框】

vue3 ant design vue项目实战——单选框&#xff08;Radio&#xff09;的使用以及Form表单的双向绑定知识调用&#xff08;form表单的源代码附在文章最后&#xff09;场景复现实现需求form表单整体架构的搭建input输入框文本域的嵌套单选组合Radio的嵌套button按钮组合的嵌套fo…

JVM 面试题

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java面试题…