Structural Patterns organize classes and objects into a system that focuses on a specific goal to be achieved at compile time; the structure itself is important.
Acronym to remember: “ABCDFFP”
[A]dapter, [B]ridge, [C]omposite, [D]ecorator, [F]acade, [F]lyweight, [P]roxy
This DP provides an “expected interface” of code to a user. “Adapt the interface of a class to match the interface that the client expects.” In other words, provide the client access to a method via an Adapter class that has an expected interface. Then inside the Adapter class the method is implemented using a field reference to a nested object that has the implementation. The adapter uses the method input to pass into the nested object method input, but anything else the nested object needs as input must come from some external source and not from the client.
Consequences of using the Adapter DP:
- “Class Adapter” is not shown here because Java doesn’t allow extending multiple superclasses. The diagram above is for “Object Adapter”.
- The amount of functionality that is handled in the Adaptee (the “TextData” class) depends on the Target (the “Shape” class). If the Adaptee and the Target method interface is similar, the Adaptee class may handle much of the Target functionality. If Adaptee and Target method interfaces share less in common, then it is likely that the Adaptee would handle a smaller portion of work (the arguments not going to the Adaptee would be handled elsewhere).
- “Pluggable Adapters” is a term meaning that the Adapter logic is used in place of the Target logic. This DP recognizes both scenarios of where a) the Client and the Target are the same and the Adapter subclasses them, or b) when the Client has a nested object “delegate” field that points to the Adapter class (see diagram above). In both scenarios the Adapter interface is used in place of the Target, but in “b” the delegate object can be swapped out at run time, plugging in a new Adapter object based on the requirements of the design.
- “Two-Way Adapters” is a term meaning that instead of one Adapter using only one Adaptee, it can use two Adapters. This allows the Adapter class to provide access to functionality from both Adaptees methods. This can obviously be extended to N number of Adaptees. The intent here for the Object Adapter is that it has a reference to two Adaptee classes, and depending on the client action, methods from one Adaptee can be selected in preference to the other. NOTE: for languages with multiple inheritance (i.e.: C++), this could be implemented as a “Class Adapter” (not shown above) and the Adapter would subclass both Adaptee classes.
This DP handles separating the client class from the implementation logic so that both can vary independently. What this means is that the client class used could be changed or rewritten, but if the implementation class is unchanged, the client can still access the needed implementation. Improvements in the client class can be made and it shouldn’t affect the implementation logic. On the flip side, if the client class is stable but implementation logic needs updating or changing, this can be done without affecting the client class and its interaction with the code users. Improvements in the logic can be made without affecting the client.
This DP handles structures that have individual objects and nested groups of objects which implement the same interface as the outer object. You can then build complex elements using a composite of simpler elements. One example of this would be a tree structure (binary search trees, database index, etc). Another example of this would be an icon class that contains sub-elements like lines and shapes that have the same operations but different implementations as the icon class. Composites can be composed of sub-elements that are all the same or nearly the same (trees with branch and leaf nodes) or that can all be different (icon class). Structuring your classes this way leverages reusability and programs to the interface and not to the implementation (design principle #1).
This DP adds functionality to an existing class while still allowing access to the original class methods. This is handy when you don’t have access to the implementation of the original class. The decorator can be a subclass of the original class or a wrapper class containing field references to an object of the original class. Either way, the decorator passes calls that are intended for the original class either via a superclass or via a field, and handles new method calls in the decorator class. Since decorating a superclass can be dodgy, the programmer should favor the second approach, favoring object composition over class inheritance (design principle #2).
French for “wall”, the Facade DP is used to provide a simple interface between the client class and a collection of other classes. The client class sees a straightforward list of methods available, but behind the facade there could be multiple sources of complexity including several objects that for instance could be making multiple third party library calls, or so-called “legacy code” that has built up over the years in a messy, unplanned way. The upside to this DP is that the client class sees a simple api to use, and if the complexity of the implementation of the facade changes, the client is mostly shielded from this change. The downside to this DP is that it could be difficult to maintain the complexity of the facade implementation if the implementation is spaghetti code. If complexity can be avoided say by splitting up, reorganizing or rewriting the code then over the long term that may be the better solution to handling spaghetti code. In addition, regardless of how simple the Facade api may be to the client class, if implementation changes are made frequently then that could cascade to the client class and cause problems down the road.
“Flyweight” is a boxing (fisticuffs) term meaning the lightest weight class in which boxers can compete. In the case of DP, this just means that instead of having a single large class with lots of methods doing the work, lots of nested objects do the work instead. This DP is basically composition (the implementation of a class is realized by nested objects), where a class is composed a pool of nested objects. This approach delegates implementation to the nested objects, where at the extreme case each method is implemented by a corresponding single object. Dividing up implementation this way can help with maintainability in that its a more Object-Oriented way of doing things. Also, when logic can be used in multiple places within the class, or even in other unrelated classes, reusability is achieved.
This DP is used to exchange or replace one object by another dynamically at run-time. The client code has a reference to a proxy class. At run-time the client code makes a method call to a proxy object. The proxy object has implementation details for connecting with a service or object on behalf of the client. After performing its functionality and using the external service, the proxy object returns success, failure or data to the client code. The client is shielded from the code that takes place in the proxy and beyond, only having contact with the service via the proxy object. This sort of setup is common in network programming, remote method invocations and web services.
Note: Much of the information used here comes from “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides.