ACCU Home page ACCU Conference Page
Search Contact us ACCU at Flickr ACCU at GitHib ACCU at Facebook ACCU at Linked-in ACCU at Twitter Skip Navigation

pinControlling access to objects by using private interfaces

Overload Journal #28 - Oct 1998 + Programming Topics   Author: Paul Field

I read Overload 27 on the train this morning and Francis' article "Exploring Patterns Part 2" made me think.

In that article Francis develops some host classes containing data and he wants to limit access to that data to a particular hierarchy of classes (the base of the hierarchy being MyVisitor). The solution he provides in the postscript section makes the host data private and makes MyVisitor a friend of the host classes.

But what is friendship?

One of the ideas of OO programming is that an object presents an interface that clients can use to access it. In fact an object can provide several interfaces. An interface is basically a collection of operations you can call and attributes you can access.

C++ objects provide three standard interfaces to an object: the public interface, intended for use by everyone, the protected interface, intended for use by derived classes, and the private interface, intended for use by only that class.

Friendship is about allowing a privileged class to access a special interface. However, the C++ friendship facility is limited (by that definition) because the only interface it can provide access to is the private one (which I mean to include everything that's declared as public and protected too).

Many problems, including Francis's, require one object to get access to a special interface of another object. We usually solve that problem by using friendship but if we could explicitly define special interfaces and make each available only to certain classes, we'd have a very powerful and flexible alternative. The advantages would be that we could provide exactly the interface that we wanted (rather than the entire private interface) and we could provide different interfaces to different clients.

One way to provide additional interfaces is to inherit from base classes. If we have a Square class that inherits from a Shape class then, as an external client, we have two interfaces through which we can access a Square object: as a Shape or as a Square. However, these interfaces don't have any restrictions on who can use them… unless we use private inheritance.

To provide special interfaces and restrict access to them, we can declare the interfaces as abstract base classes, inherit them privately and then write access functions that provide (restricted) access to the interface.

Here's some example code that defines a class, Host, with three different interfaces. There are three (abstract) visitor classes that are allowed to access the Host's interfaces but notice that each visitor is restricted to accessing the Host through a particular interface of the Host. Finally, to make the code actually do something, there are three concrete visitors (each implementing one of the abstract visitors) and a main function that makes a host and uses the visitors.

Notice that Host gives access to its interfaces using the access… methods. The parameter type of the method restricts access to the interface. For example, accessI2() accepts only an I2Visitor as a parameter and so only I2Visitors (including classes derived from I2Visitor) can access Host's Interface2.

The UML class diagram below gives an overview of all these classes and their relationships. Remember this example is intentionally complicated; it's intended to show several interfaces and several types of visitor. Usually, you won't need such complication.

class GetInterface
{ public:
    virtual int getA() const = 0;
    virtual int getB() const = 0;
};

An example of an interface that extends another interface.

class GetSetInterface : public GetInterface
{ public:
    virtual void setA(int a) = 0;
    virtual void setB(int b) = 0;
};

An example of an interface with an attribute.

class Interface2
{ public:
    int c;
};

Here are the classes that are allowed to access the Host. Three different 'visitors' which use the different interfaces provided by Host.

class ConstABVisitor
{ public:
    virtual void visitHost(const GetInterface& i) = 0;
};

class ABVisitor
{ public:
    virtual void visitHost(GetSetInterface& i) = 0;
};

class I2Visitor
{ public:
    virtual void visitHost(Interface2& i) = 0;
};

Define the class with multiple (private) interfaces.

class Host : private GetSetInterface, private Interface2
{ private:
    int a;
    int b;

    /* Implementation of interfaces - declared private */
    int getA() const { return a; }
    int getB() const { return b; }
    void setA(int new_a) { a = new_a; }
    void setB(int new_b) { b = new_b; }

  public:
    /* Provide public functions to allow (restricted) access to interfaces */
    void accessConstAB(ConstABVisitor& v) const { v.visitHost(*this); }
    void accessAB(ABVisitor& v) { v.visitHost(*this); }
    void accessI2(I2Visitor& v) { v.visitHost(*this); }
};

Example concrete visitors.

class ConcreteConstABVisitor : public ConstABVisitor
{ public:
    void visitHost(const GetInterface& i) { cout << i.getA() << i.getB(); }
};

class ConcreteABVisitor : public ABVisitor
{ public:
    void visitHost(GetSetInterface& i) { i.setA(1); i.setB(2); }
};

class ConcreteI2Visitor : public I2Visitor
{ public:
    void visitHost(Interface2& i) { i.c = 3; }
};

Example usage of the pattern.

int main(void)
{ 
  Host host;
  ConcreteConstABVisitor cab;
  ConcreteABVisitor ab;
  ConcreteI2Visitor i2;
  
  host.accessAB(ab);
  host.accessConstAB(cab);
  host.accessI2(i2);

  return 0;
};

Overload Journal #28 - Oct 1998 + Programming Topics