我如何正确地删除/重构“朋友”依赖声明?
这个问题的背景是基于一个实际的例子,我想从一对用于pipe理对共享资源的读/写locking访问的类中移除“朋友”依赖项。
以下是该场景的原始结构devise的抽象:
标记为红色,这是我想从devise中删除这个丑陋的“朋友”依赖项。
总之,为什么我有这样的事情:
-
ClassAProvider
通过多个并发访问的Client
实例共享对ClassA
的引用 -
Client
实例应该只通过pipe理内部的ClassAAccessor
辅助类来访问ClassA
-
ClassA
隐藏所有从ClassAAccessor
被保护的方法。 - 所以
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 // ...
到目前为止,这一切都很好,但是ClassA
和ClassAAccessor
之间的“朋友”依赖关系应该被删除,原因很多
- 在上一个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» ...
- 我所见过的大多数编码规则和指导方针,或强烈阻止使用朋友,以避免从出口类到朋友的紧密依赖。 这件事带来了一些严重的维护问题。
正如我的问题标题所述
如何正确地删除/重构一个朋友声明(最好从我的课程的UMLdevise开始)?
首先设置一些重构约束:
- ClassAAccessor公开可见的界面应该不会改变
- ClassA的内部操作不应该是公众可见的
- 原有devise的整体性能和占地面积不应受到影响
第1步:介绍一个抽象接口
对于第一个镜头,我将“friend”原型分解出来,并用一个类(接口) InternalInterface
和适当的关系代替它。
什么组成了“朋友”依赖关系,被分解成了一个简单的依赖关系(蓝色)和一个«呼叫»依赖关系(绿色),对着新的InternalInterface
元素。
步骤2:将构成“调用”依赖关系的操作移动到接口
下一步是成熟«呼叫»依赖。 要做到这一点,我改变了图表如下:
- «呼叫»依赖关系变成了从
ClassAAccessor
到InternalInterface
的直接关联(即ClassAAccessor
包含一个私有variablesinternalInterfaceRef
)。 - 有问题的操作从
ClassA
移到InternalInterface
。 -
InternalInterface
是用受保护的构造函数进行扩展的,它只在inheritance中有用。 -
ClassA
与InternalInterface
的“概括”关联被标记为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
声明相比有一些缺点:
- 这更复杂的代码
-
friend
不需要引入抽象接口(这可能影响足迹,所以约束3没有完全实现) -
protected
泛化关系ip没有得到UML表示的很好的支持(我不得不使用这个约束)
关于属性或操作的访问没有依赖关系。 依赖关系用于表示模型元素之间的定义依赖关系! 怎么样从你的模型中删除所有的依赖关系,并学习如何使用可见性。 如果您的朋友关系表示从特定types(类)访问特性(属性或操作),则可以将属性或操作的可见性设置为包。 软件包可视性意味着,可以从同一个软件包中定义的实例中访问该属性值。
在相同的包中定义ClassAProvider和Client,并将PackageA可见性设置为包可见性types。 客户端实例可以读取classA属性值,但在同一个包中未定义的其他types的实例不能。