我如何正确地删除/重构“朋友”依赖声明?

这个问题的背景是基于一个实际的例子,我想从一对用于pipe理对共享资源的读/写locking访问的类中移除“朋友”依赖项。

以下是该场景的原始结构devise的抽象:

使用朋友的原始设计

标记为红色,这是我想从devise中删除这个丑陋的“朋友”依赖项。

总之,为什么我有这样的事情:

  1. ClassAProvider通过多个并发访问的Client实例共享对ClassA的引用
  2. Client实例应该只通过pipe理内部的ClassAAccessor辅助类来访问ClassA
  3. ClassA隐藏所有从ClassAAccessor被保护的方法。
  4. 所以ClassA可以确保Client需要使用ClassAAccessor实例

此模式主要用于确保将ClassA实例保留在已定义的状态,如果Client操作被释放(由于例如未捕获的exception)。 想想ClassA提供(内部可见)成对的操作,如lock() / unlock()open() / close()

无论如何应该调用(状态)反转操作,特别是当客户端由于exception而崩溃时。
这可以通过ClassAAcessor的生命周期行为安全的处理,析构函数的实现可以保证它。 以下序列图说明了预期的行为:

整个结构的理想行为

此外, Client实例可以轻松访问ClassA ,只需使用C ++范围块即可:

 // ... { ClassAAccessor acc(provider.getClassA()); acc.lock(); // do something exception prone ... } // safely unlock() ClassA // ... 

到目前为止,这一切都很好,但是ClassAClassAAccessor之间的“朋友”依赖关系应该被删除,原因很多

  1. 在上一个UML的UML 2.2上层结构的C.2部分中,它表示: The following table lists predefined standard elements for UML 1.x that are now obsolete. ... «friend» ... The following table lists predefined standard elements for UML 1.x that are now obsolete. ... «friend» ...
  2. 我所见过的大多数编码规则和指导方针,或强烈阻止使用朋友,以避免从出口类到朋友的紧密依赖。 这件事带来了一些严重的维护问题。

正如我的问题标题所述

如何正确地删除/重构一个朋友声明(最好从我的课程的UMLdevise开始)?

首先设置一些重构约束:

  1. ClassAAccessor公开可见的界面应该不会改变
  2. ClassA的内部操作不应该是公众可见的
  3. 原有devise的整体性能和占地面积不应受到影响

第1步:介绍一个抽象接口

对于第一个镜头,我将“friend”原型分解出来,并用一个类(接口) InternalInterface和适当的关系代替它。

第一枪重构

什么组成了“朋友”依赖关系,被分解成了一个简单的依赖关系(蓝色)和一个«呼叫»依赖关系(绿色),对着新的InternalInterface元素。


步骤2:将构成“调用”依赖关系的操作移动到接口

下一步是成熟«呼叫»依赖。 要做到这一点,我改变了图表如下:

成熟的设计

  • «呼叫»依赖关系变成了从ClassAAccessorInternalInterface的直接关联(即ClassAAccessor包含一个私有variablesinternalInterfaceRef )。
  • 有问题的操作从ClassA移到InternalInterface
  • InternalInterface是用受保护的构造函数进行扩展的,它只在inheritance中有用。
  • ClassAInternalInterface的“概括”关联被标记为protected ,所以它是公开的。

步骤3:在实施过程中将所有东西粘合在一起

在最后一步中,我们需要模拟ClassAAccessor如何获得对InternalInterface的引用。 由于泛化不公开, ClassAAcessor无法从构造函数中传递的ClassA引用初始化它。 但是, ClassA可以访问InternalInterface ,并使用ClassAAcessor引入的额外方法setInternalInterfaceRef()传递引用:

把所有东西粘在一起


这是C ++实现:

 class ClassAAccessor { public: ClassAAccessor(ClassA& classA); void setInternalInterfaceRef(InternalInterface & newValue) { internalInterfaceRef = &newValue; } private: InternalInterface* internalInterfaceRef; }; 

这个实际上被调用的时候,也是新引入的方法ClassA::attachAccessor()被调用的时候:

 class ClassA : protected InternalInterface { public: // ... attachAccessor(ClassAAccessor & accessor); // ... }; ClassA::attachAccessor(ClassAAccessor & accessor) { accessor.setInternalInterfaceRef(*this); // The internal interface can be handed // out here only, since it's inherited // in the protected scope. } 

因此,ClassAAccessor的构造函数可以按照以下方式重写:

 ClassAAccessor::ClassAAccessor(ClassA& classA) : internalInterfaceRef(0) { classA.attachAccessor(*this); } 

最后,你可以通过引入另一个InternalClientInterface来解耦这个实现:

在这里输入图像描述


至less有必要提到这种方法与使用friend声明相比有一些缺点:

  1. 这更复杂的代码
  2. friend不需要引入抽象接口(这可能影响足迹,所以约束3没有完全实现)
  3. protected泛化关系ip没有得到UML表示的很好的支持(我不得不使用这个约束)

关于属性或操作的访问没有依赖关系。 依赖关系用于表示模型元素之间的定义依赖关系! 怎么样从你的模型中删除所有的依赖关系,并学习如何使用可见性。 如果您的朋友关系表示从特定types(类)访问特性(属性或操作),则可以将属性或操作的可见性设置为包。 软件包可视性意味着,可以从同一个软件包中定义的实例中访问该属性值。

在相同的包中定义ClassAProvider和Client,并将PackageA可见性设置为包可见性types。 客户端实例可以读取classA属性值,但在同一个包中未定义的其他types的实例不能。