Ports are a design pattern
Ports are a design pattern (see my book Real-Time Design Patterns for a more detailed description of the pattern). All design patterns are an attempt to optimize some set of qualities of a system at expense of other aspects. All patterns have both pros (benefits) and cons (costs), just like any other design optimization decision. In the case of ports, they allow the connection points of elements to be identified and characterized, independent of the types of things connecting to them. That means that the class types at either end of the ports need not be known to the port – the only requirement is that the interface (or one of its subtypes) must be supported. Ports therefore support an interface-oriented design strategy pretty well. In fact, their best use arises when the behavior being invoked via a port is actually met by an internal part of a composite class; ports enforce information hiding by not allowing the client of the service to know who delivers the service or how it is done. This greatly improves maintainability of the system since internal changes to a composite element only require changes to the client when the interface changes, not when the implementation strategy changes.
As with all design patterns, there are costs to using ports. The first, and less serious, is that they clutter the diagram by adding more visual elements to the model. In addition, they add a small amount of overhead that is not always easy to optimize away. Thirdly, although multiplicity of ports is provided for by the UML specification, tools generally don’t support a multicast to send the same message to all elements connected over a (multi)port and so designers have to fill in means to do that when necessary. Lastly, ports can only be connected via links, not associations. That means that in order to show connected collaborations, only instances of the containing class can be shown on the diagram. Many developers find this limiting.
Technically speaking, a port is a feature of its containing class. Ports can pass both asynchronous (e.g. asynchronous events) and synchronous messages (e.g. function calls). Ports are “typed” by the interfaces they support. An interface is a collection of message specifications. Each port has two sets of interfaces supported, each of which can contain zero or more interfaces. The first are known as “provided” interfaces. These are services provided by the containing element or parts within that element. The second are called “required” interfaces. These refer to sets of services that must be provided by elements (outside the current one) that connect to this port. Ports that can be connected (what is provided by one is required by the other and vice versa) are said to be port conjugate pairs.
Ports may either terminate on the element that actually provides the behavior (in which case, they are called “behavioral ports”) or they may delegate to other ports on internal parts of a composite element (in which case, they are “delegation” or “relay” ports).
Delegate and behavioral ports form the core value of the usage paradigm of ports because they strongly enforce implementation hiding. If a service implementation is completely refactored inside a composite element, the client can remain unchanged. Sure information hiding is crucial for the development of large-scale reusable software elements and for product line engineering.
Are Ports for Systems Engineering or Software Development?
Modeling is used in both systems engineering and software development, but how about ports? The output of systems engineering is ultimately specification, not implementation. Within system modeling, done either with UML or SysML, ports are useful in two different cases. First, the Harmony Process (see my book Real-Time Agility for more information) contains a requirements capture practice known as “System Functional Analysis”. In this practice, use cases are modeled as blocks (classes) and given state-based behavior that represents the externally visible aspects of the requirements relevant to that use case. These blocks are then linked to actors (also blocks) that represent external elements that interact with the system and this collaboration is then executed. I’ve used this in many systems projects, from automotive to spacecraft development, and it’s always been helpful in finding missing, incorrect, incomplete, and conflicting requirements. These blocks are typically connected with ports. Although system functional analysis is inherently a requirements definition activity, constructing an executable model of the use case is made easier through the use of ports.
Another kind of specification output from systems engineering is the system architecture. The key here is to identify and characterize the subsystems to which we will allocate functionality. These subsystems are typically multi-disciplinary; that is, the implementation will be some combination of engineering disciplines such as pneumatic, hydraulic, mechanical, electronic, and software. Subsystems are most commonly modeled as blocks in SysML (classes in UML) that connect to other subsystems through ports characterized with well-specified interfaces. Ports provide a powerful means to model the connectivity of the subsystems at a “logical interface” definition level without having to specify even the implementation discipline, let alone the specific technology.
Having said all that, ports are still used even more in software. The Port Pattern is very common, especially in large-scale software units, called composite classes. These classes realize their behavior primarily through the orchestration of their internal parts. These parts are themselves typed by classes and may also be composites. This whole-part taxonomy provides a simple means of organizing the most complex software architectures and ports provide message delegation and information hiding throughout the software architecture.
Ports with links or associations between classes?
Of course, ports aren’t the only way to connect model elements. Normal associations between classes specify links that exist at run-time. Associations are most commonly implemented as pointers or references and are both lightweight and easy to manage. Non-unary multiplicity can be managed with arrays of pointers or references or more elaborate containers such as those provided by the C++ STL. Although they are lightweight, associations also have a cost; the navigable end must know the type (class) of the element on the other end of the association. This ties the relationship to one between specific types and their subtypes. This is more confining than using ports because ports only require interface compliance not type dependency.
Although ports can be used to connect to part object within a composite from outside that composite, ports provide a cleaner way to do so than with associations. Generally, I recommend that when traversing a link to an internal part object, ports be used if the overhead is acceptable. A common use of ports in this way is to send messages across thread boundaries.
The primary means for thread management in the UML is via active classes. An active class owns its own thread and event queue and its parts typically run inside of that thread. Active classes are typically composites as well and their internal parts perform the semantic functionality of the thread (the active object normally does the thread management and event distribution). Thus, to send an event to an object inside another thread, it is common to connect that object with its client via ports on the boundary of the active class.
Normal or Flow?
Normal ports in UML or SysML support fundamentally discrete communication. This communication can be synchronous (function calls) or asynchronous (asynchronous events) but the message is fundamentally discrete in nature. So, how do you model a continuous flow?
With SysML, a new kind of port was introduced – flow ports. These ports are used in systems engineering as a means to model continuous flows, such as the (continuous) pressure on a brake pedal, the voltage level on a wire, or the energy in a battery. Flow ports are bound to attributes of a block and when linked, the change of an attribute in one block flows along the link to update the value of an attribute bound to the flow port at the other end of the link.
Since UML defines four different kinds of event (asynchronous, synchronous, time, and change), these value changes can induce an event on the receivers state machine via a change event. Flow ports thus provide a simple means for data distribution in systems. Used in this way, you can even think of flow ports are providing a rudimentary publish-subscribe pattern since when the server object’s data changes it automatically propagates to the receiver.
Ports are a very useful design pattern for requirements analysis, architecture definition, and data propagation. They do have some overhead that may not always be optimized away but they support an interface-based development strategy and as well as simple data distribution. I personally recommend ports be used primarily between architectural or large-scale elements such as subsystems, software components, structured classes, and task threads. However, once you’re inside a composite, associations are recommended for inter-class communications.