领域驱动设计与面向对象的一点想法

我的Github

本文只是我在阅读了《领域驱动设计》这本书以后的一点浅薄的理解和认知,如果有不正确的地方还请大家指出=_=。

什么是计算机软件

software

如上图所示,我所理解的计算机软件是通过使用程序的概念与现实世界中的事物进行映射,最终实现影响现实世界的计算机程序。

如何进行映射

面向过程设计

面向过程其实是一种与人的思维方式很一致的设计方法,将一个事情的处理步骤按照顺序的去执行。在这过程中一般都会使用Top-Down的方法,自顶向下逐步求精的逐步分解一个任务。并且在这个过程中考虑分层,模块化等具体的组织方式,从而分解软件的复杂度。当软件的复杂度不是很大或者要实现的功能的模型与面向过程的设计很契合的情景中,面向过程是一种简单直观的设计方法,也能得到很好的效果。

面向对象设计

面向对象的程序设计通过将现实世界中的事物概念映射到程序设计中的“对象”概念上,现实世界中事物的属性和行为映射到“对象”的属性和方法。程序通过各种对象之间的调用以及协作,从而实现计算机软件的功能。跟很多工程方法一样,面向设计的初衷就是一种处理软件复杂度的设计方法。

面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对电脑下达的指令。面向对象程序设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其它对象,因此它们都可以被看作一个对象。目前已经被证实的是,面向对象程序设计推广了程序的灵活性和可维护性,并且在大型项目设计中广为应用。此外,支持者声称面向对象程序设计要比以往的做法更加便于学习,因为它能够让人们更简单地设计并维护程序,使得程序更加便于分析、设计、理解。

面向对象设计的原则

S.O.L.I.D

The Single Responsibility Principle SRP

如果要在这些原则里面选一条最重要的,那么毫无疑问我会选单一职责原则,这个原则的本质就是我们的设计和实现的对象和实体在各自的概念层次上面都应该是高内聚的。我认为单一职责原则并不是面向对象设计才有的原则,它的适用性非常广泛。我理解的单一职责原则是这样的,对于一个层次上的抽象对象,它只表达或者负责一个这个层次上的概念。比如一个方法只负责单一的行为,一个类只表示单一的概念,一个模块只负责这个模块所表示的单一概念的属性和操作,一个子系统只负责属于这个子系统概念边界内的流程;在面向过程的设计中这个原则同样适用,比如分层模型中一个层只负责这个层的功能并且与上下层的边界要划分清楚。所以说单一职责原则在设计和实现的过程中都具有很广泛的适用性。

The Open/Closed Principle OCP

开闭原则,软件对象实体应该对扩展开放对修改关闭。在需求变动的时候,最好的不需要对原有的设计和代码做改动,而是增加一些类就可以满足需要。 要做到开闭原则,1.抽象,恰当的抽象才能将逻辑或者对象抽象成为可复用和易扩展的组件。2.封装变化,预先将可预测的变动点进行封装抽象,从而可以不修改已有逻辑。

The Liskov Substitution Principle LSP

里氏替换原则,一个子类可以替换他们的父类。即子类应该拥有父类的所有操作,实现IS-A的关系。

The Interface Segregation Principle ISP

接口要小而专,不要大而全。也就是说接口的设计也要高内聚,这样就减少了客户端依赖额外的需需要的接口,减少各个模块之间的耦合。

The Dependency Inversion Principle DIP

依赖反转原则,也就是面向接口编程。具体的说一个类依赖的属性尽量不要直接依赖具体的实现,而是依赖接口或者是abstract类。这样更容易解耦,从而实现OCP。

Composite/Aggregate Reuse Principle CARP

优先使用聚合或组合关系复用代码。 类与类之间简单的关系有Is-A,Has-A,Use-A,对应着继承,关联和依赖。而他们的耦合关系是继承>关联>依赖。而且使用继承的时候尽量要保证子类与父类之间是Is-A关系。所以尽可能使用聚合关系,除非它们真的适用继承关系。

Law of demeter LOD

一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。这是强调了低耦合的重要性。

领域驱动设计

在文章开头的那张图中,我觉得软件与现实重叠的地方就是我们所说的领域。而领域驱动开发也并不是多么神奇的东西,它其实只是面向对象设计的一种方法而已。它是一种与敏捷开发相适应的对系统进行逐步迭代的面向对象的分析和建模方法。

领域知识

所谓领域知识就是指软件所需要处理的业务的流程,相关概念。这个知识需要从领域专家,或者领域相关的书籍中获取,所谓领域专家也就是你的用户,或者是相关的对这些业务很熟悉的人员。你有可能会认为这个就是需求获取,是产品经理的事情,但是很多时候产品经理所提供的信息并不足以让你设计出符合领域的模型,而且产品经理往往缺乏系统建模的知识,另外,信息在传递的过程中往往还有损耗,你从产品经理那里得到的信息或许已经少了很多细节。所以最好的方式还是能直接与领域专家进行交流,从而得到设计领域模型所需要的信息。

UBIQUITOUS LANGUAGE

在与领域专家进行交流的时候,经常会有那么一种情况,就是领域专家说的话开发人员听不懂,开发人员说的话领域专家也不能很好的理解,这样就导致了沟通的效率低下。所以在交流的时候,双方需要对同一个概念有一个统一的描述和理解,并且这些UBIQUITOUS LANGUAGE不仅仅在与领域专家交流的时候使用,并且在领域建模和后续的实现都要持续的使用下去。要让从业务专家到开发人员都能对这些词汇概念有统一的认识。

模型和实现的关系

领域模型应该与软件实现是相对应的,模型的修改必然会导致代码的修改。并且有一些隐式的领域概念在软件的实现中应该在领域模型中体现出来,这是一种抽象的层次,可以减少领域模型的复杂度。比如说,在某个业务场景中,有利息,手续费,等等要收的资金,这时候可以考虑它们能不能抽象成费用这个隐式的概念,并且在领域模型中尝试着加入这个概念的对象,验证是否可以将模型变得更为的合理。

在实际的实现过程中,往往会出现开发人员对模型理解有偏差,导致实现与模型不符合的情况。或者实际的开发人员不认同所设计的模型,在开发过程中可能会根据自己的理解去实现,最终导致了实现与领域模型不相符。所以领域驱动强调模型设计人员必须在开发流程中保证实现与模型是相符的,它提倡模型设计人员也应该是开发人员。现在有很多设计人员都会认为自己只负责设计,而不去保证实现符合设计,这在实际的项目开发过程中大概率会导致实现与模型不符合。还有一些设计人员认为,所谓的设计就是数据库模型设计,其他的都不重要,还有就是设计人员只负责设计,怎么实现就不关心了;根据领域驱动设计的说法,这种想法也是错误的,因为我们的目标是实现一个符合领域模型的软件,而所谓的设计并不狭隘的指数据库模型设计,还有模块和类的设计,这些对于实现我们的目标都是很重要的。虽然数据库的模型很大程度上反映了领域模型,但是领域模型的信息含量远比数据库模型要多,一开始就直接动手设计数据库而不是分析领域往往会导致很多隐藏的问题在开发的过程中才暴露出来,导致在开发过程中还在不断的修改表结构。

分离领域

分离领域更多的是强调在实现的过程中在模块划分和代码组织上讲核心的领域模型与其他模块隔离开来,这也是封装变化的一种体现。比如核心的业务模型代码与view层和基础服务代码分离,这样可以得到更为内聚的模块划分,并且可以更灵活的对领域模型进行重构而降低对其他模块的影响。

逐步迭代

在实际的执行过程中,一开始就能把领域知识完全理解是很困难的,也不符合人类的认知模型,所以领域驱动设计提倡的是逐步迭代的方法。领域驱动设计也是与敏捷开发相契合的。在开发的过程中不断的去理解和消化领域知识,发现现有模型的缺陷或者发现新的对象能将现有模型变得更合理的时候再修改现有的模型。这本来就是敏捷开发逐步迭代的做法。

注重重构

我们所说的重构一般是指代码级别的改进,比如《重构》和《代码整洁之道》里面的方法,但是在领域驱动设计的概念中,重构更偏重于领域模型上的重构。在迭代的过程中,可能在对领域理解的程度加深以后会发现现有的领域模型可以如何改进得更符合领域知识。这时候就应该更新现有的领域模型,因为实现与领域模型是一致的,所以也要对代码实现进行修改。但是在现实的工作中并不是每个人都有这个认知,所以当一个重构点被提出来,有可能因为还有其他功能要实现,或者领导认为重构没有绩效,导致重构一致得不到执行,然后架构就在一次次的迭代过程中不断腐化,每次迭代效率越来越低。这就要各位开发人员提升自己的谈判技巧了=_=

最后

书中我印象最为深刻的一句话,它的大意是“我们并不是能够预测到需求的变化,我们只是把模型设计的尽量符合领域而已。”