简体   繁体   English

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

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

The background of this question is based on a practical sample where I wanted to remove a «friend» dependency from a pair of classes that are used to manage read/write locked access to a shared resource. 这个问题的背景是基于一个实际的示例,我想从一对用于管理对共享资源的读/写锁定访问的类中删除«friend»依赖项。

Here's an abstraction of the original structural design for that scenario: 这是该场景的原始结构设计的抽象:

原创设计使用的朋友

Marked in red, there's this ugly «friend» dependency I want to remove from the design. 标记为红色,我想从设计中删除这个丑陋的“朋友”依赖。

In short, why do I have this thing there: 简而言之,为什么我在那里有这个东西:

  1. ClassAProvider shares a reference to a ClassA over a number of concurrently accessing Client instances ClassAProvider通过多个并发访问的Client实例共享对ClassA的引用
  2. Client instances should access ClassA solely through the ClassAAccessor helper class that manages the internals Client实例应仅通过管理内部的ClassAAccessor帮助程序类来访问ClassA
  3. ClassA hides all methods intended to be used from ClassAAccessor as protected. ClassA将所有打算从ClassAAccessor使用的方法隐藏为受保护。
  4. So ClassA can ensure that Client needs to use a ClassAAccessor instance 因此ClassA可以确保Client需要使用ClassAAccessor实例

This pattern comes primarily useful, when it's about ensuring to leave instances of ClassA in a defined state, if a Client operation bails out (because of eg an uncaught exception). 如果Client操作失效(因为例如未捕获的异常),则此模式主要用于确保将ClassA实例保留在已定义的状态。 Think of ClassA providing (internally visible) paired operations like lock() / unlock() or open() / close() . 想想ClassA提供(内部可见)配对操作,如lock() / unlock()open() / close()

The (state-)reversing operations should be called in any case, especially when a client crashes due to an exception. 在任何情况下都应调用(state-)反转操作,尤其是当客户端由于异常而崩溃时。
This can be safely handled through the ClassAAcessor 's life cycle behavior, the destructor implementation can ensure it. 这可以通过ClassAAcessor的生命周期行为安全地处理,析构函数实现可以确保它。 The following sequence diagram illustrates what's the intended behavior: 以下序列图说明了预期的行为:

整个构造的期望行为

Additionally Client instances can achieve a fine control of accessing ClassA easily, just using C++ scope blocks: 此外,只需使用C ++范围块, Client实例就可以轻松实现对访问ClassA的精细控制:

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

All fine so far, but the «friend» dependency between ClassA and ClassAAccessor should be removed for a number of good reasons 到目前为止一切正常,但ClassAClassAAccessor之间的“朋友”依赖应该被删除,原因很多

  1. In the UML 2.2 Superstructure, Section C.2 under Changes from previous UML it says: The following table lists predefined standard elements for UML 1.x that are now obsolete. ... «friend» ... 在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. Most coding rules and guidelines I've seen forbid, or strongly discourage using friend, to avoid the tight dependency from the exporting classes to the friends. 我见过的大多数编码规则和指南禁止或强烈反对使用朋友,以避免从导出类到朋友的紧密依赖。 This thing brings in some serious maintenance issues. 这件事带来了一些严重的维护问题。

As my question title says 正如我的问题标题所说

How can I remove/refactor a friend declaration properly (preferably starting out at the UML design for my classes)? 如何正确删除/重构朋友声明(最好从我的课程的UML设计开始)?

Let's setup some constraints for refactoring first: 让我们首先为重构设置一些约束:

  1. The ClassAAccessor's publicly visible interface should change in no way ClassAAccessor的公开可见界面应该不会改变
  2. The ClassA internal operations should not be visible/accessible from the public 不应公开/可从公众访问ClassA内部操作
  3. The overall performance and footprint of the original design should not be hurt 不应该损害原始设计的整体性能和占用空间

Step 1: Introduce an abstract interface 第1步:介绍一个抽象接口

For a first shot, I factored out the «friend» stereotype, and replaced it with a class (interface) InternalInterface and the appropriate relations. 对于第一个镜头,我考虑了«friend»构造型,并将其替换为类(接口) InternalInterface和相应的关系。

第一次重拍

What made up the «friend» dependency, was split up into a simple dependency relation (blue) and a «call» dependency (green) against the new InternalInterface element. 构成«friend»依赖关系的内容被分成了一个简单的依赖关系(蓝色)和一个«call»依赖关系(绿色)对新的InternalInterface元素。


Step 2: Move the operations, that make up the «call» dependency to the interface 步骤2:将构成«call»依赖关系的操作移动到接口

The next step is to mature the «call» dependency. 下一步是成熟«call»依赖。 To do this, I change the diagram as follows: 为此,我按如下方式更改图表:

成熟的设计

  • The «call» dependency turned into a directed association from ClassAAccessor to the InternalInterface (Ie ClassAAccessor contains a private variable internalInterfaceRef ). «call»依赖变为从ClassAAccessorInternalInterface有向关联(即ClassAAccessor包含私有变量internalInterfaceRef )。
  • The operations in question were moved from ClassA to InternalInterface . 有问题的操作从ClassA移到了InternalInterface
  • InternalInterface is extended with a protected constructor, that it's useful in inheritance only. InternalInterface使用受保护的构造函数进行扩展,它仅在继承中有用。
  • ClassA 's «generalization» association to InternalInterface is marked as protected , so it's made publicly invisible. ClassAInternalInterface的“泛化”关联被标记为protected ,因此它被公开隐藏。

Step 3: Glue everything together in the implementation 第3步:在实现中将所有内容粘合在一起

In the final step, we need to model a way how ClassAAccessor can get a reference to InternalInterface . 在最后一步中,我们需要为ClassAAccessor如何获取对InternalInterface的引用ClassAAccessor Since the generalization isn't visible publicly, ClassAAcessor can't initialize it from the ClassA reference passed in the constructor anymore. 由于泛化不公开,因此ClassAAcessor不能再从构造函数中传递的ClassA引用初始化它。 But ClassA can access InternalInterface , and pass a reference using an extra method setInternalInterfaceRef() introduced in ClassAAcessor : 但是ClassA可以访问InternalInterface ,并使用ClassAAcessor引入的额外方法setInternalInterfaceRef()传递引用:

将所有东西粘在一起


Here's the C++ implementation: 这是C ++实现:

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

This one is actually called, when the also newly introduced method ClassA::attachAccessor() method is called: 当调用新引入的方法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.
}

Thus the constructor of ClassAAccessor can be rewritten in the following way: 因此,ClassAAccessor的构造函数可以通过以下方式重写:

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

Finally you can decouple the implementations even more, by introducing another InternalClientInterface like this: 最后,通过引入另一个类似于此的InternalClientInterface ,您可以进一步解耦实现:

在此输入图像描述


It's at least necessary to mention that this approach has some disadvantages vs using friend declarations: 至少有必要提一下,这种方法与使用friend声明相比有一些缺点:

  1. It's complicating the code more 它使代码变得更复杂
  2. friend doesn't need to introduce abstract interfaces (that may affect the footprint, so constraint 3. isn't fully fulfilled) friend不需要引入抽象接口(可能会影响足迹,因此约束3.未完全实现)
  3. The protected generalization relationsip isn't well supported by the UML representation (I had to use that constraint) UML表示不能很好地支持protected泛化关系(我不得不使用该约束)

Dependency say nothing about accessing of attributes or operations. 依赖性对访问属性或操作一无所知。 Dependency is used to represent definition dependency between model elements ! 依赖关系用于表示模型元素之间的定义依赖关系! What about to remove all dependencies from your model and learn how to use visibility. 如何从模型中删除所有依赖项并学习如何使用可见性。 If your friend relationship represents accessing of feature (attribute or operation) from specific type (class), you can set visibility of attribute or operation to Package. 如果您的朋友关系表示从特定类型(类)访问功能(属性或操作),则可以将属性或操作的可见性设置为Package。 Package visibility means, that attribute value is accessible from instances which classes are defined in the same package. 包可见性意味着可以从在同一包中定义的类的实例访问该属性值。

Define ClassAProvider and Client in same package and set classA attribute visibility to Package visibility type. 在同一个包中定义ClassAProvider和Client,并将classA属性可见性设置为Package可见性类型。 Client instance can read classA attribute value, but instances of other types not defined in the same package cannot. 客户端实例可以读取classA属性值,但是在同一个包中未定义的其他类型的实例不能。

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

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