Design Patterns - Structural
In Software Engineering, Structural Design Patterns ease the design by identifying a simple way to realize relationships between entities.
Adapter
: Match interfaces of different classesBridge
: Separates an object's interface from its implementationComposite
: A tree structure of simple and composite objectsDecorator
: Add responsibilities to objects dynamicallyFacade
: A single class that represents an entire subsystemFlyweight
: A fine-grained instance used for efficient sharingPrivate Class Data
: Restricts accessor/mutator accessNull Object
: Designed to act as a default value of an objectProxy
: An object representing another object
1. Adapter
- As a developer, I can wrap an object with an incompatible interface (e.g., converting a legacy payment gateway to a new interface), in order to reuse existing code without modifying it.
- As a system integrator, I can connect different modules or external services that speak different protocols, in order to enable smooth communication.
- As a project manager, I can extend system functionality by quickly adopting third-party libraries, in order to accelerate development without altering existing interfaces.
Explanation: The Adapter pattern lets you convert the interface of one class into another interface that clients expect. It’s often used to make existing classes work with others without changing their source code.
2. Bridge
- As a developer, I can separate the abstraction of a feature (e.g., shape) from its implementation details (e.g., rendering engine), in order to independently change each without impacting the other.
- As a user, I can benefit from consistent interfaces (e.g., “draw shape” actions) across various implementations (e.g., OpenGL vs. DirectX), in order to have flexible and adaptable software.
- As a architect, I can reduce code complexity, in order to simplify maintenance by avoiding tightly coupled abstraction and implementation.
Explanation: The Bridge pattern decouples an abstraction from its implementation so that they can vary independently. This helps manage complex class hierarchies by separating responsibilities.
3. Composite
- As a developer, I can treat individual objects (e.g., menu items) and compositions of objects (e.g., submenus) uniformly, in order to simplify code that deals with hierarchical structures.
- As a user, I can interact with nested structures (e.g., file system directories and files) in a consistent way, in order to navigate complex data easily.
- As a project manager, I can request new features that extend from a single interface, in order to avoid rewriting existing logic for different levels of the hierarchy.
Explanation: The Composite pattern organizes objects into tree structures. Both leaf nodes and composite nodes share the same interface, making them interchangeable in client code.
4. Decorator
- As a developer, I can dynamically add new behaviors (e.g., encryption, compression) to objects, in order to keep the base class light and flexible.
- As a system designer, I can wrap objects in multiple decorators (e.g., logging + caching) in order to build complex functionalities without subclass explosion.
- As a user, I can enable or disable features on-the-fly, in order to customize my experience without affecting other parts of the system.
Explanation: The Decorator pattern attaches additional behaviors or responsibilities to objects at runtime by placing them inside special wrapper objects. This avoids subclassing for feature extensions.
5. Facade
- As a developer, I can provide a simplified interface (e.g., a VideoPlayerFacade) to a complex subsystem (e.g., decoding, buffering, rendering), in order to reduce the learning curve for newcomers.
- As a user, I can call a single high-level API method, in order to perform complex tasks (e.g., “play video”) without detailed knowledge of underlying components.
- As a project manager, I can present a unified entry point for major functionalities, in order to improve code maintainability and reduce coupling between subsystems.
Explanation: The Facade pattern defines a high-level interface that makes a subsystem easier to use. By wrapping complex logic behind a facade, you shield clients from internal complexity.
6. Flyweight
- As a developer, I can reuse a small set of shared objects (e.g., repeating characters in a text editor), in order to reduce memory usage significantly.
- As a system administrator, I can scale applications that handle large volumes of identical data (e.g., icons, map tiles), in order to improve resource efficiency.
- As a product manager, I can meet performance requirements with minimal hardware, in order to cut costs and still handle large data sets efficiently.
Explanation: The Flyweight pattern reduces memory usage by sharing common parts of the state between multiple objects instead of storing all data in each object.
7. Private Class Data
- As a developer, I can encapsulate sensitive fields inside a class, in order to restrict direct access and reduce potential misuse.
- As a security officer, I can safeguard critical information (e.g., user credentials) from unwanted exposure, in order to comply with security standards.
- As a system architect, I can maintain better control over how data is accessed or modified, in order to ensure data integrity and consistency.
Explanation: Private Class Data (also known as the Data Hiding pattern) restricts direct access to class attributes, providing controlled access points to protect and maintain data consistency.
8. Proxy
- As a developer, I can create a substitute or placeholder object (e.g., a remote proxy for a web service) in order to control access and manage lifecycle without burdening the client.
- As a user, I can interact with a local proxy object that delegates work to a real service, in order to avoid unnecessary or expensive operations until they are truly needed.
- As a project manager, I can enforce security or caching (e.g., a protection proxy) in order to ensure critical resources are not misused or repeatedly requested.
Explanation: The Proxy pattern provides a surrogate or placeholder for another object, controlling access to it. This is useful for lazy initialization, security checks, remote method invocation, and more.
Done!
Thanks for reading! We hope this overview of creational design patterns will help you appreciate the usefulness and usecases of these patterns in software development.