如何正确删除/重构"朋友"依赖声明?

πάν*_*ῥεῖ 14 c++ refactoring friend

这个问题的背景是基于一个实际的示例,我想从一对用于管理对共享资源的读/写锁定访问的类中删除«friend»依赖项.

这是该场景的原始结构设计的抽象:

原创设计使用的朋友

标记为红色,我想从设计中删除这个丑陋的"朋友"依赖.

简而言之,为什么我在那里有这个东西:

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

ClassA如果Client操作失败(因为例如未捕获的异常),则该模式主要用于确保将实例保留在已定义的状态.考虑 ClassA提供(内部可见的)配对操作,如lock()/ unlock()open()/ close().

在任何情况下都应调用(state-)反转操作,尤其是当客户端由于异常而崩溃时.
这可以通过ClassAAcessor生命周期行为安全地处理,析构函数实现可以确保它.以下序列图说明了预期的行为:

整个构造的期望行为

此外,只需使用C++范围块,Client实例就可以ClassA轻松实现对访问的精确控制:

// ...
{ 
    ClassAAccessor acc(provider.getClassA());
    acc.lock();
    // do something exception prone ...
} // safely unlock() ClassA
// ...
Run Code Online (Sandbox Code Playgroud)

到目前为止一切都很好,但由于一些很好的理由ClassA,ClassAAccessor应该删除之间的"朋友"依赖关系

  1. 在UML 2.2超结构,第C.2节中,在以前的UML的变化下,它说: The following table lists predefined standard elements for UML 1.x that are now obsolete. ... «friend» ...
  2. 我见过的大多数编码规则和指南禁止或强烈反对使用朋友,以避免出口类对朋友的严重依赖.这件事带来了一些严重的维护问题.

正如我的问题标题所说

如何正确删除/重构朋友声明(最好从我的课程的UML设计开始)?

πάν*_*ῥεῖ 7

让我们首先为重构设置一些约束:

  1. ClassAAccessor的公开可见界面应该不会改变
  2. 不应公开/可从公众访问ClassA内部操作
  3. 不应该损害原始设计的整体性能和占用空间

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

对于第一次拍摄,我将"朋友"刻板印象排除在外,并将其替换为类(界面) InternalInterface和适当的关系.

第一次重拍

构成«朋友»依赖关系的内容被分成了一个简单的依赖关系(蓝色)和一个«call»依赖关系(绿色)对新InternalInterface元素.


步骤2:将构成«call»依赖关系的操作移动到接口

下一步是成熟«call»依赖.为此,我按如下方式更改图表:

成熟的设计

  • 该«电话»依赖变成了从直接关联 ClassAAccessorInternalInterface(即ClassAAccessor包含一个私有变量internalInterfaceRef).
  • 有问题的行动被转移ClassA到了InternalInterface.
  • InternalInterface 使用受保护的构造函数进行扩展,它仅在继承中有用.
  • ClassA'«泛化»协会InternalInterface被标记为protected,因此它被公开隐形.

第3步:在实现中将所有内容粘合在一起

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

将所有东西粘在一起


这是C++实现:

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

当调用新引入的方法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.
}
Run Code Online (Sandbox Code Playgroud)

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

ClassAAccessor::ClassAAccessor(ClassA& classA)
: internalInterfaceRef(0) {
    classA.attachAccessor(*this);
}
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述


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

  1. 它使代码变得更复杂
  2. friend 不需要引入抽象接口(可能影响足迹,因此约束3.未完全实现)
  3. protected泛化relationsip没有很好地利用UML表示支持(我不得不使用限制)