繁体   English   中英

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

[英]How can I remove/refactor a «friend» dependency declaration properly?

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

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

原创设计使用的朋友

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

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

  1. ClassAProvider通过多个并发访问的Client实例共享对ClassA的引用
  2. Client实例应仅通过管理内部的ClassAAccessor帮助程序类来访问ClassA
  3. ClassA将所有打算从ClassAAccessor使用的方法隐藏为受保护。
  4. 因此ClassA可以确保Client需要使用ClassAAccessor实例

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

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

整个构造的期望行为

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

// ...
{ 
    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» ... The following table lists predefined standard elements for UML 1.x that are now obsolete. ... «friend» ...
  2. 我见过的大多数编码规则和指南禁止或强烈反对使用朋友,以避免从导出类到朋友的紧密依赖。 这件事带来了一些严重的维护问题。

正如我的问题标题所说

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

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

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

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

对于第一个镜头,我考虑了«friend»构造型,并将其替换为类(接口) InternalInterface和相应的关系。

第一次重拍

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


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

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

成熟的设计

  • «call»依赖变为从ClassAAccessorInternalInterface有向关联(即ClassAAccessor包含私有变量internalInterfaceRef )。
  • 有问题的操作从ClassA移到了InternalInterface
  • InternalInterface使用受保护的构造函数进行扩展,它仅在继承中有用。
  • ClassAInternalInterface的“泛化”关联被标记为protected ,因此它被公开隐藏。

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

在最后一步中,我们需要为ClassAAccessor如何获取对InternalInterface的引用ClassAAccessor 由于泛化不公开,因此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 ,您可以进一步解耦实现:

在此输入图像描述


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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM