问题
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.
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:
ClassAProvider
shares a reference to aClassA
over a number of concurrently accessingClient
instancesClient
instances should accessClassA
solely through theClassAAccessor
helper class that manages the internalsClassA
hides all methods intended to be used fromClassAAccessor
as protected.- So
ClassA
can ensure thatClient
needs to use aClassAAccessor
instance
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 e.g. an uncaught exception). Think of
ClassA
providing (internally visible) paired operations like lock()
/unlock()
or open()
/close()
.
The (state-)reversing operations should be called in any case, especially when a client crashes due
to an exception.
This can be safely handled through the ClassAAcessor
's life cycle behavior, the destructor
implementation can ensure it.
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:
// ...
{
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
- 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» ...
- 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)?
回答1:
Let's setup some constraints for refactoring first:
- The ClassAAccessor's publicly visible interface should change in no way
- The ClassA internal operations should not be visible/accessible from the public
- The overall performance and footprint of the original design should not be hurt
Step 1: Introduce an abstract interface
For a first shot, I factored out the «friend» stereotype, and replaced it with a class (interface)
InternalInterface
and the appropriate relations.

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.
Step 2: Move the operations, that make up the «call» dependency to the interface
The next step is to mature the «call» dependency. To do this, I change the diagram as follows:

- The «call» dependency turned into a directed association from
ClassAAccessor
to theInternalInterface
(I.e.ClassAAccessor
contains a private variableinternalInterfaceRef
). - The operations in question were moved from
ClassA
toInternalInterface
. InternalInterface
is extended with a protected constructor, that it's useful in inheritance only.ClassA
's «generalization» association toInternalInterface
is marked asprotected
, so it's made publicly invisible.
Step 3: Glue everything together in the implementation
In the final step, we need to model a way how ClassAAccessor
can get a reference to InternalInterface
. Since the generalization isn't visible publicly, ClassAAcessor
can't initialize it from the ClassA
reference passed in the constructor anymore. But ClassA
can access InternalInterface
, and pass a reference using an extra method setInternalInterfaceRef()
introduced in ClassAAcessor
:

Here's the C++ implementation:
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:
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(ClassA& classA)
: internalInterfaceRef(0) {
classA.attachAccessor(*this);
}
Finally you can decouple the implementations even more, by introducing another InternalClientInterface
like this:

It's at least necessary to mention that this approach has some disadvantages vs using friend
declarations:
- It's complicating the code more
friend
doesn't need to introduce abstract interfaces (that may affect the footprint, so constraint 3. isn't fully fulfilled)- The
protected
generalization relationsip isn't well supported by the UML representation (I had to use that constraint)
回答2:
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 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. Client instance can read classA attribute value, but instances of other types not defined in the same package cannot.
来源:https://stackoverflow.com/questions/27492132/how-can-i-remove-refactor-a-friend-dependency-declaration-properly