Interfaces
One of our key design goals is to reduce coupling between objects and classes. By keeping coupling to a minimum a design is more resilient to change imposed by new feature requests or missing requirements[1].
An Interface represents an abstract service. That is, it is the specification of a set of behaviours (operations) that represent a problem that needs to be solved.
An Interface is more than a set of cohesive operations. The Interface can be thought of as a contract between two objects – the client of the interface and the provider of the interface implementation.
The implementer of the Interface guarantees to fulfil the specifications of the Interface. That is, given that operation pre-conditions are met the implementer will fulfil any behavioural requirements, post-conditions, invariants and quality-of-services requirements.
From the client’s perspective it must conform to the operation specifications and fulfil any pre-conditions required by the Interface. Failure to comply on either side may cause a failure of the software.
Interfaces in C++
Unlike Java and C#, C++ does not directly support Interfaces. However, we can simulate an Interface by defining an abstract class with only pure virtual functions. For example the class ICommand would be considered an Interface[2].
class ICommand { public: virtual void start() = 0; virtual void stop() = 0; virtual ~ICommand(){} };
In UML we represent the ICommand interface thus (Figure 1):
Figure 1 – In UML an Interface is a stereotype of a Class
A concrete class that supports (provides) this interface inherits from ICommand and overrides the pure virtual functions, e.g.
class SystemCoordinator : public ICommand { protected: virtual void start(); virtual void stop(); };
In UML, we normally talk in terms of a class “realizing” an interface (Figure 2), e.g.:
Figure 2 – Realizing an Interface
Note, there is an alternative notation in UML for showing a class “providing” an interface (Figure 3):
Figure 3 – UML Interface “lollypop” notation
One notation isn’t a replacement for the other, but as we shall see later, they are used to capture different design aspects.[1]
Using an Interface
Another object can now call on this interface, invoking the actual SystemCoordinator methods polymorphically, and without being directly coupled to the SystemCoordinator class, e.g.
class MMI { public: explicit MMI(ICommand& cmdRef):cmd(cmdRef){} void startRequested() { cmd.start(); } private: ICommand& cmd; // association to ICommand Interface }; int main() { SystemCoordinator sc; MMI theMMI(sc); // create binding to ICommand theMMI.startRequested(); // calls ICommand::start() }
The Interface mechanism allows an MMI object to associate with any object whose class realises the ICommand interface. The UML class diagram showing the dependency on an interface is shown in Figure 4.
Figure 4 – the MMI class depends on the Interface only; it is realised by the System Coordinator
Giving the following object diagram (Figure 5):
Figure 5 – The object diagram for the SystemCoordinator
Multiple Interfaces
Suppose that the design requires that the SystemCoordinator realises another, different, Interface e.g:
class IController { public: virtual void operational() = 0; virtual void idle() = 0; virtual ~IController(){} }; class SystemCoordinator : public ICommand, public IController { protected: virtual void start(); virtual void stop(); virtual void operational(); virtual void idle(); };
The leads to the flowing class (Figure 6) and object (Figure 7) diagrams:
Figure 6 – SystemCoordinator realises two, independent, interfaces
Figure 7 – The SystemCoordinator object model
Now either client of SystemCoordinator can interact with it through the appropriate Interface:
int main() { SystemCoordinator sc; MMI theMMI(sc); // create binding to ICommand Process theProcess(sc); // create binding to IController theMMI.startRequested(); // calls start() theProcess.doWork(); // calls operational() }
Finally we can also show the class SystemCoordinator providing the two separate Interfaces (Figure 8):
Figure 8 – SystemCoordinator may realize (provide) many Interfaces.
So far, so good; we now have a class successfully implementing multiple Interfaces.
Potential Problems
Clashing Base Class Member Functions
As you may not be responsible for defining Interfaces, a problem can arise where multiple Interfaces declare a function that shares the same signature, for example:
class ICommand { public: virtual void operational() = 0; virtual void stop() = 0; virtual ~ICommand(){} }; class IController { public: virtual void operational() = 0; virtual void idle() = 0; virtual ~IController(){} };
When implementing the derived object (SystemCoordinator), if we wanted the same behaviour for operational() independent of the Interface, this is simply achieved by overriding the appropriate member function, e.g.
class SystemCoordinator : public ICommand, public IController { protected: virtual void stop(); virtual void operational(); // ICommand and IController virtual void idle(); };
Now a call via either Interface invokes the SystemCoordinator::operational() member function, i.e.
int main() { SystemCoordinator sc; MMI theMMI(sc); // create binding to ICommand Process theProcess(sc); // create binding to IController theMMI.startRequested(); // SystemCoordinator::operational() theProcess.doWork(); // SystemCoordinator::operational() }
However, this is unlikely to be the requirement. What is typically required is a separate concrete method definition for each Interface. Unfortunately, there is no direct way of implementing this in C++.
Ports
Before looking at the implementation of a potential solution, let’s first examine the concept of the Port.
Ports provide a point of separation between a class’s internals and its environment. Any access to the class should constrained to be via one of its ports.
External elements should only get access to the class’s services via the composite’s ports. So a port also provides a point of interaction. That is, if you want access to a (set of) a class’s services you must connect to a particular port on the class.
Encapsulation via the port works both ways. The internal elements of the class are isolated from their environment via the port. That is, the only way internal parts should communicate with their environment is via a port.
A class may have more than one port. Each port typically supports a different set of services that the object may provide.
Figure 9 – A Class presenting two Ports
The UML diagram (Figure 9) shows the SystemCoordinator class with two ports (p1 and p2). Each port realises an Interface and implements a separate concrete function for each member function in the interface definition. The SystemCoordinator class is referred to as a “Composite Structure” in UML.
In simplistic terms, a port is an object that appears in the public interface of the containing class. If we start by looking at an implementation:
class SystemCoordinator { public: class Port1 : public ICommand { protected: virtual void operational(); // override ICommand virtual void stop(); }; class Port2 : public IController { protected: virtual void operational(); // override IController virtual void idle(); }; Port1 p1; Port2 p2; };
void SystemCoordinator::Port1::operational() { std::cout << "ICommand::operational\n"; } void SystemCoordinator::Port2::operational() { std::cout << "IController::operational\n"; }
In the main, the clients (MMI and Process) now bind to the ports rather than the SystemCoordinator object
int main() { SystemCoordinator sc; MMI theMMI(sc.p1); // bind to ICommand i/f Process theProcess(sc.p2); // bind to IController i/f theMMI.startRequested(); // ICommand::operational (p1) theProcess.doWork(); // IController::operational (p2) }
Port Types
The intent of ports is that they do not have “business” logic, but are simply acting as an Interface manager. Ports break down into two different types:
- Behavioural
- Delegating
With behavioural ports, any message arriving on the interface is passed through to the containing object (e.g. from p1 to SystemCoordinator). Whereas, with a Delegating port, messages are simply forwarded to other, private, nested objects (called parts in UML).
Behavioural Ports
If the port p1 is designed to be a behavioural port, when receiving a message it will call on a private message of the container, e.g.
class SystemCoordinator { public: class Port1 : public ICommand { public: Port1(SystemCoordinator* sc):container(sc){} private: virtual void stop(){} virtual void operational(){ container->mf(); } SystemCoordinator* container; }; class Port2 : public IController { … }; Port1 p1; Port2 p2; SystemCoordinator():p1(this){} private: void mf(); }; void SystemCoordinator::mf() { std::cout << "Internal function\n"; }
The port object p1 stores a pointer to its containing class, which is initialised when the SystemCoordinator object is created. As the Port1 class is defined within the namespace of the SystemCoordinator class it has visibility of the containers class private region, thus can call the private member function (mf).
Delegating Ports
In reality, behavioural ports are less common than delegating ports. A delegating port is very similar to a behavioural port, except the object it forwards the message to is a private composite object (a part).
If, for example, we have some external class Part:
class Part { public: void f(); };
And an object of this type is a private composite object of a SystemCoordinator object:
class SystemCoordinator { Part part1; // composite part …
Then the Port2 class creates an association with an object of this type and calls on its specific member functions as required by the interface:
class SystemCoordinator { Part part1; // composite part public: class Port1 : public ICommand { … }; class Port2 : public IController { public: Port2(Part* p):thePart(p){} protected: virtual void operational(){ thePart->f(); } virtual void idle(){} private: Part* thePart; }; Port1 p1; Port2 p2; SystemCoordinator():p1(this), p2(part1){} private: … };
The visualization of the design can be represented as a “Composite Structure” on a class diagram (Figure 10)[3]
Figure 10 – A composite structure, showing a behavioural port (p1) and a delegating port (p2)
The code structure must also be formalized on a class diagram (Figure 11):
Figure 11 – The class diagram for the SystemCoordinator, with the ports added.
If you are hot on your Design Patterns, you may recognize Port2 as an “Object Adaptor” pattern. It may be the case that the port is forwarding the message to different parts depending on the received message; in this context, the port would be acting as a “façade”.
And finally…
Ports are a useful idiomatic technique in the C++ programmer’s toolbox. In this example they have helped to overcome the problem of clashing Interface functions; however they can be useful for a much wider array of design issues. In a larger design, where a more component-based approach is required, ports provide a very useful mechanism for decoupling components through well-defined interfaces. Also they provide a mechanism for supporting peer-to-peer protocols between objects (covered in another white paper)
[1] It is commonly said that the only constant on any project is change!
[2] Note the use of an empty virtual destructor to support the use of new and delete via base pointer
[3] Unfortunately, for those of you that know your UML, the tool being used does not represent behavioural ports as defined by the standard.
- Navigating Memory in C++: A Guide to Using std::uintptr_t for Address Handling - February 22, 2024
- Embedded Expertise: Beyond Fixed-Size Integers; Exploring Fast and Least Types - January 15, 2024
- Disassembling a Cortex-M raw binary file with Ghidra - December 20, 2022
Co-Founder and Director of Feabhas since 1995.
Niall has been designing and programming embedded systems for over 30 years. He has worked in different sectors, including aerospace, telecomms, government and banking.
His current interest lie in IoT Security and Agile for Embedded Systems.
Am I the only one who thinks the terms "client" and "server" are not good choices for describing how objects interact in a program (this not specific to your blog and seems standard).
Also, I really hope Bjarne Stroustrup doesn't think OOP is intuitive.