软件架构设计的阐述

91 阅读8分钟

我们为什么需要面向对象编程

面向对象编程是一种编程范式,它将程序设计的实体归结为具有自身状态和行为的实际事务。它提供了一种更直观,更有效的组织方式来处理复杂的软件设计问题。通过封装,继承,多态三个特性,OOP允许开发者模拟真实世界的实体或概念,从而创造出易于管理和扩展的代码结构。它的主要优点如下:

代码复用和扩展性:通过继承,新创建的类可以利用现有类的功能,类的方法可以被子类继承和扩展,这提高了代码的可复用性,减少代码冗余。软件开发规范中,有一个重要的原则那就是 Do not repeat yourself,这也正是OOP思想的重要体现。

简化复杂性:面向对象编程通过将问题分解为独立的对象,使得复杂的问题更加具体化,每个对象都负责自己的任务(单一职责原则的体现),不需要关注其他对象的具体逻辑,这种封装和抽象性有助于减少系统的复杂性。

提高代码的可维护性: 面向对象编程使得代码易于维护,由于代码被划分为模块化对象,所以在进行修改时,只需要关注特定的对象。这种模块化设计是的代码更便于理解和调试,也更容易进行版本的控制和合作。

与OOP相对应的是面向过程编程,也可以称作记流水账代码,我们在组织自己的代码结构时,要从根本上避免这种流水账代码,要充分发挥OOP的优势才能持续保证软件的可维护性和可拓展性,这样才能保证我们软件项目的活力和价值。

有关流水账代码的解释可以参考:developer.aliyun.com/article/783…

这里可以适当展开一点进行阐述:软件相比于硬件的优势,或者说软件之所以称之为"软件",就是在于我们可以以较低的成本,不断拓展软件的功能,一旦软件失去了可维护和可拓展的特性,那么该软件就将逐渐失去它的价值。我们要始终以这样的方式考虑我们的软件:软件的可维护性随着时间的推移应该是线性相关的,而不是几何级的相关;简单来说就是软件的可维护性一直是可预测的,而不能因为不断的迭代导致可维护性难度成倍的增加。有关更多的论述,可以参考Bob大叔的《代码整洁之道》一书。

所以我们需要我们的代码遵循一些规范,以保证我们软件的“活力”,这就是我们下面讨论面向对象设计思维的意义。

至于我们常听到的23种设计模式与面向对象设计的SOLID原则,或者是当下较为流行的DDD思想,其实都是要求我们要以OOP的思想去设计软件,或者这么说,它们都是以OOP为基础进行更进一步的拓展和加工形成的思想。

面向对象设计的基本原则

单一职责原则: Single Responsibility Principle,简称SRP.这话意思是一个类只担负一个职责,不应该过多的将代码逻辑揉在一个类里。单一职责原则可以说是5个基本原则里最为抽象和最难把控的一点,这儿给出几点参考建议:

  1. 假如有这样一个功能,用户可以通过银行网页转账给另一个账户,并且支持跨币种转账;拿到这个需求之后,我们进一步拆解需求如下:
  • 从我们的账户表中找到转入和转出账户(根据用户id和对方用户的id)

  • 从第三方渠道提供的汇率服务获取转账的汇率信息

  • 计算需要转出的金额,确保账户有足够余额

  • 实现转账操作保存到数据库. 这儿如果使用上述的流水账代码实现的话,技术上是没有任何难度的;但这种流水账代码结构存在的问题,可能需要单写一篇文章进行阐述。我们这儿只讨论如果我们使用单一职责原则的话,应该如何组织代码结构;为了节省篇幅,这儿我们可以简单的将这些需求拆解为不同类去实现,如:

  • 转入和转出的账户查询类---AccountRepository

  • 获取第三方汇率信息类----ExchangeRateService

  • 计算转账主类----AccountTransferService等,

这儿其实想要阐述的是:如何让定义类的单一职责,应该宏观的考虑实现的需求,我们如何规划我们的实现方案,才能更好的保证我们代码的可读性和可复用性,其中可复用性是衡量类的单一职责原则是否到位的重要指标。

2.当一个类内部有过多的依赖时,我们就要考虑我们是否要将这些逻辑拆分到不同的类中,以保证类的职责单一性。

3.我们在定义类的名字时,就要考虑该类的具体职责是是什么,类的名称应该尽量具体的体现该类的职责,这儿就牵扯到类的命名规范问题。例如上述的ExchangeRateService类名很容易就定义了类的职责是什么。我们在定义类的名字时,应尽量避免使用模糊不清的词汇,例如:UserService,AccountService这种类的名称太过于宽泛,会误导软件开发者将有关User和Account的逻辑全部揉在这些类中;这儿还需要强调的一点是:因为spring框架的流行,很多开发者会将大部分逻辑揉在XXXXService下,这种三层代码逻辑架构是十分不可取的,会极易将代码结构变成流水账代码,所以从现在开始,要摒弃三层结构的代码结构束缚,用更加规范的代码结构实现我们的需求

开闭原则: 这儿主要强调的是我们要通过扩展来实现需求或需求的变化,而不是修改已有的代码。遵循开闭原则的基础是,我们类的职责划分的很清晰;实现开闭原则的手段是面向抽象编程;我们在设计之初时 就要考虑到潜在的变化因素,用不同的实现细节去实现已有的抽象。

当然,实现开闭原则有时是很困难的,我们要不断的重构我们的代码。这儿并不是说已有类里面的逻辑,我们不能修改,如果我们遇到相应的bug,我们也不得不修改;这儿个人认为可以有一个更加"变通"的说法:我们修改一个类的时候,是否有一个充足的理由:

  • 这个类是非修改不可吗?
  • 是不是我们的抽象没有做到位导致的修改?
  • 是不是类的单一职责没有划分清楚?
  • 是不是bug必须得修改?
  • 是不是因为需求的增加导致的?如果是需求的增加我们是否可以通过扩展实现呢?

当你觉得代码需要重构时,不要犹豫,Do it now!

依赖倒置原则: 它的定义是高层模块应该依赖底层模块的抽象。这其实就是我们上面提到的面向抽象/接口去编程;依赖倒置原则给了我们实现开闭原则的机会,让我们的软件系统有了更多拓展的切入口。

接口隔离原则:客户端需要什么接口就提供什么接口,不应该依赖它不需要的接口。这个原则就是要强调我们在设计我们的接口类时,要保持简洁性,不要强迫实现类去实现它本不需要的接口。

里氏替换原则:通俗点说,只要父类能出现的地方,替换为子类的时候不会产生任何异常。因为继承有很多缺点,当子类继承父类时,虽然可以复用父类的代码,但是父类的属性对子类都是透明的,这在一定程度上就违反了封装的原则,解决方案就是引入里氏替换原则。该原则为良好的继承定义了一些规范,它包含了几个含义:

1.子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法; 2.子类可以有自己的个性,可以有自己的属性和方法。