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

时间:2014-12-15 20:01:05

标签: c++ refactoring friend

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

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

Original Design using friend

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

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

  1. ClassAProvider分享了对ClassA的引用 同时访问Client个实例
  2. Client个实例应仅通过ClassA帮助程序类访问ClassAAccessor 管理内部的
  3. ClassA隐藏了ClassAAccessor作为受保护使用的所有方法。
  4. 因此ClassA可以确保Client需要使用ClassAAccessor实例
  5. 这种模式主要用于确保将ClassA的实例留在a中 定义状态,如果Client操作失效(因为例如未捕获的异常)。考虑到 ClassA提供(内部可见)配对操作,例如lock() / unlock()open() / close()

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

    The desired behavior of the overall construct

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

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

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

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

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

2 个答案:

答案 0 :(得分:7)

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

  1. ClassAAccessor的公开可见界面应该不会改变
  2. 不应公开/可从公众访问ClassA内部操作
  3. 原设计的整体性能和占地面积不应受到伤害

  4. 第1步:介绍抽象界面

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

    1st shot refactoring

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


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

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

    Matured design

    • «call»依赖关系转变为来自的定向关联 ClassAAccessor InternalInterface {I ClassAAccessor包含internalInterfaceRef 私有变量ClassA)。
    • 相关操作已从InternalInterface移至InternalInterface
    • ClassA使用受保护的构造函数进行扩展,它在继承中很有用 仅
    • InternalInterfaceprotected的«泛化»关联标记为ClassAAccessor, 所以它是公开隐形的。

    第3步:在实施中将所有内容粘贴在一起

    在最后一步中,我们需要为InternalInterface获取ClassAAcessor引用的方式建模。由于泛化不公开,ClassA无法再从构造函数中传递的ClassA引用初始化它。但是InternalInterface可以访问setInternalInterfaceRef(),并使用ClassAAcessor中引入的额外方法class ClassAAccessor { public: ClassAAccessor(ClassA& classA); void setInternalInterfaceRef(InternalInterface & newValue) { internalInterfaceRef = &newValue; } private: InternalInterface* internalInterfaceRef; }; 传递引用:

    Glue everything together


    这是C ++实现:

    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(ClassA& classA)
    : internalInterfaceRef(0) {
        classA.attachAccessor(*this);
    }
    

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

    InternalClientInterface

    最后,您可以通过引入另一个friend来解决实现问题:

    enter image description here


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

    1. 这使代码更复杂
    2. protected不需要引入抽象接口(可能会影响足迹,因此约束3.未完全实现)
    3. UML表示不支持{{1}}泛化关系表(我不得不使用该约束)

答案 1 :(得分:0)

依赖性对访问属性或操作一无所知。依赖关系用于表示模型元素之间的定义依赖关系! 如何从模型中删除所有依赖项并学习如何使用可见性。 如果您的朋友关系表示从特定类型(类)访问功能(属性或操作),则可以将属性或操作的可见性设置为Package。包可见性意味着可以从在同一包中定义的类的实例访问该属性值。

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