-
OOP Design Pattern - Structural Pattern프로그래밍 언어 2024. 1. 18. 21:33
Adapter
Adapter Pattern
- Allow the interface of an existing class to be used as another interface
- Allow two incompatible interfaces to work together
- Interfaces may be imcompatible, but the inner functionality should suit the need
- By converting the interface of one class into an interface
- Often used to make existing classes work with others without modifying their source code
Overview
- Solves problem like:
- How can a class be reused that does not have an interface that a client requires?
- How can classes that have incompatible interfaces work together?
- How can an alternative interface be provided for a class?
- Describes how to solve such problems:
- Define a separate adapter class that converts the (incompatible) interface of a class (adaptee) into another interface (target) clients require.
- Work through an adapter to work with (reuse) classes that do not have the required interface.
- Clients do not know whether they work with a target class directly or through an adapter
Usage
- Used when the wrapper must respect a particular interface and must support polymorphic behavior
Structure
- The client works through an adapter class that implements the target interface in terms of adaptee
- The object adapter way implements the target interface by delegating to an adaptee object at run-time
- The class adapter way implements the target interface by ineriting from an adaptee class at compile-time
Example
public interface ILightningPhone { void ConnectLightning(); void Recharge(); } public interface IUsbPhone { void ConnectUsb(); void Recharge(); } public sealed class AndroidPhone : IUsbPhone { private bool isConnected; public void ConnectUsb() { this.isConnected = true; } public void Recharge() { if(this.isConnected) Console.WriteLine("Android recharging"); else Console.WriteLine("Connect the USB"); } } public sealed class ApplePhone : ILightningPhone { private bool isConnected; public void ConnectLightning() { this.isConnected = true; } public void Recharge() { if(this.isConnected) Console.WriteLine("Apple rechargine"); else Console.WriteLine("Connect the Lightning"); } } public sealed class LightningToUsbAdapter : IUsbPhone { private readonly ILightningPhone lightningPhone; private bool isConnected; public LightningToUsbAdapter(ILightningPhone p_lightningPhone) { this.lightningPhone = p_lightningPhone; this.lightningPhone.ConnectLightning(); } public void ConnectUsb() { this.isConnected = true; } public void Recharge() { if(this.isConnected) this.lightningPhone.Recharge(); else Console.WriteLine("Connect the USB"); } } public void Main() { ILightningPhone applePhone = new ApplePhone(); IUsbPhone adapterCable = new LightningToUsbAdapter(applePhone); adapterCable.ConnectUsb(); adapterCable.Recharge(); }
Bridge
Bridge Pattern
- Decouple an abstraction from its implementation so that the two can vary independently
- The bridge uses encapsulation, aggregation, and inheritance to separate responsibilities into different classes
- Useful when both the class and what it does vary often
Overview
- Solves problem like:
- An abstraction and its implementation should be defined and extended independently from each other
- A compile-time binding between an abstraction and its implementation should be avoided so that an implementation can be selected at run-time
- Describes how to solve such problems:
- Separate an abstraction from its implementation by putting them in separate class hierarchies
- Implement the abstraction in terms of (by delegating to) an implementor object, enable to configure an abstraction with an implementor object at run-time
Example
public interface IBridge // Helps in providing truly decoupled architecture { void Function1(); void Function2(); } public class Bridge1 : IBridge { public void Function1() { Console.WriteLine("Bridge1.Function1"); } public void Function2() { Console.WriteLine("Bridge1.Function2"); } } public class Bridge2 : IBridge { public void Function1() { Console.WriteLine("Bridge2.Function1"); } public void Function2() { Console.WriteLine("Bridge2.Function2"); } } public interface IAbstractBridge { void CallMethod1(); void CallMethod2(); } public class AbstractBridge : IAbstractBridge { public IBridge bridge; public AbstractBridge(IBridge p_bridge) { this.bridge = p_bridge; } public void CallMethod1() { this.bridge.Function1(); } public void CallMethod2() { this.bridge.Function2(); } }
Composite
Composite Pattern
- A group of objects that are treated the same way as a single instance of the same type of object
- Compose objects into tree structures to represent part-whole hierarchies
- Allow clients to treat individual objects and compositions uniformly
Overview
- Solves problem like:
- A part-whole hierarchy should be represented so that clients can treat part and whole objects uniformly
- A part-whole hierarchy should be represented as tree structure
- Describes how to solve such problems:
- Define a unified component interface for both part (leaf) objects and whole (composite) objects
- Individual leaf objects implement the component interface directly
- Composite objects forward requests to their child components
Motivation
- Problem : have to discriminate between a leaf-node and a branch dealing with tree-structured data
- Solution : an interface that allows treating complex and primitive objects uniformly
- Manipulate a single instance of the object just as you would manipulate a group of them
When to use
- Should be used when clients ignore the difference between compositions of objects and individual objects
Structure
- Design for uniformity : child-related operations are defined in the component interface
- Design for type safety : child-related operations are defined only in the composite class
- Component
- The abstract for all components, including composite ones
- Declares the interface for objects in the composition
- (Optional) Defines an interface for accessing a component's parent in the recursive structure
- Leaf
- Leaf objects in the composition
- Implements all component methods
- Composite
- A composite component (component having children)
- Implements methods to manipulate children
- Implements all component methods, generally by delegating them to its children
Example
public interface IComponent // Creating an interface { void DisplayPrice(); } public class Leaf : IComponent // Creating a leaf class { private int price; private string name; public Leaf(string p_name, int p_price) { this.price = p_price; this.name = p_name; } public void DisplayPrice() { Console.WriteLine(name + " : " + price); } } public class Composite : IComponent // Creating a composite class { private string name; private List<IComponent> components = new List<IComponent>(); public Composite(string p_name) { this.name = p_name; } public void AddComponent(IComponent p_component) { components.Add(p_component); } public void DisplayPricee() { Console.WirteLine(name); foreach(var t_item in components) t_item.DisplayPrice(); } }
Decorator
Decorator Pattern
- Allow behavior to be added to an individual object, dynamically, without affecting the behaviour of other objects from the same class
- Useful for adhering to the Single Responsibility Principle, allowing functionality to be devided between classes with unique areas of concern
- Useful for adhering to the Open-Closed Principle, allowing the functionality of a class to be extended without being modified
- Can be more efficient than subclassing because an object's behavior can be augmented without defining an entirely new object
Overview
- Solve problem like:
- Responsibilities should be added to (and removed from) an object dynamically at run-time
- A flexible alternative to subclassing for extending functionality should be provided
- Describe how to solve such problem:
- Implement the interface of the extended (decorated) object (Component) transparently by forwording all requests to it
- Perform additional functionality before / after forwarding a request
Intent
- Sequence of steps of designing a new Decorator class that wraps the original class
- Subclass the original Component class into a Decorator class
- In the Decorator class, add a Component pointer as a field
- In the Decorator class, pass a Component to the Decorator constructor to initialize the Component pointer;
- In the Decorator class, forward all Component methods to the Component pointer
- In the ConcreteDecorator class, override any Component method(s) whose behavior need to be modified
- Decorators and the original class object share a common set of features
- The decoration features are usually defined by an interface, mixin, or class inheritance
Example
public interface IBike { string GetDetails(); double GetPrice(); } public class AluminiumBike : IBike { public double GetPrice() => 100.0; public string GetDetails() => "Aluminium Bike"; } public class CarbonBike : IBike { public double GetPrice() => 1000.0; public string GetDetails() => "Carbon Bike"; } public abstract class BikeAccessories : IBike { private readonly IBike _bike; public BikeAccessories(IBike bike) { _bike = bike; } public virtual double GetPrice() => _bike.GetPrice(); public virtual string GetDetails() => _bike.GetDetails(); } public class SecurityPackage : BikeAccessories { public SecurityPackage(IBike bike) : base(bike) {} public override double GetPrice() => base.GetPrice() + 1; public override string GetDetails() => base.GetDetails() + " + Security Pakage"; } public class SportPackage: BikeAccessories { public SportPackage(IBike bike) : base(bike) {} public override double GetPrice() => base.GetPrice() + 10; public override string GetDetails() => base.GetDetails() + " + Sport Pakage"; } public class BikeShop { public static void UpgradeBike() { var basicBike = new AluminiumBike(); BikeAccessories upgraded = new SportPackage(basicBike); upgraded = new SecurityPackage(upgraded); Console.WriteLine($"Bike: '{upgraded.GetDetails()}' Cost : {upgraded.GetPrice()}"): } }
Facade
Facade Pattern
- An object that serves as a front-facing interface masking more complex underlying or structural code
- Improve the readability and usability of a software library by masking interaction with more complex components behind a single
- Provide a context-specific interface to more generic functionality, involving a single wrapper class that contains a set of members required by the client
- Serve as a launching point for a broader refactor of monolithic or tightly-coupled systems in favor of more loosely-coupled code
Overview
- Solves problem like:
- To make a complex subsystem easier to use, a simple interface should be provided for a set of interfaces in the subsystem
- The dependencies on a subsystem should be minimized
- Describes how to solve such problem:
- Implements a simple interface in terms of (by delegating to) the interfaces in the subsystem
- May perform additional functionality before / after forwarding a request
Usage
- Often used to hide the complexities of the larger system and provide a simpler interface to the client when a system is very complex or difficult to understand because the system has many interdependent classes or because its source code is unavailable
- Used when an easier or simpler interface to an underlying object is desired
- Used when an entry point is needed to each level of layered software
- Used when the abstractions and implementations of a subsystem are tightly coupled
Example
struct CPU { void Freeze(); void Jump(long position); void Execute(); }; struct Memory { void Load(long position, char* data); }; struct HardDrive { char* Read(long lba, int size); }; class ComputerFacade { private: CPU cpu_; Memory memory_; HardDrive hard_drive_; public: void Start() { cpu_Freeze(); memory_Load(kBootAddress, hard_drive_.Read(kBootSector, kSectorSize)); cpu_.Jump(kBootAddress); cpu_.Execute(); } }; int main() { ComputerFacade computer; computer.Start(); }
Flyweight
Flyweight Pattern
- Refers to an object that minimizes memory usage by sharing some of its data with other similar objects
Overview
- Useful when dealing with large numbers of objects with simple repeated elements that would use a large amount of memory if individually stored
- Store intrinsic state that is invariant, context-independent and shareable
- Provide an interface for passing in extrinsic state that is variant, context-dependent and can't be shared
Structure
- Client : a class which uses the flyweight pattern
- Flyweight Factory : a class which creates and shares Flyweight objects
- Flyweight interface : an interface which takes in extrinsic state and performs an operation
- Flyweight class : a class which implements Flyweight and stores intrinsic state
- The sequence of interactions at run-time
- The Client object calls getFlyweight(key) on the Flyweight Factory, which returns a Flyweight1 object
- After calling operation(extrinsicState) on the returned Flywegith1 object, the Client again calls getFlyweight(key) on the Flyweight Factory
- The Flyweight Factory returns the already-existing Flyweight1 object
Example
public class Flyweight // Defines Flyweight object that repeats itself { public string Name { get; set; } public string Location { get; set; } public string Website { get; set; } public byte[] Logo { get; set; } } public static class Pointer { public static readonly Flyweight Company = new Flyweight { "Abc", "XYZ", "www.example.com" }; } public class MyObject { public string Name { get; set; } public string Company => Pointer.Company.Name; }
Proxy
Proxy Pattern
- A wrapper or agent object that is being called by the client to access the real serving object behind the scenes
- For client, usage of a proxy object is similar to using the real object
Overview
- Solves problem like:
- The access to an object should be controlled
- Additional functionality should be provided when accessing an object
- Describes how to solve such problems:
- Can be used as substitute for another object (Subject)
- Implements additional functionality to control the access to the Subject
- To act as substitute for a subject, a proxy must implement the Subject interface
Possible Usage Scenarios
- Remote proxy : The local object is a proxy for the remote object
- Virtual proxy : In place of a complex or heavy object, a skeleton representation may be advantageous in some cases
- Protection proxy : A protection proxy might be used to control access to a resource based on access rights
Example
interface ICar { void DriveCar(); } public class Car : ICar // Real Object { public void DriveCar() { Console.WriteLine("Car has been driven!"; } } public class ProxyCar : ICar // Proxy Object { private Driver driver; private ICar realCar; public ProxyCar(Driver driver) { this.driver = driver; this.realCar = new Car(); } public void DriveCar() { if(driver.Age < 16) Console.WriteLine("Sorry, the driver is too young to drive"); else this.realCar.DriveCar(); } } public class Driver { public int Age { get; set; } public Driver(int age) { this.Age = age; } } private void btnProxy_Click(object sender, EventArgs e) // How to use proxy class? { ICar car = new ProxyCar(new Driver(15)); car.DriveCar(); car = new ProxyCar(new Driver(25)); car.DriveCar(); }
'프로그래밍 언어' 카테고리의 다른 글
OOP Design Pattern - Behavioral Pattern 1 (2) 2024.10.14 [C#] Public Field vs Auto-Implemented Property (0) 2024.04.06 OOP Design Pattern - Creational Pattern (0) 2024.01.12 [C++] 이동 의미론 Move Semantics (0) 2023.11.20 입실론 테스트 (0) 2023.11.20