06 Dec 2023
Structural patterns are a category of design patterns in software development that focus on simplifying the organization of classes and objects. These patterns help in composing classes and objects into larger structures while keeping the system flexible and efficient. The primary goal of structural patterns is to ensure that the components of a system are easily interchangeable and can work together seamlessly.
Let's explore each structural design pattern with its definition, main components, and C# code implementation along with explanations.
1. Adapter Pattern:
Definition:
Adapter Pattern allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces.
Main Components:
- Target: Defines the desired interface that the client uses.
- Adaptee: Represents the existing interface that needs adaptation.
- Adapter: Implements the
Targetinterface and delegates calls to theAdaptee.
C# Code Implementation:
// Target
public interface ITarget
{
void Request();
}
// Adaptee
public class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("Adaptee's Specific Request");
}
}
// Adapter
public class Adapter : ITarget
{
private readonly Adaptee adaptee;
public Adapter(Adaptee adaptee)
{
this.adaptee = adaptee;
}
public void Request()
{
adaptee.SpecificRequest();
}
}
// Client
public class Client
{
public void UseAdapter(ITarget target)
{
target.Request();
}
}
// Usage
var adaptee = new Adaptee();
var adapter = new Adapter(adaptee);
var client = new Client();
client.UseAdapter(adapter);
Explanation:
The Adapter Pattern allows the Adaptee with an incompatible interface to work with the Client through the ITarget interface. The Adapter class implements the ITarget interface and contains an instance of the Adaptee. The Request method in the Adapter delegates the call to the SpecificRequest method of the Adaptee, making it compatible with the Client.
2. Decorator Pattern:
Definition:
Decorator Pattern attaches additional responsibilities to an object dynamically. It is a structural pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.
Main Components:
- Component: Defines the interface for objects that can have responsibilities added to them.
- ConcreteComponent: Represents the base object to which additional responsibilities can be attached.
- Decorator: Maintains a reference to a
Componentobject and defines an interface that conforms toComponent. - ConcreteDecorator: Adds new responsibilities to the
Component.
C# Code Implementation:
// Component
public interface IComponent
{
void Operation();
}
// Concrete Component
public class ConcreteComponent : IComponent
{
public void Operation()
{
Console.WriteLine("Concrete Component Operation");
}
}
// Decorator
public abstract class Decorator : IComponent
{
protected IComponent component;
public Decorator(IComponent component)
{
this.component = component;
}
public virtual void Operation()
{
component.Operation();
}
}
// Concrete Decorator A
public class ConcreteDecoratorA : Decorator
{
public ConcreteDecoratorA(IComponent component) : base(component) { }
public override void Operation()
{
base.Operation();
Console.WriteLine("Concrete Decorator A Operation");
}
}
// Concrete Decorator B
public class ConcreteDecoratorB : Decorator
{
public ConcreteDecoratorB(IComponent component) : base(component) { }
public override void Operation()
{
base.Operation();
Console.WriteLine("Concrete Decorator B Operation");
}
}
// Client
public class Client
{
public void UseComponent(IComponent component)
{
component.Operation();
}
}
// Usage
var component = new ConcreteComponent();
var decoratorA = new ConcreteDecoratorA(component);
var decoratorB = new ConcreteDecoratorB(decoratorA);
var client = new Client();
client.UseComponent(decoratorB);
Explanation:
The Decorator Pattern allows new behavior to be added to an object dynamically by wrapping it in decorator classes. The Component interface defines the base interface, and the ConcreteComponent class provides the base implementation. Decorator classes (Decorator, ConcreteDecoratorA, ConcreteDecoratorB) extend the functionality of the Component without altering its structure. The Client can use any combination of the Component and its decorators.
3. Composite Pattern:
Definition:
Composite Pattern composes objects into tree structures to represent part-whole hierarchies. It allows clients to treat individual objects and compositions of objects uniformly.
Main Components:
- Component: Declares the interface for objects in the composition.
- Leaf: Represents individual objects in the composition.
- Composite: Represents compositions of objects and implements operations declared in the
Component. - Client: Manipulates objects in the composition through the
Componentinterface.
C# Code Implementation:
// Component
public interface IComponent
{
void Operation();
}
// Leaf
public class Leaf : IComponent
{
public void Operation()
{
Console.WriteLine("Leaf Operation");
}
}
// Composite
public class Composite : IComponent
{
private readonly List<IComponent> children = new List<IComponent>();
public void Add(IComponent component)
{
children.Add(component);
}
public void Remove(IComponent component)
{
children.Remove(component);
}
public void Operation()
{
Console.WriteLine("Composite Operation");
foreach (var child in children)
{
child.Operation();
}
}
}
// Client
public class Client
{
public void UseComponent(IComponent component)
{
component.Operation();
}
}
// Usage
var leaf1 = new Leaf();
var leaf2 = new Leaf();
var composite = new Composite();
composite.Add(leaf1);
composite.Add(leaf2);
var client = new Client();
client.UseComponent(composite);
Explanation:
The Composite Pattern treats both individual objects (Leaf) and compositions of objects (Composite) uniformly through a common Component interface. The Leaf class represents the individual objects, and the Composite class represents compositions by containing a collection of IComponent objects. The Operation method in the Composite class recursively calls the Operation method on its children. The Client can use any Component, whether it is a Leaf or a Composite.
4. Bridge Pattern:
Definition:
Bridge Pattern separates abstraction from implementation, allowing both to vary independently.
Main Components:
- Abstraction: Declares the abstraction's interface and maintains a reference to an object of type
Implementor. - RefinedAbstraction: Extends the interface defined by
Abstraction. - Implementor: Declares the interface for concrete implementation classes.
- ConcreteImplementor: Implements the
Implementorinterface.
C# Code Implementation:
// Implementor
public interface IImplementor
{
void OperationImpl();
}
// Concrete Implementor A
public class ConcreteImplementorA : IImplementor
{
public void OperationImpl()
{
Console.WriteLine("Concrete Implementor A Operation");
}
}
// Concrete Implementor B
public class ConcreteImplementorB : IImplementor
{
public void OperationImpl()
{
Console.WriteLine("Concrete Implementor B Operation");
}
}
// Abstraction
public abstract class Abstraction
{
protected IImplement
or implementor;
public Abstraction(IImplementor implementor)
{
this.implementor = implementor;
}
public abstract void Operation();
}
// Refined Abstraction
public class RefinedAbstraction : Abstraction
{
public RefinedAbstraction(IImplementor implementor) : base(implementor) { }
public override void Operation()
{
implementor.OperationImpl();
}
}
// Client
public class Client
{
public void UseAbstraction(Abstraction abstraction)
{
abstraction.Operation();
}
}
// Usage
var implementorA = new ConcreteImplementorA();
var implementorB = new ConcreteImplementorB();
var abstraction1 = new RefinedAbstraction(implementorA);
var abstraction2 = new RefinedAbstraction(implementorB);
var client = new Client();
client.UseAbstraction(abstraction1);
client.UseAbstraction(abstraction2);
Explanation:
The Bridge Pattern separates abstraction (Abstraction) from implementation (IImplementor). The Abstraction class maintains a reference to an object of type IImplementor and declares an abstract method (Operation). Concrete implementations of Abstraction (RefinedAbstraction) can use different implementations of IImplementor without altering their code. The Client can use different abstractions with different implementations seamlessly.
5. Flyweight Pattern:
Definition:
Flyweight Pattern minimizes memory usage or computational expenses by sharing as much as possible with related objects. It is used for optimizing performance and resource usage.
Main Components:
- Flyweight: Declares an interface through which flyweights can receive and act on extrinsic state.
- ConcreteFlyweight: Implements the Flyweight interface and adds storage for intrinsic state.
- UnsharedConcreteFlyweight: Not all flyweights need to be shared. This class holds data that cannot be shared.
- FlyweightFactory: Creates and manages flyweight objects.
- Client: Maintains the extrinsic state of flyweights.
C# Code Implementation:
// Flyweight
public interface IFlyweight
{
void Operation(int extrinsicState);
}
// Concrete Flyweight
public class ConcreteFlyweight : IFlyweight
{
private readonly string intrinsicState;
public ConcreteFlyweight(string intrinsicState)
{
this.intrinsicState = intrinsicState;
}
public void Operation(int extrinsicState)
{
Console.WriteLine($"Concrete Flyweight: Intrinsic State - {intrinsicState}, Extrinsic State - {extrinsicState}");
}
}
// Flyweight Factory
public class FlyweightFactory
{
private readonly Dictionary<string, IFlyweight> flyweights = new Dictionary<string, IFlyweight>();
public IFlyweight GetFlyweight(string key)
{
if (!flyweights.ContainsKey(key))
{
flyweights[key] = new ConcreteFlyweight(key);
}
return flyweights[key];
}
}
// Client
public class Client
{
public void UseFlyweight(IFlyweight flyweight, int extrinsicState)
{
flyweight.Operation(extrinsicState);
}
}
// Usage
var flyweightFactory = new FlyweightFactory();
var flyweight1 = flyweightFactory.GetFlyweight("A");
var flyweight2 = flyweightFactory.GetFlyweight("B");
var client = new Client();
client.UseFlyweight(flyweight1, extrinsicState: 1);
client.UseFlyweight(flyweight2, extrinsicState: 2);
Explanation:
The Flyweight Pattern minimizes memory usage by sharing common parts of state between objects. The IFlyweight interface declares the operation that accepts extrinsic state. The ConcreteFlyweight class implements this interface and stores intrinsic state that can be shared. The FlyweightFactory manages the flyweight objects and ensures that they are shared when requested. The Client maintains the extrinsic state and uses the flyweights as needed.
6. Facade Pattern:
Definition:
Facade Pattern provides a simplified interface to a set of interfaces in a subsystem, making it easier to use.
Main Components:
- Facade: Provides a unified interface to a set of interfaces in a subsystem.
- Subsystem Classes: Classes that implement subsystem functionality.
- Client: Uses the facade to interact with the subsystem.
C# Code Implementation:
// Subsystem Class A
public class SubsystemA
{
public void OperationA()
{
Console.WriteLine("Subsystem A Operation");
}
}
// Subsystem Class B
public class SubsystemB
{
public void OperationB()
{
Console.WriteLine("Subsystem B Operation");
}
}
// Subsystem Class C
public class SubsystemC
{
public void OperationC()
{
Console.WriteLine("Subsystem C Operation");
}
}
// Facade
public class Facade
{
private readonly SubsystemA subsystemA;
private readonly SubsystemB subsystemB;
private readonly SubsystemC subsystemC;
public Facade()
{
subsystemA = new SubsystemA();
subsystemB = new SubsystemB();
subsystemC = new SubsystemC();
}
public void FacadeOperation()
{
Console.WriteLine("Facade Operation");
subsystemA.OperationA();
subsystemB.OperationB();
subsystemC.OperationC();
}
}
// Client
public class Client
{
public void UseFacade(Facade facade)
{
facade.FacadeOperation();
}
}
// Usage
var facade = new Facade();
var client = new Client();
client.UseFacade(facade);
Explanation:
The Facade Pattern provides a simplified interface (Facade) to a set of subsystem classes (SubsystemA, SubsystemB, SubsystemC). The Facade class delegates client requests to the appropriate subsystem classes, hiding the complexities of the subsystem. The Client can interact with the subsystem through the facade without needing to know the details of the subsystem classes.
7. Proxy Pattern:
Definition:
Proxy Pattern provides a surrogate or placeholder for another object to control access to it.
Main Components:
- Subject: Defines the common interface for
RealSubjectandProxyso that aProxycan be used wherever aRealSubjectis expected. - RealSubject: Represents the real object that the proxy represents.
- Proxy: Maintains a reference to the
RealSubjectand controls access to it.
C# Code Implementation:
// Subject
public interface ISubject
{
void Request();
}
// Real Subject
public class RealSubject : ISubject
{
public void Request()
{
Console.WriteLine("Real Subject Request");
}
}
// Proxy
public class Proxy : ISubject
{
private readonly RealSubject realSubject;
public Proxy()
{
realSubject = new RealSubject();
}
public void Request()
{
Console.WriteLine("Proxy Request");
realSubject.Request();
}
}
// Client
public class Client
{
public void UseSubject(ISubject subject)
{
subject.Request();
}
}
// Usage
var realSubject = new RealSubject();
var proxy = new Proxy();
var client = new Client();
client.UseSubject(realSubject);
client.UseSubject(proxy);
Explanation:
The Proxy Pattern provides a surrogate or placeholder (Proxy) for another object (RealSubject) to control access to it. The ISubject interface declares the common interface for both RealSubject and Proxy. The Proxy class maintains a reference to the RealSubject and controls access to it by implementing the Request method. The Client can use either the RealSubject or the Proxy interchangeably, as they both adhere to the ISubject interface.
These design patterns provide flexible solutions to common structural design problems, allowing for better code organization, maintenance, and extensibility. Understanding when and how to apply these patterns can significantly improve the design and maintainability of software systems.