More C++ Idioms/Friendship and the Attorney-Client

< More C++ Idioms

Attorney-Client

Intent

Control the granularity of access to the implementation details of a class

Motivation

A friend declaration in C++ gives complete access to the internals of a class. Friend declarations are, therefore, frowned upon because they break carefully crafted encapsulations. Friendship feature of C++ does not provide any way to selectively grant access to a subset of private members of a class. Friendship in C++ is an all-or-nothing proposition. For instance, the following class Foo declares class Bar its friend. Class Bar therefore has access to all the private members of class Foo. This may not be desirable because it increases coupling. Class Bar cannot be distributed without class Foo.

class Foo 
{
private:
  void A(int a);
  void B(float b);
  void C(double c);
  friend class Bar;
};

class Bar {
// This class needs access to Foo::A and Foo::B only.
// C++ friendship rules, however, give access to all the private members of Foo.
};

Providing selective access to a subset of members is desirable because the remaining (private) members can change interface if needed. It helps reduce coupling between classes. Attorney-Client idiom allows a class to precisely control the amount of access they give to their friends.

Solution and Sample Code

Attorney-client idiom works by adding a level of indirection. A client class that wants to control access to its internal details, appoints an attorney and makes it a friend --- a C++ friend! The Attorney class is crafted carefully to serve as a proxy to the Client. Unlike a typical proxy class, Attorney class replicates only a subset of Client’s private interface. For instance, consider class Foo wants to control access to its implementation details. For better clarity we rename it as Client. Client wants its Attorney to provide access to Client::A and Client::B only.

class Client 
{
private:
  void A(int a);
  void B(float b);
  void C(double c);
  friend class Attorney;
};

class Attorney {
private:
  static void callA(Client & c, int a) {
    c.A(a);
  } 
  static void callB(Client & c, float b) {
    c.B(b);
  }
  friend class Bar;
};

class Bar {
// Bar now has access to only Client::A and Client::B through the Attorney.
};

The Attorney class restricts access to a cohesive set of functions. The Attorney class has all inline static member functions, each taking a reference to an instance of the Client and forwarding the function calls to it. Some things are idiomatic about the Attorney class. Its implementation is entirely private, which prevents other unexpected classes gaining access to the internal of Client. The Attorney class determines which other classes, member functions, or free functions get access to it. It declares them as friend to allow access to its implementation and eventually the Client. Without the Attorney class, Client class would have declared the same set of friends giving them unrestrained access to the internals of Client.

It is possible to have multiple attorney classes providing access to different sets of implementation details of the client. For instance, class AttorneyC may provide access to the Client::C member function only. An interesting case emerges where an attorney class serves as a mediator for several different classes and provides cohesive access to their implementation details. Such a design is conceivable in case of inheritance hierarchies because friendship in C++ is not inheritable, but private virtual function overrides in derived classes can be called if base's private virtual functions are accessible. In the following example, the Attorney-Client idiom is applied to class Base and the main function. The Derived::Func function gets called via polymorphism. To access the implementation details of Derived class, however, the same idiom may be applied.

#include <cstdio>

class Base {
private:
  virtual void Func(int x) = 0;
  friend class Attorney;
public:
  virtual ~Base() {}
};

class Derived : public Base {
private:
  virtual void Func(int x)  {
    printf("Derived::Func\n"); // This is called even though main is not a friend of Derived.
  }

public:
  ~Derived() {}
};

class Attorney {
private:
  static void callFunc(Base & b, int x) {
    return b.Func(x);
  }
  friend int main (void);
};

int main(void) {
  Derived d;
  Attorney::callFunc(d, 10);
}

Known Uses

References

Friendship and the Attorney-Client Idiom (Dr. Dobb's Journal)

This article is issued from Wikibooks. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.