Structural Design Patterns are Design Patterns that help the design by identifying a simple way to realize relationships between entities. The Creational Patterns help us generate objects. But, they leave an open end in associating the objects and identifying the relationships with each other. The structural patterns help us with just that.
As the name suggests, the Adapter pattern allows us to adapt the available interface to what we need. A voltage adapter helps us obtain 110V out of a 230V socket. Similarly, the Adapter pattern provides for an adapter class that can map the available interface to the required interface.
This pattern has three main roles - client, adapter and the adaptee.
The thin Adapter is one that just maps the method names. For example, if we have an adaptee that provides a method trigger(), and the client needs the same functionality in a method name start(), the adapter provides a method named start() that simply invokes the method trigger(). This is a thin adapter. A thick adapter could do a lot more than that.
Here, we must be careful to make sure that the adapter is only adapting the interface - and not adding a functionality of its own. This separation of concern is very important to ensure maintainability.
OOAD is all about separating the abstraction from implementation. The Bridge Pattern takes this a step forward.
It encapsulates an implementation class inside of an interface class. The bridge pattern allows the Abstraction and the Implementation to be developed independently. The client code needs to access only the Abstraction - without being concerned about the Implementation. The abstraction is an interface or abstract class and the implementor is also an interface or abstract class.
The abstraction contains a reference to the implementor. Children of the abstraction are referred to as refined abstractions, and children of the implementor are concrete implementors. Since we can change the reference to the implementor in the abstraction, we are able to change the abstraction's implementor at run-time. Changes to the implementor do not affect client code.
The Bridge Pattern has four participants:
When we think of object oriented programming, we think of inheritance. But inheritance is not always the best way of attacking a problem. Inheritance should be used only when the a class inherits from the other in spirit - not because it has similar functionality.
For example, a circle is a shape. But, an idol has a shape. In its spirit, an idol has a lot more than a shape. A circle too is more than just a shape. But, in its spirit, it is just a shape. In plain English, a circle "Is a" shape, but an idol "Has a" shape. The inheritance is called an "Is a" relation while composition is "Has a" relation.
The composition pattern provides a formal definition for this composition. Here, the container class contains a reference to an interface in the hierarchy of implementing objects.
The composition pattern has four different players
The client uses the component class interface to interact with objects in the composition structure. If recipient is a leaf then request is handled directly. If recipient is a composite, then it usually forwards request to its child components, possibly performing additional operations before and after forwarding.
As the name suggests, a Decorator "decorates" a class. The code is pretty similar to a container class. But the concept is different. A decorator is used when we want to add a lot more functionality to the given class - that does not make a case for inheritance.
Note that inheritance is used only when we have a genuine "Is a" relationship. When we just want to add more functionality, we should use a decorator. This essentially contains the original class object and adds more functionality to it. The calls are passed over to the contained class - perhaps with some more logic before and after it. The decorator can also have its own methods.
To elaborating again, that is exactly what we do with inheritance. But we should use decoration rather than inheritance when the extension does not merge well in the class hierarchy. For example, consider the hierarchy - Shape - Polygon - Quadrilateral - Rectangle - Square.
Now if we have a "Yellow Rectangle", it does not fit well in this hierarchy. The language does not prevent us from using inheritance here. But, the design principles do. Instead, here we should use a decorator class - Yellow Shape - that can have a hierarchy of its own. The Yellow Rectangle would contain an object of class Rectangle.
The Decorator pattern has four participants:
A real life application is never implemented as a monolithic block. That is a sure way to disaster. The application should be divided into multiple subsystems that interact with each other according to a defined protocol. This makes things more manageable and extensible - so that a change in one component does not impact the others. If we can achieve such isolation, we can guess that we are on the right track.
Just having multiple subsystems is not enough. The subsystems should be separate in spirit - not just in code - else an isolation is impossible. If the subsystems are truly independent, then the implementation of one has no relation with that of the other - so long as they follow the agreed interface.
In object oriented design, such a protocol is often implemented in form of a façade class. The façade class receives method calls from the rest of the application and passes them to the appropriate code within the subsystem - that is isolated from the application.
There is an important point we must remember when working with a façade class. Ideally the façade class should just pass on requests to the appropriate code within the module. But, at times developers tend to use it as a "shock absorber" - that just absorbs any changes that are required. This bloats the façade class and then damages the design pattern - reducing the maintainability. The façade class should remain a façade and nothing more than that.
Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use. For example, consider a compiler that has many components hidden behind a simple interface.
There are two participants in this pattern:
We often encounter a situation where we need to create too many instances of a given class. If such an object is complex , and its creation requires a lot of computation, this can create performance issues and also risk an out of memory exception. The flyweight pattern tries to avoid this scenario.
The flyweight pattern tries to reduce the number of objects by "reusing" a single instance of the class. That is, it creates a single instance of each type of class and passes a reference to any client that needs to use it.
But this is not so easy to implement. For it to work properly, we need to ensure a few aspects of the code. The foremost requirement is that the objects of the given class should be immutable - that is, the internal state of the object should not change once the object is created.
This is required, because if we have multiple clients using it simultaneously, we do not want to land in a situation where two clients are trying to update it simultaneously. This can get even more complex if we have multi-threading. But even in a single thread operation, we can have the client code using two references to the same object - where updates to one will impact the other invocation.
One might say what is the point if we do not allow the object to change its state. We want the object to track a state as it works. We can't have all the invocations to return the same result!
That is true. In order to use the flyweight pattern, we need to split the class into intrinsic state and extrinsic state. The intrinsic state is the immutable state of the object that is tracked within the object. The extrinsic state is specific to the client invocation and is tracked by the client. Each method invocation takes this extrinsic state as an input parameter and works accordingly. The method invocation alters the extrinsic state and not the intrinsic state. Hence the object remains immutable.
That leads to another question - what is the meaning in having an extrinsic state? If we separate the method and the state, we are violating the fundamental principle of object oriented programming. That is right. If the extrinsic state is a major component of the object state, there is no point in using the flyweight pattern. But if the intrinsic state is much more intricate and heavier than the extrinsic state, it makes sense to keep the extrinsic state away from the object.
The flyweight pattern has the following roles:
Most of us have used a proxy server to access the internet. An HTTP proxy server is essentially a server on our campus, that controls all the HTTP requests made to the outside world. An HTTP request that I make from my PC bangs on the proxy server, which verifies its sanity and then makes the call on my behalf. It then verifies the sanity of the HTTP response and returns it back to me. It gives me a feeling that I am connected to the internet. But in reality, I am only connected to the proxy server.
The proxy design pattern does just that. The proxy object provides a kind of transparent wall between the caller and the callee. In the code, this is pretty similar to the adapter or decorator patterns. But the design concept is very different. The adapter is used to alter an interface and the decorator is used to add value. But the proxy pattern does neither of these. It is just transparent.
Then what does it do? What is the great deal we achieve by implementing a proxy? A proxy class can be used in one or more of the following scenarios:
The Proxy pattern has the following roles: