软件建模基础

摘录自某PPT

文章目录

  • 软件建模基础
    • 0 软件质量属性
      • 0.1 如何评价代码质量
      • 0.2 软件质量属性
    • 1 面向对象
      • 1.0 面向对象知识点
      • 1.1 面向对象四大特性
        • 1.1 (封装)
        • 1.1 (抽象)
        • 1.1 (继承)
        • 1.1 (多态)
      • 1.2 面向对象 VS 面向过程
      • 1.3 OOA、OOD、OOP
      • 1.3 面向对象设计
      • 1.4 接口 VS 抽象类
      • 1.5 基于接口而非实现编程
      • 1.6 多用组合少用继承
      • 1.7 贫血模型 VS 充血模型
    • 2 设计原则、设计模式、编程规范、重构
      • 2.1 设计原则
      • 2.2 设计模式
      • 2.2 设计模式 —— 创建型
      • 2.2 设计模式 —— 结构型
      • 2.2 设计模式 —— 行为型
      • 2.3 编程规范
      • 2.4 代码重构
      • 2.5 五者之间的联系
    • 3 静态建模
      • 3.1 静态建模
      • 3.2 二元关联
      • 3.3 三元关联、一元关联
      • 3.4 组合和聚合
      • 3.5 泛化/特化
      • 3.6 UML2.5
    • 4 对象生命周期
      • 4.1 对象的概念
      • 4.2 对象创建
      • 4.3 对象创建的管理
      • 4.4 对象应用之对象角色
      • 4.4 对象应用之对象依赖
      • 4.5 对象销毁
    • 5 架构、框架、模式、系统、模块、组件
      • 5.1 架构(Architecture)
      • 5.1 软件架构
      • 5.2 框架(Framework)
      • 5.3 模式(Pattern)
      • 5.4 系统(System)
      • 5.5 模块(Module)
      • 5.6 组件(Component)
      • 5.7 插件(Plug-in、Add-in)
      • 5.8 控件、中间件
      • 5.9 架构、框架、组件、模块、系统
    • 6 DDD基本概念
      • 6.1 什么是DDD
      • 6.2 Ubiquitous language (通用语言)
      • 6.3 Layered architecture(分层架构)
      • 6.4 依赖倒置的四层架构
      • 6.5 DDD相关概念
      • 6.6 Entity、Value Object
      • 6.7 Service、Module
      • 6.8 Aggregate、Factory
      • 6.9 Repository、Sepcfication
      • 6.10 Bounded context
      • 6.11 贫血模型
      • 6.12 充血模型
      • 6.13 领域模型
      • 6.14 领域模型(理论派)
      • 6.15 实现步骤

软件建模基础

设计模式 | 菜鸟教程
软件设计模式概述 | C语言中文网

0 软件质量属性

0.1 如何评价代码质量

    1. 可维护性(maintainability)
      对于一个项目来说,维护代码的时间远远大于编写代码的时间。“代码不易维护”就是指,修改或者添加代码需要冒着极大的引入新 bug 的风险,并且需要花费很长的时间才能完成。
    1. 可读性(readability)
      “Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”
      代码的可读性在非常大程度上会影响代码的可维护性。
    1. 可扩展性(extensibility)
      代码预留功能扩展点,可以把新功能代码,直接插到扩展点上,而不需要因为要添加一个功能而大动干戈。
    1. 灵活性(flexibility)
      灵活性是一个抽象的评价标准。
      如果一段代码易扩展、易复用或者易用,我们都可以称这段代码写得比较灵活。
    1. 简洁性(simplicity)
      “Keep It Simple,Stupid”
      代码简单、逻辑清晰,也就意味着易读、易维护。
      思从深而行从简,真正的高手能云淡风轻地用最简单的方法解决最复杂的问题。
    1. 可复用性(reusability)
      DRY(Don’t Repeat Yourself)
      可复用性是很多设计原则、思想、模式等所要达到的最终效果。
    1. 可测试性(testability)

0.2 软件质量属性

(Bass,Clements,and Kazman 2003)

  • 可维护性(maintainability)在软件部署之后它能够被更改的程度。
  • 可修改性(modifiability)在最初开发期间和最初开发之后软件能够被修改的程度。
  • 可测试性(testability)软件能够被测试的程度。
  • 可追踪性(traceability)每一个阶段的产品能够被追踪到上一个阶段产品的程度。
  • 可伸缩性(scalability)在最初部署之后系统能够成长的程度。
  • 可复用性(reusability)软件能够被复用的程度。
  • 性能(performance)系统满足其性能目标的程度,例如吞吐量和响应时间。
  • 安全性(security)系统抵御安全威胁的程度。
  • 可用性(availability)系统能够解决系统失效问题的程度。

1 面向对象

1.0 面向对象知识点

  • 面向对象的四大特性:封装、抽象、继承、多态
  • 面向对象编程与面向过程编程的区别和联系
  • 面向对象分析、面向对象设计、面向对象编程
  • 接口和抽象类的区别以及各自的应用场景
  • 基于接口而非实现编程的设计思想
  • 多用组合少用继承的设计思想
  • 面向过程的贫血模型和面向对象的充血模型

1.1 面向对象四大特性

1.1 (封装)

封装(Encapsulation)封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据。

封装的意义:如果我们对类中属性的访问不做限制,那任何代码都可以访问、修改类中的属性,虽然这样看起来更加灵活,但从另一方面来说,过度灵活也意味着不可控,属性可以随意被以各种奇葩的方式修改,而且修改逻辑可能散落在代码中的各个角落,势必影响代码的可读性、可维护性。除此之外,类仅仅通过有限的方法暴露必要的操作,也能提高类的易用性。如果我们把类属性都暴露给类的调用者,调用者想要正确地操作这些属性,就势必要对业务细节有足够的了解。而这对于调用者来说也是一种负担。

1.1 (抽象)

抽象(Abstraction)隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。我们常借助编程语言提供的接口类(interface)或者抽象类(abstract)这两种语法机制,来实现抽象这一特性。

抽象的意义:抽象及其前面讲到的封装都是人类处理复杂性的有效手段。在面对复杂系统的时候,人脑能承受的信息复杂程度是有限的,所以我们必须忽略掉一些非关键性的实现细节。很多设计原则都体现了抽象这种设计思想,比如基于接口而非实现编程、开闭原则、代码解耦等。我们在定义(或者叫命名)类的方法的时候,也要有抽象思维,不要在方法定义中,暴露太多的实现细节,以保证在某个时间点需要改变方法的实现逻辑的时候,不用去修改其定义。

1.1 (继承)

继承(Inheritance)继承是用来表示类之间的 is-a 关系,从继承关系上来讲,继承可以分为两种模式,单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类,比如猫既是哺乳动物,又是爬行动物。但在Java和.Net等只能单继承,更提倡面向接口编程。

继承的意义:继承最大的一个好处就是代码复用。假如两个类有一些相同的属性和方法,我们就可以将这些相同的部分,抽取到父类中,让两个子类继承父类。通过继承来关联两个类,反应真实世界中的这种关系,非常符合人类的认知,而且,从设计的角度来说,也有一种结构美感。不过,过度使用继承,继承层次过深过复杂,就会导致代码可读性、可维护性变差。

1.1 (多态)

多态(Polymorphism)顾名思义,一个接口,多种形态。子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。同样是一个绘图(draw)的方法,如果以正方形调用,则绘制出一个正方形;如果以圆形调用,则画出的是圆形。

多态的意义:只使用封装和继承的编程方式,称之为基于对象(Object Based)编程,而只有把多态加进来,才能称之为面向对象(Object Oriented)编程。多态特性能提高代码的可扩展性和复用性。除此之外,多态也是很多设计模式、设计原则、编程技巧的代码实现基础,比如策略模式、基于接口而非实现编程、依赖倒置原则、里式替换原则、利用多态去掉冗长的 if-else 语句等等。

1.2 面向对象 VS 面向过程

面向对象和面向过程两种编程风格并不是非黑即白、完全对立的。在用面向对象编程语言开发的软件中,面向过程风格的代码并不少见,甚至在一些标准的开发库(比如 JDK、Apache Commons、Google Guava)中,也有很多面向过程风格的代码。
不管使用面向过程还是面向对象哪种风格来写代码,我们最终的目的还是写出易维护、易读、易复用、易扩展的高质量代码。只要我们能避免面向过程编程风格的一些弊端,控制好它的副作用,在掌控范围内为我们所用,我们就大可不用避讳在面向对象编程中写面向过程风格的代码。

1.3 OOA、OOD、OOP

面向对象分析就是要搞清楚做什么,面向对象设计就是要搞清楚怎么做,面向对象编程就是将分析和设计的的结果翻译成代码的过程。

需求分析的过程实际上是一个不断迭代优化的过程。我们不要试图一下就给出一个完美的解决方案,而是先给出一个粗糙的、基础的方案,有一个迭代的基础,然后再慢慢优化。

面向对象设计和实现要做的事情就是把合适的代码放到合适的类中。至于到底选择哪种划分方法,判定的标准是让代码尽量地满足“松耦合、高内聚”、单一职责、对扩展开放对修改关闭等我们之前讲到的各种设计原则和思想,尽量地做到代码可复用、易读、易扩展、易维护。

面向对象分析的产出是详细的需求描述。

面向对象设计的产出是类。

1.3 面向对象设计

划分职责进而识别出有哪些类 根据需求描述,我们把其中涉及的功能点,一个一个罗列出来,然后再去看哪些功能点职责相近,操作同样的属性,可否归为同一个类。
定义类及其属性和方法 我们识别出需求描述中的动词,作为候选的方法,再进一步过滤筛选出真正的方法,把功能点中涉及的名词,作为候选属性,然后同样再进行过滤筛选。
定义类与类之间的交互关系 UML 统一建模语言中定义了六种类之间的关系。它们分别是:泛化、实现、关联、聚合、组合、依赖。我们从更加贴近编程的角度,对类与类之间的关系做了调整,保留了四个关系:泛化、实现、组合、依赖。
将类组装起来并提供执行入口 我们要将所有的类组装在一起,提供一个执行入口。这个入口可能是一个 main() 函数,也可能是一组给外部用的 API 接口。通过这个入口,我们能触发整个代码跑起来。

1.4 接口 VS 抽象类

抽象类是对成员变量和方法的抽象,是一种 is-a 关系,是为了解决代码复用问题。

接口仅仅是对方法的抽象,是一种 has-a 关系,表示具有某一组行为特性,是为了解决解耦问题,隔离接口和具体的实现,提高代码的扩展性。

什么时候该用抽象类?什么时候该用接口?实际上,判断的标准很简单。如果要表示一种 is-a 的关系,并且是为了解决代码复用问题,我们就用抽象类;如果要表示一种 has-a 关系,并且是为了解决抽象而非代码复用问题,那我们就用接口。

在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

1.5 基于接口而非实现编程

应用这条原则,可以将接口和实现相分离,封装不稳定的实现,暴露稳定的接口。上游系统面向接口而非实现编程,不依赖不稳定的实现细节,这样当实现发生变化的时候,上游系统的代码基本上不需要做改动,以此来降低耦合性,提高扩展性。

实际上,“基于接口而非实现编程”这条原则的另一个表述方式是,“基于抽象而非实现编程”。后者的表述方式其实更能体现这条原则的设计初衷。在软件开发中,最大的挑战之一就是需求的不断变化,这也是考验代码设计好坏的一个标准。

越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性,越能应对未来的需求变化。好的代码设计,不仅能应对当下的需求,而且在将来需求发生变化的时候,仍然能够在不破坏原有代码设计的情况下灵活应对。而抽象就是提高代码扩展性、灵活性、可维护性最有效的手段之一。

1.6 多用组合少用继承

继承是面向对象的四大特性之一,用来表示类之间的 is-a 关系,可以解决代码复用的问题。虽然继承有诸多作用,但继承层次过深、过复杂,也会影响到代码的可维护性。在这种情况下,我们应该尽量少用,甚至不用继承。

继承主要有三个作用:表示 is-a 关系、支持多态特性、代码复用。而这三个作用都可以通过组合、接口、委托三个技术手段来达成。除此之外,利用组合还能解决层次过深、过复杂的继承关系影响代码可维护性的问题。

尽管我们鼓励多用组合少用继承,但组合也并不是完美的,继承也并非一无是处。如果类之间的继承结构稳定,层次比较浅,关系不复杂,我们就可以大胆地使用继承。反之,我们就尽量使用组合来替代继承。除此之外,还有一些设计模式、特殊的应用场景,会固定使用继承或者组合。

1.7 贫血模型 VS 充血模型

很多Web 项目的业务开发,大部分都是基于贫血模型的 MVC 三层架构,我们称为传统的开发模式。之所以称之为“传统”,是相对于新兴的基于充血模型的 DDD 开发模式来说的。基于贫血模型的传统开发模式,是典型的面向过程的编程风格。相反,基于充血模型的 DDD 开发模式,是典型的面向对象的编程风格。

不过,DDD 也并非银弹。对于业务不复杂的系统开发来说,基于贫血模型的传统开发模式简单够用,基于充血模型的 DDD 开发模式有点大材小用,无法发挥作用。相反,对于业务复杂的系统开发来说,基于充血模型的 DDD 开发模式,因为前期需要在设计上投入更多时间和精力,来提高代码的复用性和可维护性,所以相比基于贫血模型的开发模式,更加有优势。

2 设计原则、设计模式、编程规范、重构

2.1 设计原则

  • SOLID 原则 -SRP(Single Responsibility Principle) 单一职责原则

  • SOLID 原则 -OCP(Open Closed Principle) 开闭原则

  • SOLID 原则 -LSP(Liskov Substitution Principle) 里式替换原则

  • SOLID 原则 -ISP(Interface Segregation Principle) 接口隔离原则

  • SOLID 原则 -DIP(Dependence Inversion Principle) 依赖倒置原则

  • DRY原则(Don’t Repeat Yourself)

  • KISS原则 (Keep It Simple,Stupid)

  • YAGNI原则 (You Ain’t Gonna Need It)

  • LOD法则(Law of Demeter)

2.2 设计模式

  • 创建型
    • 单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式、原型模式。
  • 结构型
    • 代理模式、桥接模式、装饰器模式、适配器模式、
    • 门面模式、组合模式、享元模式。
  • 行为型
    • 观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式、
    • 访问者模式、备忘录模式、命令模式、解释器模式、中介者模式。

设计模式 | 菜鸟教程
软件设计模式概述 | C语言中文网

2.2 设计模式 —— 创建型

  • 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 工厂模式:在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
  • 抽象工厂:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
  • 原型模式:用原型实例指定创建对象的类型,并通过拷贝原型创建新的对象。
  • 建造者模式:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

2.2 设计模式 —— 结构型

  • 代理模式:为其他对象提供一种代理以控制对这个对象的访问。
  • 桥接模式:把抽象化与实现化解耦,使得二者可以独立变化。
  • 装饰器模式:允许向一个现有的对象添加新的功能,同时又不改变其结构。
  • 适配器模式:是作为两个不兼容的接口之间的桥梁。
  • 门面模式:隐藏系统的复杂性,并向客户端提供了一个访问系统的接口。
  • 组合模式:又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。
  • 享元模式:用于减少创建对象的数量,以减少内存占用和提高性能。尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

2.2 设计模式 —— 行为型

  • 观察者模式:定义对象间的一种一对多的依赖关系,当被观察对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
  • 模板模式:一个抽象类公开定义了执行它的方法的方式/模板。
  • 策略模式:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
  • 职责链模式:为请求创建了一个接收者对象的链。
  • 迭代器模式:顺序访问集合对象的元素,不需要知道集合对象的底层表示。
  • 状态模式:对有状态的对象,把复杂的状态跳转提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
  • 访问者模式:主要将数据结构与数据操作分离。
  • 命令模式:将一个请求封装成对象,使发出请求的责任和执行请求的责任分割开。
  • 备忘录模式:保存一个对象的某个状态,以便在适当的时候恢复对象。
  • 解释器模式:提供了评估语言的语法或表达式的方式。
  • 中介者模式:降低多个对象和类之间的通信复杂性。

2.3 编程规范

编程规范主要解决的是代码的可读性问题。

编码规范相对于设计原则、设计模式,更加具体、更加偏重代码细节。

即便你可能对设计原则不熟悉、对设计模式不了解,但你最起码要掌握基本的编码规范:

  • 如何给变量、类、函数命名
  • 如何写代码注释
  • 函数不宜过长
  • 参数不能过多

《重构》《代码大全》《代码整洁之道》

2.4 代码重构

在软件开发中,只要软件在不停地迭代,就没有一劳永逸的设计。随着需求的变化,代码的不停堆砌,原有的设计必定会存在这样那样的问题。针对这些问题,我们就需要进行代码重构。

重构是软件开发中非常重要的一个环节。持续重构是保持代码质量不下降的有效手段,能有效避免代码腐化到无可救药的地步。

而重构的工具就是我们前面罗列的那些面向对象设计思想、设计原则、设计模式、编码规范。实际上,设计思想、设计原则、设计模式一个最重要的应用场景就是在重构的时候。虽然使用设计模式可以提高代码的可扩展性,但过度不恰当地使用,也会增加代码的复杂度,影响代码的可读性。

在开发初期,除非特别必须,我们一定不要过度设计,应用复杂的设计模式。而是当代码出现问题的时候,我们再针对问题,应用原则和模式进行重构。这样就能有效避免前期的过度设计。

2.5 五者之间的联系

  • 面向对象因为其具有丰富的特性(封装、抽象、继承、多态),可以实现很多复杂的设计思路,是很多设计原则、设计模式等编码实现的基础。
  • 设计原则是指导我们代码设计的一些经验总结,对于某些场景下,是否应该应用某种设计模式,具有指导意义。比如,“开闭原则”是很多设计模式(策略、模板等)的指导原则。
  • 设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。应用设计模式的主要目的是提高代码的可扩展性。从抽象程度上来讲,设计原则比设计模式更抽象。设计模式更加具体、更加可执行。
  • 编程规范主要解决的是代码的可读性问题。编码规范相对于设计原则、设计模式,更加具体、更加偏重代码细节、更加能落地。持续的小重构依赖的理论基础主要就是编程规范。
  • 重构作为保持代码质量不下降的有效手段,利用的就是面向对象、设计原则、设计模式、编码规范这些理论。

3 静态建模

3.1 静态建模

静态建模展示的是问题的静态结构视图,它不随时间的变化而变化。一个静态模型描述了被建模系统的静态结构,相比系统的功能,这些静态结构被认为不太会改变。特别地,静态模型定义了系统中的类、类的属性、类的操作及类之间的关系。

类之间的关系,有三种类型:

  • 关联
  • 整体/部分(组合和聚合)关系
  • 泛化/特化(继承)关系

关联定义了两个或多个类之间的关系,指明了类之间的一种静态的、结构化的关系。例如,“雇员”工作于“部门”,这里“雇员”和“部门”是类,“工作于”是一个关联。关联本身是双向的。关联的名称取其正向:“雇员”工作于“部门”。关联也有一个隐含的相反的方向:“部门”雇佣“雇员”。关联大部分是二元的,即描述两个类之间的关系。

3.2 二元关联

  • 一对一关联。在两个方向上的关联都是一对一的。任意一个类的一个对象只与另一个类的一个对象有一个链接。例如,“学校”和“校长”。
  • 一对多关联。两个类之间的一个方向上有一个一对多关联,而在相反方向上是一个一对一关联。例如,“银行”管理多个“账户”。
  • 规定数值关联。一个指明了特定数字的关联。例如,一辆汽车有两扇或四扇门。相反的关联依然是一对一的。
  • 可选关联。一个类的一个对象到另一个类的一个对象可能不总是存在链接。例如,“客户”拥有“借记卡”,客户能够选择是否有一张借记卡。相反的关联是一对一的。
  • 多对多关联。两个类的两个方向上各是一个一对多的关联。例如,“课程”和“学生”,一个课程由多个学生听课,相反一个学生可以参加多个课程。

3.3 三元关联、一元关联

  • 三元关联是类之间的三个方向上的关联。三元关联的一个例子是“买方”、“卖方”和“中介”。该关联是“买方”通过“中介”和“卖方”协商价格。
  • 一元关联(也称自身关联)是一个类的一个对象与同一个类的另一个对象之间的关联。例如,“人”是“人”的孩子。

3.4 组合和聚合

组合和聚合层次都是讨论一个类由其他类构成的情况。组合和聚合都是关系的特殊形式:类通过整体/部分关联连接起来。在这两种情况下,部分和整体间的关系是(是……的一部分)关系。

组合是一种比聚合更强的关系,聚合是一种比关联更强的关系。

组合关系是一种在部分和整体关联较强的关系,也是实例之间的关系。部分对象的创建、存在和消亡都是和整体一起的。

聚合层次是整体/部分关系较弱的形式。在一个聚合里,部分实例能够添加到聚合整体中,也能从聚合整体中移除。

3.5 泛化/特化

有一些类相似但不相同,它们有些共同的属性,也有其他不同的属性。在泛化/特化层次中,共同属性被抽象到一个泛化类,称作超类或父类。不同的属性是特化类的特质,特化类被称作子类。在子类和超类之间由一个Is-a关系。

每一个子类继承了超类的性质,但是也对这些性质以不同的方式进行了扩展。一个类的性质是其属性或操作。继承允许对父类进行适配,来形成子类。子类从超类继承了属性和操作。子类还可以增加属性、增加操作或者重定义操作。每一个子类自身也可以成为超类,进一步特化形成其他子类。

3.6 UML2.5

软件建模基础_第1张图片

UML2.5教程

4 对象生命周期

4.1 对象的概念

一个对象是现实世界中物理的或概念的实体,它提供了对现实世界的理解。一个现实世界的对象可具有物理属性(它们能被看到或被触摸到),例如一扇门、一辆汽车或一盏灯。一个概念对象是更抽象的概念,例如一个账户或一次交易。

面向对象的应用由对象组成。从设计的视角来说,一个对象涵盖了数据(data)以及作用于数据之上的过程(procedure),这些过程通常被称为操作(operation)或者方法(method)。

一个操作的签名(signature)代表该操作的名字、参数以及返回值。一个对象的接口(interface)是它提供的操作的集合,这些操作通过签名定义。

属性(attribute)是由类中的对象所持有的一个数据值,每一个对象的属性都有一个特定的值。

操作是由一个对象所执行的一项功能的规约。一个对象可拥有一个或多个操作。操作对象所包含的属性进行操控。操作可具有输入和输出参数。在同一个类中的所有对象拥有相同的操作。

4.2 对象创建

软件建模基础_第2张图片

对象创建包含申请内存、初始化变量、堆栈引用等一系列操作,需要分配资源且消耗时间。常见的对象创建方式如下,

  • 静态创建 Person person = new Person();
  • 动态创建 T instance= (T)Activator.CreateInstance(typeof(T))
  • 反序列化 Deseriallize
  • 克隆
  • 反射
  • ……

4.3 对象创建的管理

对象构成组件,组件构成模块,模块构成系统。一个系统包含多少对象,每个对象何时创建、何时回收等,需要进行统一的管理,对象不应该游离于系统之外。如果控制权交给用户,一方面用户需要对系统深入了解,增加使用负担,另一方面也会导致对象创建过程不可控。从现实世界的角度,任何一个对象都不是凭空出现的,都有其被创建的过程和动机。

对象创建的管理

  • 控制权完全交给用户 - new
  • 封装创建过程 - 工厂模式
  • 对象池 - ObjectPool
  • 插件系统 - MEF、OSGI
  • ……

4.4 对象应用之对象角色

  • 实体对象。一种软件对象,很多情况下是持久的,封装了信息并提供对它所储存信息的访问。
  • 边界对象。连接到外部环境并与之通信的软件对象。边界对象进一步分类如下:
    • 用户交互对象。与人类用户进行交互并通过接口连接到人类用户的软件对象。
    • 代理对象 。连接到外部系统或者子系统并与之通信的软件对象。
    • 设备I/O边界对象。从硬件设备接收输入或向硬件设备输出的软件对象。
  • 控制对象。对对象的集合提供全局协调的软件对象。控制对象可以是协调者对象、状态相关控制对象或计时器对象。
  • 应用逻辑对象对信息系统而言,应用逻辑对象通常是业务逻辑对象,而对实时应用、科学应用或工程应用而言,应用逻辑对象通常是算法对象。另一个分类是服务对象,为客户对象提供服务,典型地存在于面向服务的架构和应用中。

4.4 对象应用之对象依赖

降低对象间的耦合和依赖,贯穿整个软件开发周期,也是很多设计模式和设计原则要达到的目标。

  • 直接引用 、直接依赖
  • 设计模式:中介者、观察者、访问者……
  • 设计原则:接口隔离、依赖倒置
  • 控制反转(IoC)、依赖注入(DI)
  • 消息中间件:消息队列(MQ)、事件总线(EventBus)
  • 有限状态机
  • 行为树

4.5 对象销毁

  • 全局对象。这类对象贯穿整个应用程序始终,主程序启动时创建,退出时销毁。一般为系统的实体对象和控制对象,比如相机、光源等物理对象,后台线程对象等。
  • 临时对象。什么时候用,什么时候创建,只在局部临时使用,用完直接销毁。如果开发环境有自动回收机制,则不需要手动销毁,比如高级语言C#、Java、C++的智能指针等。
  • 享元对象。这类对象在程序中会频繁使用,如果每次使用都重新创建则浪费时间,增加系统负担。通常采用对象池的设计,用完回收或挂起,比如我们经常用的线程池。池的容量,可以根据系统运行时的使用情况动态调整。
  • 持久化对象。所谓持久化,在主程序退出时,这类对象可以持久化到硬盘,下次启动主程序,可以从硬盘创建对象。最常用的是序列化和反序列化,数据量较大时采用数据库框架。

5 架构、框架、模式、系统、模块、组件

5.1 架构(Architecture)

架构名词起源于建筑。架构就是指人们根据自己对世界的认识,为解决某个问题,主动地、有目的地去识别问题,并进行分解、合并,解决这个问题的实践活动。架构的产出物,自然就是对问题的分析,以及解决问题的方案:包括拆分的原则以及理由,沟通合并的原则以及理由,以及拆分,拆分出来的各个部分和合并所对应的角色和所需要的核心能力等。架构的一般步骤:

  • 确定问题范围: 根据要解决的问题,对目标系统的边界进行界定。
  • 分解问题:对目标系统按某个原则的进行切分。切分的原则,要便于不同的角色对切分出来的部分,并行或串行开展工作,一般并行才能减少时间。
  • 确定协作方式:并对这些切分出来的部分,设立沟通机制。
  • 协作解决问题:使得这些部分之间能够进行有机的联系,合并组装成为一个整体,完成目标系统的所有工作。

5.1 软件架构

  • 软件架构,也称为软件体系结构。简单地说,软件架构就是一个蓝图,是一种设计方案,将客户的不同需求抽象成为抽象组件,并且能够描述这些抽象组件之间的通信和调用。它是对软件系统的系统组织,是对构成系统的构件的接口,行为模式,协作关系等体系问题的决策总和。它不仅涉及到结构与行为,而且还涉及到系统的使用,功能,性能,适应性,重用性,可理解性,经济性和技术约束的权衡和美学考虑。
  • 使用方法:软件架构会分析工程中的问题,针对问题设计解决方案,针对解决方案分析应具有的功能,针对功能设计软件系统的层次和模块及层次模块之间的逻辑交互关系,确定各个功能如何由这些逻辑实现。工程开发者,可以根据软件架构中分析出来的层次和模块进行软件的编写。如B/S架构,网络架构,企业架构、国家架构。

5.2 框架(Framework)

  • 框架连接而成的结构,在软件工程中的概念是指是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。
  • 软件框架是项目软件开发过程中提取特定领域软件的共性部分形成的体系结构,不同领域的软件项目有着不同的框架类型。框架不是现成可用的应用系统,而是一个半成品,是一个提供了诸多服务,供开发人员进行二次开发,实现具体功能的应用系统。特别强调,框架是一个可供二次开发的程序实体。
  • 框架与架构关系 :首先说框架不是架构,框架比架构更具体,更偏重于技术,而架构偏重于设计。还有一个关系就是架构可以通过多种框架来实现。
  • 使用方法:特定领域软件有一些共性部分形成的软件架构,将这个共性的软件架构开发出来,形成一个可供二次开发的程序实体,这个程序实体就是软件框架。 可供二次开发。如Spring框架,Felix框架等。

5.3 模式(Pattern)

  • 模式是指从生产经验和生活经验中经过抽象和升华提炼出来的核心知识体系。模式(Pattern)其实就是解决某一类问题的方法论。把解决某类问题的方法总结归纳到理论高度,那就是模式。模式是一种指导,有助于你完成任务,有助于你作出一个优良的设计方案,达到事半功倍的效果。而且会得到解决问题的最佳办法。
  • 软件设计模式强调的是一个设计问题的解决方法,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
  • 软件框架与设计模式关系 :设计模式研究的是针对单一问题的设计思路和解决方法,目的是设计重用,一个模式可应用于不同的框架和被不同的程序语言所实现;而框架则是一个应用的体系结构,是一种或多种设计模式和代码的混合体,可实现代码重用。虽然它们有所不同,但却共同致力于使人们的设计可以被重用,在思想上存在着统一性的特点,因而设计模式的思想可以在框架设计中进行应用。

5.4 系统(System)

  • 系统泛指由一群有关联的个体组成,根据某种规则运作,能完成个别元件不能单独完成的工作的群体。它的意思是“总体”“整体”或“联盟”。我来提炼一下里面的关键内容:
    • 关联:系统是由一群有关联的个体组成的,没有关联的个体堆在一起不能成为一个系统。例如,把一个发动机和一台 PC 放在一起不能称之为一个系统,把发动机、底盘、轮胎、车架组合起来才能成为一台汽车。
    • 规则:系统内的个体需要按照指定的规则运作,而不是单个个体各自为政。规则规定了系统内个体分工和协作的方式。例如,汽车发动机负责产生动力,然后通过变速器和传动轴,将动力输出到车轮上,从而驱动汽车前进。
    • 能力:系统能力与个体能力有本质的差别,系统能力不是个体能力之和,而是产生了新的能力。例如,汽车能够载重前进,而发动机、变速器、传动轴、车轮本身都不具备这样的能力。

5.5 模块(Module)

  • 模块是一个通用概念,可能从功能或其他目的来区分。模块可以是子系统子领域,主要取决于上下文环境的用法。通常我们会说程序模块,功能模块,这实际上是在按照不同的标准对模块的内容和范围的不同定义。
  • 通常我们说的程序模块,是指的一段能够实现某个有价值目标的的成员代码段,即函数和过程,它们都能实现一个有价值的目标供其它的模块使用。
  • 而功能模块的说法一般在分析和设计阶段出现得比较频繁,通常我们用一个功能模块来说明一个功能所包含的系统行为,当我们觉得分析的颗粒度可能更大一些的时候我们可以用一个功能模块来表示一组功能的集合,这似乎让我们觉得,模块这个词的概念和“子系统”这个词的概念有些模糊,是的,事实上,有些大的模块会慢慢的让我们觉得称呼他们子系统更合适,或者一个子系统,我们会慢慢发现你还包含着一些模块。
  • 定义模块的原则应该是:高内聚和低耦合。

5.6 组件(Component)

  • 组件已经不是一个抽象的概念了,是封装了一个或多个实体程序模块的实体。
  • 组件这个词通常是现在描述产品的时候出现,一个大的产品会有很多小的部分组成,而小的部分除了是一个大的组件的部分以外,自己可能还包含更小的组件,所以组件是递归的,那么组件到底是什么呢?最常见的组件就是我们已经写好的程序代码,任何一小段代码都可以是一个组件,它可以和其它代码段连接起来组成更大的一段程序代码,一个更大的组件,然后可能是一个函数,或者一个类程序单元,或者数个类单元文件的集成,当不同的组件的组装形成更大的组件时候,我们实际就是在做我们通常提到的一件事情:集成。软件中有很多集成工作要做,每日集成,重要版本集成等等,集成是什么呢?软件中,就是链编调试。这样一来,我们知道集成是需要对被集成的组件有规模要求的,换句话说,至少是一个单元文件,所以通常说到的组件就可以直观的理解为单元文件,或者可以组成软件的其他文件,以及编译后的文件。
  • 组件是面向对象里面的一个重用的概念,也称为构件,组件非常类似机械中构件概念,现在机械都是走向构件生成,通过不同构件组装成一个机械成品,软件目前也是这样的一个生成方式。
  • 维基百科上说,组件之间通过接口进行交互,这个挺起来有些象插件,现实中也是这样,比如一个dll文件,可以说是插件,也可以说是组件。插件是组件的一个子类,就是将组件中具有某些特点的组件归为插件,这些特点是:益于与系统分离,接口明晰,可以替换的程序模块。
  • 组件强调的是封装,利用接口进行交互。因为封装有不同层次的封装,对应不同层次的接口,(比如将一个人封装成一个组件,比如国家主席,多个人封装成一个组合,比如中央的常委们),所以组件所表述的范围和层次也是多种多样的,在谈论组件的时候一定要分辨清楚谈论的层次和范围。层次是相对的。你说地球是整个世界,但是将地球放到银河系中,地球就显得渺小了;你说物质世界是整个世界,但是人类的精神世界也是无比的浩瀚;你说物质世界和精神世界合起来是整个世界,但是历史又是那么的神秘和真实;你说物质世界、精神世界、历史时空是整个世界,但是科学家又说人类可以探察的宇宙物质仅占全部宇宙的百分之四。

5.7 插件(Plug-in、Add-in)

  • 根据对组件和模块的分析,插件属于组件,而且还是一个程序模块,也是一个功能模块。插件是一种电脑程序,通过和应用程序的互动,来替应用程式增加一些特定的功能。 插件必须依赖于应用程序才能发挥自身功能,仅靠插件是无法正常运行的。
  • 使用方法:满足一定接口规范的具有一定功能的程序模块。开发者可以在自己软件系统中设计相应的接口以匹配某个插件,也可以设计一定的接口规范,来让别人开发插件。插件和程序之间通过接口进行交互。

5.8 控件、中间件

  • 控件(Control)
    • 可视化的组件。
  • 中间件(Middleware)
    • 中间件(middleware)是基础软件的一大类,属于可复用软件的范畴。顾名思义,中间件处于操作系统软件与用户的应用软件的中间。
    • 提供系统软件和应用软件之间连接的软件,以便于软件各部件之间的沟通。

5.9 架构、框架、组件、模块、系统

  • 架构是顶层设计。
  • 框架是面向编程或配置的半成品。
  • 组件是从技术维度上的复用。
  • 模块是从业务维度上职责的划分。
  • 系统是相互协同可运行的实体。

6 DDD基本概念

6.1 什么是DDD

  • Domain Driven Design
  • 领域是软件要解决的问题区域
  • 通过对领域知识建立模型,形成团队内各成员的通用语言,指导设计和实现,从而达到为客户解决问题的目的
  • 当领域模型发生改变时,开发人员需要重构,以便反映模型的变化,这样新知识就合并到软件中

6.2 Ubiquitous language (通用语言)

  • 将领域模型作为语言的支柱
  • 领域模型包括类和主要操作的名称
  • 领域模型尽量以文本为主,穿插简化图为说明
  • 不管是画图、写文档、写代码还是讲话,都用的同一种术语
  • 语言是演进的

6.3 Layered architecture(分层架构)

软件建模基础_第3张图片

  • 用分层来隔离领域

  • 层中的任何元素都仅依赖于本层其他元素,或其下层元素。

  • 高内聚,低耦合

  • 领域层应重点放在如何表达领域模型上,而不需要考虑自己的显示和存储问题

  • 用户界面层:向用户显示信息,解释用户命令

  • 应用层:尽量简单,不包含业务规则或知识,只为下层的领域对象分配任务,使他们协作

  • 领域层:负责表达业务概念,业务状态信息和业务规则

  • 基础设施层:上面各层提供通用的技术。比如持久

6.4 依赖倒置的四层架构

软件建模基础_第4张图片

6.5 DDD相关概念

软件建模基础_第5张图片

6.6 Entity、Value Object

  • Entity(实体)
    由标识定义的对象,而不是属性
    整个生命周期都有联系性
    模型必须定义出“符合什么条件才算是相同的事物”
  • Value Ojbect(值对象)
    描述领域的某个方面,但本身没有概念标识的对象
    关心它们是什么,而不关心它们是谁
    值对象是不可变的,具体实现分为共享和复制

6.7 Service、Module

  • Service(服务)
    • 领域操作(活动或者对象)
    • 定义能够为客户做什么,是动词而不是名词
    • 操作是无状态的
    • 结果和参数应该是领域对象
    • 是否包含业务规则来确定是应用还是领域Service
  • Module(模块)
    • 可以查看细节,而不会被整个模型淹没
    • 可以观察Module相互之间的关系,而不考虑细节
    • 将相同职责的对象放在一起

6.8 Aggregate、Factory

  • Aggregate(聚合)
    • 作为数据修改的单元,一组相关对象的集合
    • 包括一个根(root)和一个边界(boundary)
    • 根是Aggregate内一个特定的Entity
    • Boundary定义了Aggregate内有哪些
    • 外部引用时,只可引用根;而边界内部的对象之间可以相互引用
  • Factory(工厂)
    • 对象本身承担大量的职责,让复杂对象创建自身,会职责过载导致问题
    • 将职责交给客户对象创建,会导致客户必须了解对象内部规则,导致和领域类产生耦合
    • Factory隐藏创建细节,通常和Aggregate有关
    • 当创建新对象未满足固定规则时,Factory应拒绝创建对象

6.9 Repository、Sepcfication

  • Repository(仓库)
    • 同一类型的对象的集合
    • 具有复杂的查询、添加和删除对象功能
    • 只为那些确实需要直接访问的Aggreagte提供Repository,而不是所有对象都提供
  • Specfication(规格)
    • 计算结果是真或者假的函数,一般用动词或形容词
    • 用于验证对象是否满足特定的业务规则
    • 用于Repository查询对象是否满足要求
    • 用于创建对象时是否满足需求

6.10 Bounded context

Bounded context(界限上下文)

  • 大型项目都会存在多个模型
  • 权利上的划分和管理级别的不同也可能要求模型分开
  • 标记不同模型之间的边界和关系
  • 边界内部严格保持模型的一致性
  • 防止重复的概念和假同源

6.11 贫血模型

  • 贫血模型:贫血模型是指领域对象里只有get和set方法(POJO),所有的业务逻辑都不包含在内而是放在Business Logic层。
  • 优点是系统的层次结构清楚,各层之间单向依赖,Client->(BusinessFacade)->BusinessLogic->Data Access Object。可见,领域对象几乎只作传输介质之用,不会影响到层次的划分。
  • 缺点是不够面向对象,领域对象只是作为保存状态或者传递状态使用,它是没有生命的,只有数据没有行为的对象不是真正的对象,在Business Logic里面处理所有的业务逻辑,对于细粒度的逻辑处理,通过增加一层Facade达到门面包装的效果。

6.12 充血模型

  • 充血模型层次结构和上面的差不多,不过大多业务逻辑和持久化放在Domain Object里面,BusinessLogic只是简单封装部分业务逻辑以及控制事务、权限等,这样层次结构就变成Client->(BusinessFacade)->BusinessLogic->Domain Object->Data Access Object。
  • 优点是面向对象,BusinessLogic符合单一职责,不像在贫血模型里面那样包含所有的业务逻辑太过沉重。
  • 缺点是如何划分业务逻辑,什么样的逻辑应该放在Domain Object中,什么样的业务逻辑应该放在Business Logic中,这是很含糊的。即使划分好了业务逻辑,由于分散在Business Logic和DomainObject层中,不能更好的分模块开发。熟悉业务逻辑的开发人员需要渗透到Domain Logic中去,而在DomianLogic又包含了持久化,对于开发者来说这十分混乱。

6.13 领域模型

  • 理论派观点:
    • Domain Model是一个商业建模范畴概念,即使一个企业不开发软件,也具备其业务模型;
    • 所有同行企业,其业务模型必定有非常大的共性和内在的规律性。
    • 由行业内的各个企业的业务模型再向上抽象出整个行业的业务模型,这个模型称之为“领域模型”。
  • 实战派观点:
    • 领域模型是一个分析模型,帮助系统分析人员、用户认识现实业务的工具,描述的是业务中涉及到的实体及其相互之间的关系,它是需求分析的产物,与问题域相关。
    • 是需求分析人员与用户交流的有力工具,是彼此交流的语言。

6.14 领域模型(理论派)

  • 领域模型(Domain Model)是一个商业建模范畴的概念,他和软件开发并无一丝一毫的关系,即使一个企业他不开发软件,他也具备他的业务模型,所有的同行业的企业他们的业务模型必定有非常大的共性和内在的规律性,由这个行业内的各个企业的业务模型再向上抽象出来整个行业的业务模型,这个东西即“领域模型”。
  • 一个掌握了行业领域模型的软件公司,根本不需要再给人家开发项目了,根本不需要靠软件开发养活自己了,你光给这个行业的企业提供业务咨询已经赚得非常丰厚的利润了。一家软件公司,在行业内积累了足够的领域模型,成立一个专门的咨询部门,这个部门下面都是咨询师,他们是不管软件开发的,也不懂软件开发,他们就专门教这个行业的客户,教他们怎么去做自己的业务,他们比客户还精通客户的业务,光是业务咨询已经可以为公司带来很多的收入。

6.15 实现步骤

  • 根据需求建立一个初步的领域模型,识别出一些明显的领域概念以及它们的关联
  • 分析程序功能,识别出应用层和领域层的职责
  • 识别Entity,Value Object,Service等
  • 找出Aggregate Root
  • 为Aggregate写Repository
  • 走查场景,分析领域模型是否解决业务需求
  • 考虑创建Entity,Value Object,Aggregate等是用Factory还是构造,又或者是IoC
  • 重构模型

你可能感兴趣的:(软件工程)