类的模块化

时间:2020-03-06 14:24:22  来源:igfitidea点击:

有什么技巧可以使我的课程和应用程序设计更加模块化?

解决方案

依赖倒置/注入原理在这方面帮助了我很多。
我使用Spring(Java)或者Spring.NET(C / .NET Framework)来实现它。

尝试TDD。确保执行"重构"步骤,这通常是我们开始分解的地方。另外,重点关注问题的分离。如果一堂课做得太多,那就把它分解。是的,DI / IoC对于使这些功能正常工作非常重要。

"打开/关闭原则"是朝正确方向迈出的相当不错的一步。

我想说的最重要的是正交性。它是什么?基本上,如果GUI进行了外观上的更改,则无需修改基础的"引擎"。严格在"引擎"中的错误修正不需要对程序的其他部分进行任何更改。

拥有模块化程序并不容易。想一想关于Windows和Linux的情况,在第一种情况下,一切都取决于一切(而MS很难改变它,在Vista中具有第一个效果),而在Linux上,我们可以在桌面环境,cron实现,web之间切换服务器实施,程序包管理器...

让代码变得害羞。得墨meter耳定律不要求一个物体,然后向该物体询问其他东西。直接要求"其他"。但是我们可能想更多地研究这个主题。

TDD。首先编写测试;当难以测试对象时;它指出代码中缺乏解耦/模块化/单一责任原则。

TDD实际上是一个设计过程。恰好会产生人工制品的测试。 IMHO TDD的真正实力迫使设计可测试,因此高度分离和模块化

每个软件产品都可以看作是两个相互交织的元素:一个静态规范(源代码)和一个运行时行为(该程序希望执行指定的操作)。

封装仅涉及前者,而不涉及后者。封装适用于人员,而不适用于处理器。用最讨厌的词来说,封装和运行时行为是"正交的"。从封装的角度来看,程序可能是计划要拆除的破旧,吱吱作响的木棚子,但可能会以热导导弹的精确度和轰鸣声运行。或者,程序可以精美,强大地封装,但在最轻的夏日微风中崩溃。1

此外,封装本身沿着两种赋予其生命力的强大力量破裂:一种是定性的,另一种是定量的。

定性从程序试图解决的问题的自然语言语义中得出其影响。基本上,如果一个程序由两个程序包(一个gui程序包和一个计算程序包)组成,并且我们要负责向程序中添加一个新的弹出窗口,那么在没有任何其他信息的情况下,我们将正确地假设新功能将包含在gui软件包中。这很有道理:弹出窗口与GUI功能的共同点大于与计算功能的共同点。

但是,从自然语言语义到源代码的这种映射不是自动的。机器无法做到。它本质上是主观的。在这里,如果有的话,它的艺术将与计算机科学的装甲兵抗争。

但是,定量封装是可以通过客观测量来评估的。例如,通过将程序输入到分析器中,分析器可以识别该程序在定量上是好还是封装得不好。这里没有主观空间。这就是科学。

为了实现定量封装,需要封装的数学理论。

本文介绍了这种封装理论。
3.那么什么是封装?

我们需要一个定义。

如果有一个国际的,标准的封装定义可以在其上建立数学模型,那就太好了,但是,要是这样,就需要一个国际标准化组织,而这个国际标准化组织就必须过去了。麻烦地以一种有意义的方式定义"封装"。

好吧,幸运的是,有一个国际标准化组织:它叫做国际标准化组织,这些好人正式将封装定义为(参见[5]):

封装:(转鼓)属性,对象中包含的信息只能通过对象支持的接口上的交互来访问(interaction崩溃!)。 [作者添加的音效。]

我们可能会认为,ISO的优秀人才(从希腊术语来说,众所周知)显然只有在他们认为有一定价值的情况下才愿意提出这样的定义。你会是对的。其他伟大的思想家也得出了相同的结论。

上面定义的本质是将某些事物与其他事物分隔开:在这种情况下,对象的信息与希望访问它的对象(即对象的客户)分开。这是对基础计算机概念(关注点分离)的重申,该概念由荷兰杰出的计算机科学家Edsger W. Dijkstra于1974年首次提出,他在反思人脑的极端局限性时对此进行了描述(请参见[6]):

让我尝试向我们解释,我的品味是所有明智思维的特征。就是说,一个人愿意出于自身的一致性而独立地深入研究其主题的一个方面,一直都知道一个人只在其中一个方面中占据一席之地。我们知道一个程序必须是正确的,我们只能从那个角度研究它;我们也知道它应该是有效的,可以这么说,我们可以在另一天研究它的效率。在另一种情况下,我们可能会问自己是否,如果这样:为什么,该程序是理想的。但是,通过同时处理这些各个方面,却无济于事-相反!这就是我有时所说的"关注点分离",即使不是完全可能,这也是我所知道的唯一有效地整理自己的思想的方法。

封装使对象的客户可以将精力集中在对象的接口上,而不是直接关注信息,因此,如果可以将接口定义为比信息本身负担少的话,则客户可以解决的问题就更少。

在迪杰斯特拉(Dijkstra)的前两年,1972年,另一座智能计算塔以类似的方式写作。加拿大的David Parnas描述了应如何使用"信息隐藏"技术来设计软件(请参见[7]):

相反,我们建议从一系列困难的设计决策或者可能会更改的设计决策开始。然后,将每个模块设计为对其他模块隐藏此类决策。

关键是,如果隐藏了可能会更改的功能,那么与其他所有模块都可以访问该功能相比,更改此功能将带来较小的负担。

阅读上面的封装定义,我们看到如果对象的信息只能通过对象的接口访问,那么对象本身必须表现出两种根本不同的现象:客户端(接口)可以访问和客户端无法访问的现象(包含的信息)。确实很简单,ISO和Parnas定义可以通过以下建议结合起来:即客户无法访问的信息在Parnasian意义上是信息隐藏的。我们正是在这里提出这个建议。

Parnas建议隐藏的信息的性质也值得注意。信息不仅仅是数据,而是"设计决策",它不仅可以体现在数据中,还可以体现在功能行为中。应该将可能更改的方法与可能更改的数据隐藏起来。整个类应与方法一样隐藏。今天,人们谈论"数据封装",但这只能是此处定义的封装的子类别(可能会更全面,更笨拙地描述为"行为和数据封装");我们得出的规则应该适用于封装,因此就逻辑扩展而言,也适用于数据封装。

尽管如此,历史已经为我们提供了从封装方法中获得明显好处的管道热帮助,但是我们还没有足够的资源来开始我们的数学尝试。

一种成分仍然缺失。
4.电位耦合

为了使封装变得定量,我们需要进行一些测量。为此,我们必须再次回顾过去。

再次回到1974年(当时的火光还是迷人的岩石使人们变得如此聪明?),史蒂文斯先生,迈尔斯和君士坦丁先生撰写了一篇论文,其中写了计算文学史上最重要的两个连续句子(请参阅[8]):

模块之间的连接越少越简单,就越容易理解每​​个模块而无需参考其他模块。最小化模块之间的连接也最小化了更改和错误传播到系统其他部分的路径,从而消除了灾难性的"涟漪效应",其中一个部分的更改会导致另一部分的错误,从而需要在其他地方进行其他更改,从而产生新错误等

为了描述这些联系,作者发明了两个术语:耦合和内聚。

耦合是对从一个模块到另一个模块的连接所建立的关联强度的度量。内聚性(尽管照亮得不太清楚)是对模块内连接所建立的关联强度的度量。耦合是模块间的,凝聚是模块内的。本文将继续描述耦合和内聚力如何取决于连接的类型以及建立连接的环境。从那以后,通过定义各种类型的连接(因而是耦合)和各种类型的上下文(因此是内聚力)而引入的复杂性对程序员来说具有巨大的实用性。

但是,必须提出两点。

首先,在我们寻求理论时,我们需要简化。如果我们要开发有用的,定量的封装理论,则必须将要考虑的现象数量减至最少。因此,本文将放弃模块内和模块间连接,耦合和内聚之间的区别。一个新的取代概念必须至少在它们的计数方式上将它们视为等效。包中两个类之间的静态软件依赖关系将被视为一个依赖关系,就像两个不同包中两个类之间的软件依赖关系一样。

其次,让我们回到Parnas写作中的关键短语。

帕纳斯(Parnas)写下设计决策"可能会改变的a |"。该陈述涉及预测。帕纳斯(Parnas)不仅考虑软件,还考虑软件的发展。

耦合和内聚似乎按原样描述了软件,但是我们肯定希望封装理论包含这种预测性,以便它可能为程序员提供的不仅仅是当前状态的程序描述(程序员可能会对此有所描述)。请注意),而是为程序的未来发展提供指南。

因此,本文将定义潜在耦合的概念,以试图将这两种观点结合在一起。

潜在耦合是程序中最大可能的源代码依赖关系数。这些依赖关系可能尚未实现,但是鉴于程序的当前信息隐藏和非信息隐藏元素,它们是允许的。因此,潜在耦合根据程序可能产生的依赖关系来描述程序可能演变成的格局。

在潜在耦合的基础上,我们将进行数学处理。实际上,封装理论被定义为对封装图的潜在耦合的研究。

但是,既然我们具有这种潜在的耦合能力,那么我们程序的最大可能依赖关系数是什么呢?在我们应该如何处理潜在耦合方面,这一系列伟大的思想有什么暗示吗?

是的,有。

如果我们遵循ISO的建议,则使对象内部无法访问信息将减少程序的潜在耦合。

如果我们遵循Dijkstra的建议,将关注点分为可访问的问题和不可访问的问题,将减少程序的潜在耦合。

如果我们遵循Parnas的建议,则将模块中的设计决策与所有其他模块隐藏在一起将减少程序的潜在耦合。

如果我们遵循史蒂文斯(Stevens),迈尔斯(Myers)和君士坦丁(Constantine)的建议,最小化模块内部和模块之间的连接将减少程序的潜在耦合。

似乎已经为我们设定了目标:我们希望使用封装理论来减少程序的潜在耦合。

伟大的。

我们现在需要的是一种计算潜在耦合的方法。

以及减少它的方法。
5.模型:简要概述

封装理论描述了程序模型的构建以及对该模型的分析,以得出可以反馈到程序中以减少其潜在耦合的各种更改。

该模型由以下元素及其等效的Java构造组成:

*

  Nodes. A node is a model of a class.
*

  Edges. An edge is a model of a static, source code dependency.
*

  Encapsulated regions. An encapsulated region is a model of a package.
*

  Encapsulated graphs. An encapsulated graph is a model of an entire program.

就是这样。

我们为程序构建的每个模型将仅包含这四个元素。

但是,节点可以是以下两种类型之一:

*

  Information-hidden. This is a class that is declared with the default accessor (also informally called, "Package-private").
*

  Violational. This is a class that is declared public in a package, and so is accessible outside that package. (It is, "Violational," because it violates information-hiding.)

顾名思义,模型在不考虑非本质性的情况下捕获了系统的基本方面。上述元素的模型会丢弃大量有关原始程序的信息,尤其是其运行时行为。但是,如我们所见,封装与运行时行为无关。

其他遗漏也值得一提。在上面的模型中,没有任何地方可以选择从外部包访问类:正如Heidi原则毫不留情地指出:"我们或者在里面,或者就在外面"。因此,无法对受保护的访问器进行建模。通常,受保护的类很少,因此不必担心,但是在程序大量使用此访问器的情况下,封装理论将是分析工具的较差选择。

Java的反射也没有建模。反射可用于解决信息隐藏问题。同样,广泛使用反射的程序将无法准确建模。

尽管如此,对程序进行建模的过程仍为:

Map all packages to encapsulated regions.
Map all public classes to violational nodes within those regions.
Map all package-private classes to information-hidden nodes within those regions.

4,

Form edges between all nodes within each region.

5,

Form edges from each node in each region to every violational node in every other region.

Count the total number of edges.

第6点为我们提供了我们所需要的:一种客观地计算潜在耦合的方法。

当然,我们并不是应该自己手动在建筑师的桌子上用铅笔和纸,橡皮,橡皮和咖啡来进行映射。定量封装的全部要点是它是机器可验证的:让程序在繁重的工作中挣扎。第一个这样的程序是" Fractality",可以在[2]上免费下载。只需将其指向Java代码,其余的工作就可以完成。

现在我们已经计算了潜在的耦合,我们只需要知道减少耦合的规则即可。
6.封装规则

理论的实用性与琐碎的应用一样有用。幸运的是,封装理论仅产生了四个描述如何减少潜在耦合的规则。这些是:

Scope minimally.
Group isoledensally.
Hide uniformly.

4,

Violate non-uniformly.

让我们快速看一下每个规则,看看如何减少潜在的耦合。
规则1:最小范围

这条规则是古老,简单和重要的。

这里的"作用域"指的是一个类的可访问性。基本上,该规则表示,如果某个类从其包外部不存在依赖关系,则应将该类声明为package-private。

从潜在耦合的角度来看,实际的依赖关系并不重要:所有重要的事情都是潜在的依赖关系。一旦某个类被宣布为公共类,系统中其他所有类都将对它形成潜在的依赖关系。它们可能实际上不是今天形成的,也许不是明天形成的,而是很快形成的,并贯穿了余生。

从务实的角度来看,当一个类是公共类时,在该类上形成包间依赖的可能性更高。如果一个类不打算接收包间的依赖关系,那么将其公开就没有任何好处。

没有包间依赖关系的公共类就像晚上在银行中打开的窗口:这并不意味着银行将被盗,但这无济于事。
规则2:异性地分组

不要让这个可怕的词使我们失望:一旦我们掌握了这个窍门,这个奇怪的满足。

该规则告诉我们程序应具有多少个软件包。

参加任何程序。要找出应包含的软件包数量,请计算其中的类数;称它为n(对于节点而言为n)。计算其中的包裹数量;称此r(r为地区)。计算其中的公共班级数量;称它为p(p代表a |,你知道了)。

然后进行两步计算。

首先,计算区域违规密度d。这只是每个包的公共类的数量,或者:

其次,计算类别数的平方根除以区域违规密度,这是系统应具有的程序包数,用下式表示:

为什么有人要打扰所有这一切?因为这给出了在给定这些特定参数的情况下将使分布均匀的图的潜在耦合最小化的程序包数量。即使程序分布不均匀,这也提供了一个不错的目标。

请记住,这是定量封装,并且必须始终通过语义封装来平衡:如果我们有10个软件包,所有这些软件包在质量上都非常合理,但是上面的等式告诉我们应该有8个软件包,那么这样做可能是安全的。服从语义本能。这里的目标不是要付出一切代价,而是要在质的约束下进行。

(顺便说一句," Isoledensal"仅表示最小化保留了违规密度,在这种情况下是区域违规密度。选择是任意的;我们可能已经保留了各种各样的其他参数,但很少有与势能耦合匹配的参数-降低选择的能力。)
规则三:统一躲藏

此规则指出,程序包专用类应在所有程序包中平均分配。如果我们有50个软件包专用类和10个软件包,则每个软件包应有5个软件包专用类。

已经发现,这种均匀性可以在大多数情况下减少潜在的耦合,并且不难理解为什么。包中的依赖项数量大约与包中类的数量的平方成正比。因此,如果封装中的类数增加一倍,则潜在的耦合将增加4.

同样,这是要实现而不是残酷追求的目标。更加务实地采取"避免混合小包装和大包装"的形式。很少有程序员会本能地将1000个类的系统划分为两个包,一个包含7个类,另一个包含993. 规则3仅支持这种本能。

规则4:违规

仅仅从数量上来说,该规则几乎没有使其进入候选列表,因此影响相对较小。

它指出,与规则3的统一分发的程序包私有类不同,公共类应尽可能紧密地聚集在一起,最好将所有免费的公共类放在一个程序包中。 (由于每个程序包必须包含一个公共类,否则系统的其余部分无法联系该程序包,因此每个程序包中的一个公共类不能随意移动,而其余的则可以。)

该规则基于一个猜想,该猜想以尽可能低的潜在耦合(比完全均匀分布的图更低)呈现图的形式,显然,这种对最小化的顽固追求不适合当今编写的大多数Java软件。因此,此规则实际上仅适用于核心的封装纯粹主义者,而且很少。考虑到当今的封装状态,此规则就像建议通过电子显微镜观看超级碗一样。

除了从某种意义上说。

除了理想的情况外,该规则还说,即使有多个这样的包,当将公共类捆绑在一起时,潜在的耦合也会降低:这很好地映射了CORBA和OSGI等技术中使用的接口存储库的概念。接口存储库的用途远不止封装,因此在此我们总结一下,该规则建议使用接口存储库,但建议不多。

但是,这里有:封装的四个规则。
8.理想的配置效率

仅计算潜在耦合是可以的,但是当我们编写程序时,最好有一个固定的目标。如果我们的程序在星期一的潜在耦合为10,000,在星期五的潜在耦合为20,000,我们是否降低了程序的封装能力,或者我们引入了足够的功能来值得潜在耦合的增加?要回答这个问题,最好有一个简单的百分比,该百分比可以将我们的程序封装的大小与理想的,相同大小的完美程序进行比较。因此,如果我们的程序在星期一是60%,在星期五是80%,那么我们就知道我们已经提高了程序的封装水平,并且我们的程序已接近100%完美封装的(不可实现的)目标。

封装理论精确地给了我们这样一个度量:异烯烃构型效率。

这种配置效率是介于0和1之间的数字,0是完全未封装的程序(每个类都是公共的),而1是完全封装的程序。如果将其乘以100,则配置效率采用百分比形式,并且无论该程序的大小如何,我们都可以使用该百分比来跟踪程序的封装。

当然,不用说,对于给定数量的类,当我们减少潜在的耦合时,我们会提高配置效率。
9.图栈

尽管我们只讨论了封装在包中的类,但是相同的规则适用于所有可以映射为封装区域中的节点的事物。具体来说,方法可以用与管理程序包中的类封装的规则完全相同的规则封装在类中。
10.现在是坏消息...

如果我们已经读了那么多书,则可能浪费了时间。

至少看起来如此。

一项调查分析了60多个开源Java项目,目的是记录配置效率,以查看程序的封装程度。结果不言自明。

绝大多数程序的配置效率在20%到30%之间。没有一个程序的效率超过50%。

由于某些原因,大多数程序都是使用75%的公共班级编写的。换句话说,对于每个编写的信息隐藏类,都要编写三个公共类。 A.W.O.L.制定了规则1,"最小范围",大量公共类从其程序包外部没有任何依赖关系。

这种情况只有两个可能的原因:

Encapsulation theory is horribly wrong.
Or something else.

所有建议列表的第零条规则在这里都是相关的:请自己思考。

如果我们认为封装的四个规则合理,并且可以看到它们如何在程序中发挥优势,那么请务必使用它们。

如果我们认为它们听起来很荒谬,如果我们早上没有起床而没有最大化事物的范围,如果没人会告诉我们有多少软件包在数学上将任何东西最小化,那么非常感谢我们,那么请务必忽略它们。

但是,无论我们做什么,都有理由。知道为什么要这么做。挑战时保卫它。

感谢我们抽出宝贵的时间阅读本文。
11.进一步阅读

如果我们想进一步了解封装理论,可以在作者的网站上免费找到介绍该概念的原始论文系列(请参见上面的标题)。每篇论文都采用相同的格式:上半部分是对特定概念的简单描述,下半部分是从中推导出特定思想的一连串定理。不喜欢数学的人欢迎完全忽略后半部分。

第一篇论文发表在[3],并通过许多示例描述了潜在的耦合。除非我们先阅读这篇论文,否则其他任何论文都没有多大意义。
8.参考

[1]"如何封装软件(第1部分)",Ed Kirwan,www.EdmundKirwan.com / pub / paper7.pdf

[2] Fractality,Ed Kirwan,www.EdmundKirwan.com / frac.jar

[3]"封装理论基础",Ed Kirwan,www.EdmundKirwan.com / pub / paper1.pdf

[4]"封装理论:L2电位耦合",Ed Kirwan,www.EdmundKirwan.com / pub / paper6.pdf

[5]"信息技术开放式分布式处理",ISO / IEC 10746,1998.

[6]"论科学思想的作用",Dijkstra,Edsger W(1982),在"计算:个人观点"的精选著作中,纽约,纽约,美国:Springer-Verlag纽约,公司,第60a66页。

[7]"关于将系统分解为模块的标准",D。L. Parnas,1972年。

段落数量不匹配