java对象扩展方法_高可扩展的面向对象代码架构是如何设计的

导语

Java后端程序员的日常工作,大多数可能都是写基于数据库CRUD的Dao层、Manager层、Service层、Controller层,需求来了,就对着这几个层一顿怼代码、调试跑通了,就完事了。分层确实是一个支持扩展性的好东西,它可以支持我们隔离不同层面的变化(变化点分离),从而达到可扩展、可复用的目的(比如多个Controller可以复用多个Service)。

对于一些简单的增删查改逻辑操作,分层确实可以比较好地支持我们的基本业务需求了;但是,如果遇到比较复杂、多变化点的逻辑流程,如果还是基于分层的思路去实现代码,那一个层(主要逻辑代码所在的层,比如Service层)里的代码量可能就就会非常大,日后的维护也会变得艰难,经过多次迭代修改后很容易出现代码腐化。

其实很容易可以看得出,虽然我们用的是Java语言,但基于分层的编码方式,其实主要还是在面向过程编程,只不过是把不同的过程“内聚”在不同层的类里而已。

怎么解这个问题呢?这时候很多同学的脑袋里就很容易会蹦出:面向对象、设计模式等词,但是具体应该如何操作呢?

《面向对象葵花宝典 思想、技巧与实践》这本书很好地把面向对象、设计原则、设计模式结合起来,通俗易懂地教我们怎么推导设计出一个高可扩展的代码架构,以下为个人读书总结:

面向对象面向过程:有一套设计严格的操作顺序,有一个类似中央控制器的角色来进行统一调度

面向对象:没有明确的中央控制的角色,也不需要指定严格的操作顺序,而是设计了很多对象,并且指定了这些对象需要完成的任务,以及这些对象如何对外界的刺激做出反应。

面向对象思想的适用范围面向过程的方法主要是按逻辑流程,流水式地写代码。每次需求的变更,可能都要对逻辑流程的多个步骤进行修改,这样的代价在软件系统中几乎是不可接受的,因为每次修改都需要对全系统改动一次,不但工作量会大大增加,同时风险也在大大增加

而面向对象正是为了解决面向过程的这个缺点诞生的。面向对象的方法可以将变化带来的影响控制在有限的范围内,避免产生全流程或大范围的影响,从而降低风险。面向对象思想的核心就是“可扩展性”,因此,经常变化的地方就是面向对象应用的地方。对于软件系统来说,常见的可变的主要集中在客户需求的部分,而不变的一般属于计算机系统的基础。

操作系统、数据库、协议(TCP、3GPP等)这些地方并不合适面向对象大展身手,因为这些软件基础一般都比较稳定(相对稳定,并不是不变)。

对于企业应用、互联网、游戏等应用,需求经常变更,功能不断扩展,这正是面向对象大展身手的地方

面向对象理论类:具有相似点的事物就是同一类设计方法基本原则:方法单一化原则。即“一个方法只做一件事”

设计属性基本原则:属性最小化原则。即“属性不可拆分”

属性:类具有的特性

方法:类具有的功能

对象:一个具体的类

接口:一组相关的交互功能点定义的集合用处:你不知道一个对象所属的具体“类”,只知道这些对象都具备某种功能

抽象类:基于类而抽象出来的,只能用于继承,而不能用于实例化抽象类与接口的区别:抽象类本质还是类,强调一组事物的相似性,包括属性和方法的相似性;接口只强调方法的相似性,并且仅仅体现在方法声明上的相似性,而没有方法定义实现上的相似性

抽象:“抽取比较像的地方出来”。抽象的主要目的是划分类别;隔离关注点,降低复杂度

面向对象三大核心特征:封装:保护隐私、隔离复杂度

继承:抽象和继承是前后衔接的关系,先有抽象,通过抽象得出类,后通过继承来表达抽象结果

多态:使用指向父类的指针或引用,能够调用子类的对象。当增加新的子类时,调用者的代码无须变动就能适用新的子类

面向对象分析和设计流程

需求模型

通过和客户沟通,结合行业经验和知识,明确客户的需求需求:对客户来说有价值的事情

功能:系统为了实现客户价值而提供的能力

需求分析要时刻抓住“客户的问题和价值”这个指导思想:“挖掘客户的问题,实现客户价值”

需求分析方法:518:5W:When、Where、Who、What(客户想要的到的输出)、Why(客户提出需求的驱动力:遇到的问题、困难、阻碍等)。最关键的是why,只有解决了客户的问题,客户才会真正满意

1H:How(用例方法,描述整个流程如何运行)

8C:8个Constraint。包括性能(Performance)、成本(Cost)、时间(Time)、可靠性(Reliability)、安全性(Security)、合规性(Compliance)、技术性(Technology)、兼容性(Compatibility)

用例方法:描述需求的流程【用例名称】

【场景】:5W中的3个W:Who、Where、When

【用例描述】:5W中的What和How,即用户应该怎样做,以及每个步骤步中的输出

【用例价值】:5W中的Why

【约束与限制】:8C

需求用例分析方法:用例方法三段法(NEA方法):其他了解客户需求的方法:调查问卷、头脑风暴、数据分析、数据挖掘、竞争对手分析等

正常处理(Normal):通过和客户沟通,分析需求的正常流程

异常处理(Exception):在正常处理流程的步骤上,分析每一步的各种异常情况和对应的处理

替代处理(Alternative):在正常处理流程的步骤上,分析每一步是否有其他替代方法,以及替代方法如何做

提取功能:将用例中需要系统完成的事情(动词)提取出来,就成为了系统的功能

将不同用例中的相同功能合并,得到功能描述表

画出系统顺序流程图

领域模型

基于需求模型,提炼出领域相关的概念发掘重要的业务领域概念;建立业务领域概念之间的关系

从需求用例中找名词、加属性、连关系

更多领域建模方法可参考:问题空间领域模型基本抽象方法

设计模型

以领域模型为基础,综合面向对象的各种设计技巧,完成类的设计静态模型:类模型。主要关注系统的“静态”结构,描述系统包含的类,以及类的名称、职责、属性、方法,类与类之间的关系第一步:领域类映射软件类的属性、类关系:从领域类(只有属性、类关系,没有方法)映射;

软件类的方法:从需求用例中提炼出动词,分配给已经有了属性的软件类

软件类与领域模型类相比,部分领域类被剔除,留下来的领域类映射成软件类后,又增加了方法

第二步:应用设计原则和设计模式(保证“可扩展性”)设计之道:设计模式:找到变化,封装变化Doing:设计模式一句话总结​zhuanlan.zhihu.comb8a4f89fdebd80e674fbe2e277c70f8d.png先找到可能变化的地方,再来看具体使用哪个模式可以封装这种变化

找到变化:解决了“在哪里”使用设计模型的问题,即回答了“where”的问题

如果你不知道什么会变化,就抱着怀疑一切的想法,一切都可能是变化的,找到“有限时间内可能发生的变化”

封装变化:解决了“为什么”使用设计模型的问题,即回答了“why”的问题

“封装变化”意味着将变化的影响范围控制到最小,将风险降到最低

“封装变化”将变化封装起来,使其只在有限的范围内有影响

设计原则:(1)基于接口编程,而不是基于实现编程

(2)优先使用对象组合而不是类继承

(3)开闭原则(OCP,Open-Closed Principle):总的指导思想,如果能符合LSP/ISP/DIP原则,一般就能符合OCP原则了

模块应尽量在不修改原代码(闭)的情况下进行扩展(开)。在程序需要进行拓展的时候,不用去修改原有的代码,实现一个热插拔的效果。

对使用者修改关闭,对提供者扩展开放。即:提供者增加新的功能,但使用者不需要修改代码。

系统和系统、子系统和子系统、模块和模块之间都可以应用开闭原则:通过接口交互

类和类之间——使用interface进行交互

模块和模块、系统和系统之间——使用规定好的协议交互,比如HTTP、SOAP等

(4)单一职责原则(SRP,Single Responsibility Principe):指导类的设计

每个类只负责一组相关的事情

(5)里氏代换原则(LSP,Liskov Substitution Principe):指导类继承的设计

如果调用的是父类的话,那么换成子类也完全可以运行。 派生类能够在基类的基础上增加新的行为。只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用。

实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

(6)接口隔离原则(ISP,Interface Segregation Principle):指导接口的设计

接口的功能要内聚,每一个接口应该是一种角色,不干不该干的事,该干的事都要干。

调用接口的类可以根据自己的需要精确使用某个接口,而不是调用一个大而全的接口。降低类之间的耦合度。

(7)依赖反转原则(DIP,Dependency Inversion Principle):指导如何抽象

1、高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。

2、抽象不应该依赖于具体实现,具体实现应该依赖于抽象。要求面向抽象/接口进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

(8)通过抽象/接口交互;依赖抽象/接口,而不依赖具体实现。

最少知识原则:一个对象应对其它对象有尽可能少的了解。即一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

(9)高内聚、低耦合

内聚:当模块(函数、类、包、子系统)的元素全部都专注于模块的职责的时候,即使元素间的结合不是很紧密,也是符合内聚性的要求的。(这也是CRUD设计符合内聚性的原因)

(10)避免过度设计

设计原则主要用于指导“类的定义”的设计(类的静态设计原则)

设计模型主要用于指导“类的行为”的设计(类的动态设计原则)

一般情况下,“先设计原则,后设计模型”

第三步:拆分辅助类拆分辅助类的主要目的是为了使我们的类在编码的时候能够满足一些框架或规范要求。比如常见的MVC模型,将一个业务拆分成Control、Model、View三个元素;在J2EE模式中,将对象分为PO、BO、VO、DTO等众多对象。

拆分辅助类仅仅是为了满足框架或者规范的要求,仅仅在编码的时候拆分即可,所以一般不需要将拆分的辅助类体现在类模型中。

做法:将设计出来的类,按照规范要求,一一对应拆分。

例子:POS机使用的场景里有一个“购物卡”类,假如框架要求提供DAO对象,负责数据库的相关操作,则“购物卡”类就应该拆分为两个:“购物卡”和“购物卡DAO”,其中“购物卡”用于负责提供支付功能给“交易”类调用;“购物卡DAO”用于负责从数据库读取购物卡信息,修改数据库中的购物卡余额等操作

动态模型:表达方式:主要是UML建模里的工具:状态模型、活动模型、序列模型、协作模型的等

动态模型可以将复杂的业务用模型表示出来,其根本作用是便于我们去思考和理解实现过程,如果业务比较简单,就很容易理解,就没有必要设计动态模型了

关注系统的“动态”行为,描述类本身的一些动作或状态变化,以及类之间如何配合以完成最终的业务功能。

实现模型

以设计模型为基础,将设计模型翻译为具体的编程语言实现(Java等),完成编码

参考来源:《面向对象葵花宝典 思想、技巧与实践》

本文首发于微信公众号:EnjoyMoving

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值