在稳定抽象原则(SAP)上阅读this wiki后,我想知道是否有人知道取决于抽象而不是混凝土有任何不利之处(我想,这超过了优势)。
SAP声称包越稳定,它应该越抽象。这意味着如果包装不太稳定(更有可能改变),那么它应该更具体。我真正不明白的是为什么会出现这种情况。当然,在所有情况下,无论稳定性如何,我们都应该依赖于抽象并隐藏具体实现?
答案 0 :(得分:4)
<强>&#34;稳定性&#34; 强>
首先,了解Martin如何定义&#34;稳定性&#34;是非常重要的。他根据 afferent 和传出耦合来定义它,产生稳定性度量:
instability = efferent / (efferent + afferent)
&#34;传入神经&#34;和&#34;传出&#34;是如此模糊的术语。为简单起见,我们使用&#34;传出依赖关系&#34; 代替&#34;传出耦合&#34;和&#34;传入的依赖关系&#34; 用于&#34;传入耦合&#34;。所以我们有这个:
instability = outgoing / (outgoing + incoming)
它与变革的可能性非常分离,并且与变革的难度有关。令人困惑的是,通过这个定义,一个稳定的&#34;包装仍然可能一直在变化(当然,它会很糟糕,但很难管理)。
如果您使用上述公式得到除以零的错误,那么您的包既不会被使用也不会使用任何东西。
稳定的依赖原则
为了理解Martin关于SAP的观点,从SDP(稳定依赖关系原则)开始更容易。它声明:
包之间的依赖关系应该是方向的 包装的稳定性。包装应仅依赖于包装 比它更稳定。
这很容易理解。更改设计的成本与传入依赖项的数量(和复杂性)级联。可能任何曾经在大规模代码库中工作的人都可以非常快速地欣赏这一点,因为中心设计变更最终可能会破坏代码库中10,000个非常复杂的部分。
因此,依赖关系应该(会?)流向那些不变的,坚定根基,坚定不移的部分,就像一棵树从树叶向下流向树根。
根应该归结为 零传出耦合(零传出依赖关系) 的稳定性指标。也就是说,这个稳定的&#34; root&#34;包装不应该依赖于其他任何东西。换句话说,它应该完全独立于外部世界。这是定义&#34;最大稳定性的特征&#34;根据马丁的指标:完全独立。
Maximum independence = "stable root" (as I'm calling it)
Maximum dependence = "unstable leaf" (as I'm calling it)
鉴于这种完全独立,超稳定的根设计,我们如何能够在不影响界面/设计的情况下轻松扩展和更改其实现的灵活性?这就是抽象的来源。
稳定的抽象原则
抽象允许我们将实现与接口/设计分离。
因此,这里出现了稳定的抽象原则:
最大稳定的包应该是最大抽象的。 不稳定的包装应该是具体的。包的抽象性 应该与其稳定性成比例。
如SDP所述,我们的想法是允许这些中央根设计超稳定,同时仍然保留一定程度的灵活性,以便通过抽象不会影响核心设计。
举个简单的例子,考虑一个软件开发工具包,它位于某个引擎的核心,并被全球的插件开发人员使用。根据定义,这个SDK必须具有非常稳定的设计,因为许多传入的依赖项(所有这些插件开发人员都使用它)与最小的或没有外向依赖性相结合(SDK依赖于其他很少)。这个原则表明它的接口应该是抽象的,以便在不影响稳定设计的情况下具有最大程度的灵活性。
&#34;中等抽象&#34;这里可能是一个抽象的基类。 &#34;最大抽象&#34;将是一个纯粹的界面。
<强>混凝土强>
另一方面,抽象是对混凝土的需求。否则就没有什么可以提供抽象的实现。所以这个原则也表明混凝土部件应该(会?)是不稳定的部件。如果你把它想象成一棵树(从通常的编程树中反转),依赖从叶子向下流动,叶子应该是最具体的,根应该是最抽象的。
叶子通常具有最外向的依赖性(对外部事物的许多依赖性 - 对所有那些分支和根),而它们将具有零传入依赖性(没有任何东西依赖于它们)。根源将是相反的(一切都取决于它们,它们完全不依赖)。
这就是我如何理解马丁的描述。他们很难理解,我可能会在某些方面离开。
当然,无论稳定性如何,我们都应该依赖 在抽象和隐藏具体实施?
也许你在实体方面思考的更多。实体的抽象接口仍然需要在某处进行具体实现。具体部分可能不稳定,同样更容易改变,因为没有其他东西直接依赖它(没有传入耦合)。抽象部分应该是稳定的,因为许多可能依赖于它(许多传入依赖,很少或没有传出依赖),因此很难改变。
同时,如果您按照应用程序包的方式进行更加依赖的程序包,其中您的应用程序的主要入口点将所有内容组合在一起,那么在此处对所有接口进行抽象通常会增加改变的难度,仍然需要在其他地方实现具体(不稳定)的实施。在代码库中的某个时刻,必须依赖于具体部分,如果只是为抽象接口选择适当的具体实现。
抽象或不摘要
我想知道是否有人知道任何不利因素依赖 抽象而不是混凝土(我想,这超过了 优点)。
表现浮现在脑海中。通常,抽象具有动态调度形式的某种运行时成本,例如,其随后变得易受分支错误预测的影响。很多马丁的写作都围绕着经典的面向对象范式。而且,OOP通常希望在单一实体级别上对事物进行建模。在极端情况下,它可能希望将图像的单个像素转换为具有自己操作的抽象接口。
在我的领域,我倾向于使用具有面向数据的设计思维模式的实体组件系统。这种翻转经典的OOP世界。结构通常被设计为一次聚合多个实体的数据,其设计思维方式寻找最佳的存储器布局(为机器设计而不是逻辑地为人工设计)。实体被设计为组件集合,组件使用面向数据的思维模式建模为原始数据。对于处理组件的系统,接口仍然是抽象的,但抽象是为了批量处理事物而设计的,并且依赖关系从系统流向最不是抽象的中心组件。
这是游戏引擎中常用的一种技术,它在性能和灵活性方面提供了很大的潜力。然而,这与马丁对面向对象编程的重点形成鲜明对比,因为它总体上远离OOP。
答案 1 :(得分:0)
首先,从您链接到的论文:
稳定性不是衡量模块可能性的标准 更改;而是衡量改变模块的难度
因此很难改变(例如在很多地方使用)应该是抽象的,以使扩展变得容易/可能。
是的,有缺点。这是变革的容易程度。更改具体代码而不是抽象和代码更容易,更快捷。
当然,无论稳定性如何,我们都应该依赖 在抽象和隐藏具体实施?
这是真的。但抽象程度不同。即时示例:如果我要求您计算方形对角线的长度,那么您可能只使用内置double sqrt(double)
函数。是抽象的吗?是。我们不知道是否使用了牛顿方法,或者它是否直接委托给了cpu。
但是如果我们想创建一个sqrt函数并依赖某种物理计算库呢?在这种情况下,以前的抽象是否足够?可能不是因为我们可能想要处理(以统一的方式)矩阵,相对错误,任意长度数,所需数量的核心/线程的并行化,可能委托给gpu并且它应该准备好用于其他扩展,因为迟早有人可能希望它处理NaN和虚数。
所以它仍然是sqrt函数,但抽象级别要高一些。这只是因为很多代码都依赖于它。哪个功能更容易改变?
答案 2 :(得分:0)
这意味着如果包装不太稳定(更有可能改变) 那应该更具体。我真正理解的是为什么 应该是这样的。
抽象是软件中难以改变的东西,因为一切都依赖于它们。如果您的软件包经常更改并提供抽象,那么依赖它的人将在您更改某些内容时被迫重写大量代码。但是如果你的unstable包提供了一些具体的实现,那么在更改之后就必须重写更少的代码。
所以,如果你的包装经常变化,它应该更好地提供混凝土,而不是抽象。否则......谁会用到它? ;)