弥合领域差距的解决方案

Solutions To Bridge Domain Gaps

在过去的十年中,软件工程主要的目标之一是构建促进抽象的可重用的软件。正因为如此,开发者见证了面向对象范式的出现,从而导致了引入可重用的面向对象的框架,面向对象的框架可以定义为一组类,这些类体现了解决给定问题域中一系列相关问题的抽象设计。

框架的高可重用性对软件工程师来说是相当重要的,它已经解决了许多与改进组件可重用性的目标相关的问题。随着新学科的出现,出现了新的和必须解决的问题。在最初使用单一框架的地方,我们现在看到了多个框架必须以内聚的方式相互通信。通常,在不同的领域差距之间存在沟通问题,特别是当每个框架由不同的开发团队开发时。对于没有明确参与其开发的开发人员来说,其他框架的源代码通常是不可用的,这通常会导致框架之间的一些集成问题。

在本章中,我将讨论多个框架之间存在摩擦背后的原因,并提供一些实用的方法来构建内连的设计,即使您无法访问应用程序中使用的其他框架的源代码。在本章中,我将把不同问题领域中的框架之间的通信问题称为(compositional friction)组合摩擦。

Compositional Friction

在两个或多个框架之间存在组合摩擦,甚至在一个框架中的类之间的单独水平上存在。尽管在单个框架中,类之间可能存在摩擦,但这些问题可以通过具有源代码可用性的迭代重构过程来解决。本章旨在消除多个框架之间的摩擦,其中一个框架通常只能通过其公共接口访问外部框架,而这些接口不能被修改或重构。

许多软件开发问题可能会导致(compositional friction)组合摩擦,但其中一些最值得注意的问题包括(domain coverage, design intentions, framework gap, entity overlap, and source code access).领域覆盖、设计意图、框架差距、实体重叠和源代码访问。

Domain Coverage

框架的一般目的是为特定问题领域中的应用程序提供一个抽象的设计。重要的是要认识到,该框架不需要覆盖整个问题域,而只需要覆盖给定问题域中相关实体的一个子集。但是,使用框架实现目标的领域覆盖量是相当主观的,因为问题域通常没有广泛的详细定义。确定使用多少覆盖率取决于解决方案架构师,而迭代重构有助于提高域覆盖率。

当组成两个框架时,可能会出现三个层次的领域重叠,每个层次都有不同的含义和解决方案。如果没有发生重叠,则在组成重叠实体时不会出现集成问题的风险,但必须管理的框架之间可能存在差距。如果框架之间有相对少量的域重叠,最好的解决方案是在两个框架中发展几个类,以在几乎没有或没有组合摩擦的情况下彼此通信。然而,当出现相当大的域重叠时,需要做出一些重要的决定。有时,当框架重用受到威胁时,从头开始重写一个或两个框架会更有利。当产品的预期寿命相当长时,重构两个框架之间的通信可能会更成问题。

如果使用框架的应用程序将在很长一段时间内不断发展,则必须为应用程序的每个连续版本不断更新框架。记住要根据问题领域和框架的覆盖范围做出决定。

Design Intentions

一个众所周知的设计理念是,可重用的软件必须通过组合和适应来编写以实现重用。通常,框架的设计是通过适应来重用,而不是通过组合。通过合成设计软件可重用性是非常重要的,可以出现两个组合方向。第一个方向是并行组合,它针对的是存在于应用程序中同一层上的框架。并行是最简单的组合方向,因为这两个框架都不直接依赖于彼此提供的服务来正确操作。另一个组合方向是垂直的,它存在于支持分层体系结构的软件应用程序中,其中框架可以依赖于不同层中的另一个框架提供的服务。与合成方向无关的一个问题是通信支持,它可以是半双工(单向,或单工)或全双工(双向)。半双工通信相当容易设计,但全双工通信在组合多个框架时可能会带来额外的设计问题。

Framework Gap

当需要组合多个框架以满足需求,但这两个框架都不能完全满足需求时,就会出现框架缺口。出现这个问题通常是因为每个框架都没有足够的域覆盖范围,从而导致域间隙或重叠。

对于这个问题有一些解决方案,第一个是使用包装器类,它封装现有功能并扩展任何缺失的功能,还提供统一的公共接口,以便客户端不知道内部体系结构。

另一种选择是开发一个软件联络,它基本上是一个应用程序,它向客户端公开公共接口,并处理框架之间的功能通信和功能扩展。这种方法非常适用于无法访问源代码或不应该修改基本框架的情况。

最后,如果源代码可用,那么最干净的解决方案是通过提供缺失的功能来弥合域差距,或者通过重构方法来消除域重叠。

Entity Overlap

当多个框架在一个特定的问题域中呈现相同的实体时,每个实体都从不同的角度出发,这些框架的组成也要求组成相关的实体。这个问题被称为实体重叠,当相同的问题和实体在多个框架之间建模不同时,就会发生这个问题。在组合多个框架时,实体重叠是一个常见的问题,由于实体类的内聚性,以及它们有时在某些操作发生时需要通知其他框架,因此解决方案可能相当棘手。

解决实体重叠问题的一个解决方案是使用多重继承,但当一个实体的属性不是互斥时,这种方法就会出现一个问题。多个继承通过处理框架中相关实体之间的转换和路由必要的事件来完成合成目标。可以在源代码不可访问且无法修改的开发环境中使用此解决方案。

另一种解决方案是使用聚合,其中聚合类用来部分表示框架。每个聚合类都是应用程序的实体定义,但这种方法要求源代码可用,以便对特定实体的所有引用都可以被更改为指向新的聚合类。这个解决方案的一个缺点是,某个实体的每个表示的所有接口都必须存在于聚合类中,并且在使用聚合来桥接域间隙时存在许多额外的开销。

最终的解决方案是使用子类化,其中每个框架都是子类的,每个子类处理更新的双向通信和其他子类之间的转换。此外,每个子类必须重写超类中的操作。此解决方案也可用于源代码无法访问且无法修改的情况。此解决方案的主要缺点是所表示的实体被划分为多个框架。此解决方案的另一个改进是使用包含所有子类的聚合类,并促进了部分之间的通信和转换。

Legacy Components

有时,框架中的类不满足问题域的解决方案,并且设计保证将遗留组件与框架一起使用来填补空白。如果在遗留组件(如游戏引擎或实用程序库)上投入了相当多的时间和费用,并且业务决定在新框架(例如非托管和托管互操作性)中重用现有技术,也会出现这种情况。使用遗留组件可能会在框架中导致严重的组合摩擦,除非有相应的处理。

删除组合含义的一种方法是修改框架,以引用遗留组件,而不是框架中的类,尽管此解决方案需要访问源代码。

另一种解决方案是使用适配器模式,并构建一个位于框架和遗留组件之间的类,充当解释器,以便两个部分都可以相互通信。本章的后一部分将更详细地介绍这种方法。

Source Code Access

通常,多个框架是由多个团队开发的,开发规则规定特定团队只能访问自己的源代码,并且只能使用编译库或程序集的公共接口访问其他框架中的功能。这个约束很好,因为它限制了每个团队拥有另一个团队框架的不同源代码版本,但也存在问题。有时,必须将行为添加到另一个框架中,以允许其他框架之间的通信。在没有访问源代码的情况下,每个团队必须向其他团队发送大量的更改请求,要求修改,然后当公共接口不能满足所要求它们的团队的需求时,可能会出现其他问题。

这个问题的一个解决方案是使用封装外部框架,并尝试在现有库的基础上构建新功能。但这种方法存在一些问题,例如大量的额外代码和严重的性能损失。此外,如果基础框架中的任何逻辑发生更改,则必须向框架的开发团队发送更改请求。

最好的解决方案是获得源代码,或者建立一个可靠和有效的更改请求系统,该系统几乎立即处理请求,并与您的团队联络,监督修改,以确保需求和需求得到正确满足。

Relevant Design Patterns

设计模式为常见的编程问题提供了可重用的解决方案,其中有一些问题适用于本主题。立面和适配器模式对遭受组合摩擦的建筑非常有利,并且在正确使用时,可以用来减少多个框架之间的摩擦

Façade Design Pattern

这种设计模式为子系统中的一组接口提供了一个统一的高级接口,从而使子系统更容易使用。子系统可以定义为一组为给定问题域提供解决方案的类或库。在本主题的上下文中,一个框架可以被看作是一个子系统。图9.1显示了一个紧密耦合的体系结构的描述。façade模式如图所示。

这种模式的一个好处是,子系统中的类与客户端接口解耦,从而使体系结构更加具有可移植性和可维护性。此外,使用立面模式减少了组件依赖关系,这可以大大减少大型软件项目的编译时间。

下面的代码展示了如何在c#中实现façade模式:

在构建新框架时,façade模式非常有用,但由于本章主要解决了现有框架之间的内聚性问题,因此适配器模式最适合解决这个问题。

Adapter Design Pattern

这种设计模式类似于立面模式,除了适配器模式使两个现有的接口一起工作,而不是定义一个新的接口。为了充分理解适配器模式,应该定义一些术语。这些术语见下图。

可以使适配器类继承自适配器类,但这样做可能会在适应目标接口时导致设计问题。一种更好的方法是将适配器器的一个实例存储在适配器类中,并显式地访问该实例。适配器模式如下图所示。

下面的代码展示了如何在c#中实现适配器模式:

适配器模式实现起来并不复杂,但当您遇到组合不支持所需接口的遗留组件的问题时,这可能是一个很好的设计举措。

尽管有许多方法可以减少组成摩擦和提高组件的内聚力,但人们认为扩大适配器的覆盖范围是很重要的。

处理必须接口的非托管代码(遗留组件)时对于托管代码,无论是显式还是隐式,都经常使用适配器模式。如果您考虑一下,可以将从托管应用程序导出COM接口视为适配器模式的隐式实例,以便非托管应用程序可以与托管代码通信。在更高的层次上,Windows窗体也可以被视为任何CLR兼容语言(如C#)与Win32 API中可用的传统过程控件之间的面向对象适配器。最后,您可以在.NET类框架本身中找到一些适配器。数据库连接功能使用适配器与各种数据库引擎接口。虽然每个数据库引擎都不同,但处理它们的基本接口仍然抽象且一致。

需要注意的是,使用适配器时会增加性能开销,因为必须首先通过适配器方法调用适配器中调用的所有方法。不管时间、环境或芽获取约束,最好的方法就是重构代码,但在开发工具或游戏时,这种情况很少发生。

Conclusion

面向对象框架的引入是在软件组件重用领域中向前迈出的一大步,但最近已经推动了在单个应用程序中使用多个框架。在本章中,我讨论了使用多个框架背后的问题、这样做后出现的问题,以及克服这些问题的一些可能的解决方案。目前,有一些解决方案来减少组合摩擦,如适配器模式和使用包装对象,但这些解决方案只是部分解决问题时,它们也需要大量的实现工作。框架可重用性的最佳方法是仔细研究问题域,以找到适当的域覆盖范围,并使用松散耦合和可维护的架构从头开始构建您的框架。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值