0% found this document useful (0 votes)
2 views42 pages

Chapter 31

Uploaded by

kuneezykuneezuz
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views42 pages

Chapter 31

Uploaded by

kuneezykuneezuz
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

1

Chapter 31
Structural Design Patterns
1. Introduction

There are, fundamentally, three ways in which data structures can be defined in C#:

• Statically, when you simply write the classes and they get compiled. This is the most common
case out there.

• Code generation happens when structures get created from templates or databases or some
user scripts. For example, Visual Studio has an option to generate a type if you right-click on
an undefined class.

• Dynamically, i.e., at runtime. This is the most sophisticated option. Advanced libraries are
capable of constructing data structures and compiling them into executable code right at the
moment when the application is executing.

This chapter deals with various design patterns that provide an approach to assemble objects and
classes into larger structures, while keeping these structures flexible and efficient. Structural
design patterns use inheritance to compose interfaces or implementations. Structural patterns are
all about setting up the structure of an application so as to improve SOLID conformance as well
as general usability and maintainability of your code.

2. Adapter pattern

2.1 Introduction

We all know what an adapter is. We need an adapter to plug a two-pronged plug into a three-
point electrical outlet. "An adapter is a structural design pattern that allows objects with
incompatible interfaces to collaborate." (Shvets, 2020, p 150). This pattern is also known as the
wrapper.

The adapter pattern is recognizable by a constructor which takes an instance of a different


abstract/interface type as parameter. When the adapter receives a call to any of its methods, it
translates parameters to the appropriate format and then directs the call to one or several
methods of the wrapped object.

Copyright: PJ Blignaut, 2022


2

Ohkravi (2017) says that the Adapter pattern is one of four patterns that are very similar and
easily confused: The others are the Façade, Proxy, and Decorator.
• The Adapter pattern is about making two interfaces that are not compatible, compatible.
• The Façade pattern is about taking a bunch of complex interactions and creating a façade
that you can use instead of dealing with all those complex objects and complex interactions.
• The Proxy is placed between the client and something that you want to call. So instead of
calling a method directly, you call the proxy who calls that method.
• The Decorator is a way of adding behaviour to some particular object without opening the
object and changing it.

2.2 Conceptual class diagram

Figure 1. Adapter pattern

1. The Client class contains the existing business logic of the program. This may not be
changed to suit the methods as provided by the legacy Adaptee (service) class.

2. The Client Interface (or Target class) describes a protocol that other classes must follow
to be able to collaborate with the client code.

3. The Service (or Adaptee) is some useful class (usually 3rd-party or legacy) that provides a
service. The client can’t use this class directly because it has an incompatible interface.

4. The Adapter (wrapper) is a class that’s able to work with both the client and the service: it
implements the client interface, while wrapping the service object. The adapter receives calls
from the client via the adapter interface and translates them into calls to the wrapped service
object in a format it can understand.

The client code doesn’t get coupled to the concrete adapter class as long as it works with the
adapter via the client interface. Thanks to this, you can introduce new types of adapters into the
program without breaking the existing client code. This can be useful when the interface of the
service class gets changed or replaced: you can just create a new adapter class without changing
the client code.

This is actually simple: Just put a class between the two existing classes of which you may not
change anything.

2.3 Example

Copyright: PJ Blignaut, 2022


3

Consider again the DIP example of Chapter 2. Suppose a client application wants to access the
list of students with typical queue functionality. That means that adding a student should be
enqueued at the end of the queue and removing should be implemented as a dequeue at the front
of the queue.

The example below shows a minimum implementation of a typical queue and only serves to
explain the concept. Of course, one can add methods such as Contains, Position and also an
enumerator if needed.

Adaptee

The class that must be adapted is referred to as the service or adaptee. It is the class that provides
the service of data and must be adapted to conform to a client's needs. In this case, it is the
original list of students. In the example, this is class is provided in a separate project,
ListStudents.

public class Student


{
public string Name { get; private set; }
public Student(string name_) { Name = name_; }
public override string ToString() { return Name; }
} //class Student

public interface IStudents


{
int Count { get; }
void AddStudent(Student student);
void RemoveStudent(Student student);
Student this[int i] { get; }
void Clear();
} //interface IStudents

public class Students : IStudents


{
private List<Student> lstStudents = new List<Student>();
public int Count { get { return [Link]; } }
public void Clear() { [Link]();}
public void AddStudent(Student student) { [Link](student); }
public void RemoveStudent(Student student) { [Link](student); }
public Student this[int i] //Indexer
{
get { return i>=0 && i < [Link] ? lstStudents[i] : null; }
}
} //class Students

Adapter

The client application prescribes an interface to which the adapter must conform. In this case,
the client needs access to the list of students as if it is a queue.

Adapted
public interface IQueue<T>
{
void Clear();
int Count { get; }
void Enqueue(T value);
T Dequeue();
T Peek();
} //interface

Copyright: PJ Blignaut, 2022


4

The adapter's constructor takes the adaptee as parameter and assigns a local alias to it.
Thereafter, it implements the expected members as prescribed by the interface.
class ListToQueue : IQueue<Student>
{
private Students students; //Local alias of the class to be adapted
public int Count => [Link];

public ListToQueue(Students students) //Constructor


{
[Link] = students;
}

public void Clear()


{
[Link]();
}

public void Enqueue(Student value)


{
[Link](value);
}

public Student Peek()


{
return students[0];
} //Peek

public Student Dequeue()


{
Student student = Peek();
[Link](student);
return student;
}
} //class ListToQueue

The adapter should delegate most of the work to the service object. It should only intervene
when something needs to be done differently. In this example, the Clear() and Enqueue()
methods and the Count property in the adapter, just passes the job through to the service class.
The Peek() and Dequeue() methods need some work to add the behaviour for a typical queue.

Client

class Client
{
static void Main(string[] args)
{
//Original class (adaptee)
Students students = new Students();

//Adapter
IQueue<Student> qStudents = new ListToQueue(students);

//Handling of original class through adapter as if it is a Queue<Student>


// and not a List<Student>
[Link](new Student("John"));
[Link](new Student("Mike"));
[Link](new Student("Susan"));
[Link]("\tPeek: " + [Link]());
[Link]();
[Link]("\tPeek: " + [Link]());

//Wait for user

Copyright: PJ Blignaut, 2022


5

[Link]();
} //Main
} //Client
Class diagram

Figure 2. Adapter pattern for a specific example

• The association between Client and IStudents is because we need to create an


instance of the service in the client which is then passed through to the adapter's
constructor.

2.4 Other examples

• Listing AD01 Adapter (Shvets Conceptual)


[Link]
• Listing AD02 Adapter (Shvets p 157)
• Listing AD03 Adapter (Switch on)
• Listing AD04 Adapter (Nesteruk Draw lines)
• Listing AD05 Adapter (DoFactory Chemical)
• [Link]
• [Link]
adaptor-design-patter/
• [Link]
• Project 4, 2020 (csv to json)
• Project 4, 2021 (Imperial to metric units)
• Exam 2, 2021, Question 3 (string[][] to List<string[]>)
• Test 1, 2021, Question 6 (Imperial to metric units)
• Test 3, 2020, Question 5 (List<string[]> to json)
• Project 4, 2023 (Fractions)

2.5 Pros and cons

Advantages

• Single Responsibility Principle. You can separate the interface or data conversion code
from the primary business logic of the program.

Copyright: PJ Blignaut, 2022


6

• Open/Closed Principle. You can introduce new types of adapters into the program without
breaking the existing client code, as long as they work with the adapters through the client
interface.

Disadvantage

• The overall complexity of the code increases because you need to introduce a set of new
interfaces and classes.

2.6 When to use

• Use the Adapter pattern when you want to use some existing class, but its interface is not
compatible with the rest of your code.
• Use the pattern when you want to use existing subclasses that lack some common
functionality that cannot be added to the superclass.

2.7 Further reading

• Cardoso (p 177 )
• Cooper, Chapter 14
• Freeman et al., p 235
• Gamma, Helm, Johnson, Vlissides (1995, p 185)
• Martin (2000), p29
• Martin & Martin (2006, Chapter 33, p 631)
• Nesteruk (2019, p 109)
• Okhravi, C. 2017. Adapter pattern
[Link]
VR9eMBpc&index=8
• Shvets, p 150
• Tripathi, P. 2014. Adapter and Façade design pattern in C#.
[Link]
C-Sharp/

3. Bridge pattern

3.1 Introduction

One of the problems with implementing an abstract class with inheritance is that the derived
class is so tightly coupled to the base class. This can lead to problems when other clients want
to use the derived class methods without dragging along the baggage of the base hierarchy.
Revise also the YAGNI and ISP principles. With the Bridge pattern, the details of methods are
moved to a separate hierarchy, the so-called implementation.

"Bridge is a structural design pattern that lets you split a large class or a set of
closely related classes into two separate hierarchies — abstraction and
implementation — which can be developed independently of each other." (Shvets,
2020, p 163)

The terms abstraction and implementation are used here with a different meaning. In many
programming languages such as C#, an abstraction is thought of as an interface or an abstract
class and implementation is usually a structural implementation of that interface or a concrete

Copyright: PJ Blignaut, 2022


7

class that inherits from an abstract base class. Generally speaking, however, an abstraction is a
model of the real world in terms of an object's appearance and behaviour (cf Chapter 1), whereas
an implementation refers to the details of some aspect of its behaviour.

• Abstraction: How the object appears and what it can do (no details)
• Implementation: Details of what an object can do. How does it do what it does.

Actually, it does not really matter what we call the different hierarchies. Fact is, we work with
separate hierarchies that can be expanded independently with one hierarchy containing a
reference to the other.

3.2 Example

Scenario

Consider the scenario of a banking system again. We can abstract an account and have several
types of accounts, e.g. savings account, credit account, cheque account, etc. Every account has
properties such as a balance, minimum permissible balance, interest rate, etc. For every account,
we have to define operations such as Withdraw, Deposit, PayInterest, etc.

Figure 3. Simple banking system

Separate account types from transaction types

The problem comes in when we want to add an operation, e.g. to subtract the banking cost. All
account types have to be modified and the existing code might break. Also, if we want to add a
new type of account, e.g. fixed bond, we have to add all the operations again. If we want to
change the business logic of one of the operations, it has to be done for all account types. This
can quickly escalate into an uncontrollable scenario.

The solution is to separate the account types from the transaction types and have a bridge
between the two hierarchies:

Copyright: PJ Blignaut, 2022


8

Figure 4. Bridge between account types and transaction types

In this way, we decouple the accounts from the details of transactions. That means that we can
add transaction types without touching the account types and we can add data fields and
properties to the accounts without touching the way in which the transactions work.

In general terms, we decouple an abstraction (accounts) from its implementation (transactions)


so that the two can vary independently. That means that we can add and edit implementations
without touching the abstraction and we can add and edit abstractions without touching the
implementations.

The derived abstractions (Savings, Cheque and Credit) are referred to as refined abstractions
and the derived implementations (Withdraw, Deposit, etc.) as concrete implementations.

Abstraction

public abstract class AAccount


{
private ITransaction transaction { get; set; } //Bridge
public decimal Balance { get; protected set; }
public decimal minBalance { get; protected set; }
public decimal interestRate { get; protected set; }

public AAccount(decimal initialBalance)


{
Balance = initialBalance;
}

public void DoTransaction(ITransaction tr, decimal value)


//value can be a rate or amount
{
transaction = tr;
Balance = [Link](this, Balance, value);
}
} //class AAccount

• The abstraction contains a private instance of the implementation (ITransaction). This


serves as a bridge between the two herarchies. This instance can either be initialised in the
constructor or when a transaction is called by the client.
• The DoTransaction method, calls a method of the same name in the interface.
• The current abstraction object (keyword this) is passed through to the implementation
because some of its properties might be needed there.

Copyright: PJ Blignaut, 2022


9

Refined abstractions

class SavingsAccount : AAccount


{
public SavingsAccount(decimal initialBalance) : base(initialBalance)
{
minBalance = 0;
interestRate = 10;
}
} //SavingsAccount

class CreditAccount : AAccount


{
public CreditAccount(decimal initialBalance) : base(initialBalance)
{
minBalance = -10000;
interestRate = 2.5m;
}
} //CreditAccount

class ChequeAccount : AAccount


{
public ChequeAccount(decimal initialBalance) : base(initialBalance)
{
minBalance = -1000;
interestRate = 5m;
}
} //ChequeAccount

• The constructors of the refined abstractions call the constructor of the base class and pass
through all the parameters.
• Values that are specific to a refined abstraction are set in the relevant constructors, e.g. min
permissible balance and interest rate.

Implementation

public interface ITransaction


{
decimal DoTransaction(AAccount account, decimal balance, decimal value);
//value can be an amount or a rate
}

• Each concrete implementation will implement the DoTransaction method as


applicable for the specific concrete implementation. For example, for a deposit, the
value parameter will be an amount that has to be added to the balance. For a withdrawal,
the value must be subtracted from the balance. For interest, value is a rate as a
percentage of the current balance which must be used to update the balance.
• The new balance is returned as the result of the method.
• We need the AAccount instance in case we need to refer to some of its properties in the
concrete implementations. For example, to do a withdrawal, we need to know the
minimum permissible balance of the particular account.

Concrete implementations

public class Deposit : ITransaction


{
public decimal DoTransaction(AAccount accnt, decimal balance, decimal amount)
{
return balance + amount;

Copyright: PJ Blignaut, 2022


10

}
} //class Deposit

public class Withdraw : ITransaction


{
public decimal DoTransaction(AAccount accnt, decimal balance, decimal amount)
{
if (balance - amount >= [Link])
return balance - amount;
else
return balance;
}
} //class Withdraw

public class Interest : ITransaction


{
public decimal DoTransaction(AAccount accnt, decimal balance, decimal amount)
{
return balance + [Link] * balance/100;
}
} //class Interest

Client

class Client
{
static void Main()
{
//Implementations
ITransaction withdraw = new Withdraw();
ITransaction deposit = new Deposit();
ITransaction interest = new Interest();

//Abstractions
AAccount savings = new SavingsAccount(500);
[Link]("\tBalance: " + [Link]("C").PadLeft(10));

//Operations
[Link](withdraw, 200);
[Link]("\tBalance: " + [Link]("C").PadLeft(10));
[Link](withdraw, 500); //Balance cannot be less than minimum
[Link]("\tBalance: " + [Link]("C").PadLeft(10));

[Link](deposit, 700);
[Link]("\tBalance: " + [Link]("C").PadLeft(10));

[Link](interest, 0);
[Link]("\tBalance: " + [Link]("C").PadLeft(10));

//Wait for user


[Link]("\n\tPress any key to exit ....");
[Link]();
} //Main
} //class Client
Output

Copyright: PJ Blignaut, 2022


11

• The client declares instances of each of the concrete implementation types and use these to
specify to the abstraction how the DoTransaction method must be done.
• All communication with the bank system is through the abstraction.

Class diagram

Figure 5. Complete class diagram of bridge pattern for banking system

3.3 Conceptual class diagram

Figure 6. Bridge pattern - conceptual

• Implementation (Number 2) is an interface or abstract class.


• Abstraction (Number 1) is an abstract class or interface. It contains a property (the bridge)
that references the implementer. The constructor will set this value. The other members are

Copyright: PJ Blignaut, 2022


12

high level without detail. They refer to methods in the concrete implementations where
details are done.
• Refined abstraction (Number 4) is an extension to Number 1. It is optional.
• The concrete implementations file (Number 3) contains a class for each variation of the
implementation. Details of methods and properties in Number 1 and Number 4 are specified
here.
• The client class accesses the properties through the abstractions and not through the concrete
implementations.

Another example scenario

Consider a scenario of different types of vehicles, e.g. cars, trains and boats. Each type of
vehicle has properties that are not applicable to the others. The details of every type of vehicle
must be saved to text file, serialized binary file, database table or json. We do not want to create
a separate Save method for each type of vehicle for each data format in which the data must be
saved.

3.4 Other examples

• Listing BR01 Bridge (Shvets Conceptual)


• Listing BR02 Bridge (Shvets p171)
[Link]
• Listing BR03a NoBridge (Chauhan)
• Listing BR03 Bridge (Chauhan)
[Link]
• Listing AD05 Adapter (DoFactory)
• [Link]
• [Link]
design-pattern/
• Project 4, 2021, Part 2
• Project 4, 2023

3.5 Pros and cons

Advantages

• You can create platform-independent classes and apps.


• The client code works with high-level abstractions. It isn’t exposed to the platform details.
• Open/Closed Principle. You can introduce new abstractions and implementations
independently from each other.
• Single Responsibility Principle. You can focus on high-level logic in the abstraction and on
platform details in the implementation.
• Since we can change the reference to the implementer in the abstraction, we are able to
change the abstraction’s implementer at run-time.

Disadvantage

• You might make the code more complicated by applying the pattern to a highly cohesive
class.

Copyright: PJ Blignaut, 2022


13

3.6 When to use

• Use the Bridge pattern when you want to divide and organize a huge class that has several
variants of some functionality (for example, if the class can work with various database
servers).
• Use the pattern when you need to extend a class in several orthogonal (independent)
dimensions.
• Use the Bridge if you need to be able to switch implementations at runtime.

3.7 Further reading

• Cardoso, p 260
• Cooper, Chapter 15
• Gamma, Helm, Johnson, Vlissides (1995, p 198)
• Martin (2000, p 30)
• Martin & Martin (2006, Chapter 33, p 637)
• Nesteruk (2019, p 123)
• Okhravi, C. 2017.
[Link]
R9eMBpc&index=11
• Shvets, p 164
• Sonmez, J. 2015. Bridge Pattern. [Link]
the-bridge-pattern/
• Wikipedia. [Link]

4. Composite pattern

4.1 Introduction

The Composite pattern is used when we need to treat a group of objects and a single object in
the same way. The Composite pattern allows you to compose objects into tree structures and
then work with these structures as if they were individual objects. This means that this design
pattern makes sense when part of the data in the system can be represented as a tree. The
Composite pattern allows you to run a specific behaviour recursively over all components of an
object tree.

4.2 Revision

It is essential to revise the content of Chapter 25 of the first semester. You must at least make
sure that you understand the basic tree structure (Example 25.1) and understand how to apply
breadth-first and depth-first traversals.

Copyright: PJ Blignaut, 2022


14

4.3 Conceptual class diagram

Figure 7. Composite pattern - conceptual

• The Component interface (Number 1) describes operations that are common to both simple
and complex elements of the tree.
• The Leaf (Number 2) is a basic element of a tree that does not have sub-elements. Usually,
leaf components end up doing most of the real work.
• The Container (aka composite) (Number 3) is an element that has sub-elements: leaves or
other containers. A container does not know the concrete classes of its children. It works
with all sub-elements only via the component interface. Upon receiving a request, a container
delegates the work to its sub-elements, processes intermediate results and then returns the
final result to the client.
• The Client works with all elements through the component interface. As a result, the client
can work in the same way with both simple and complex elements of the tree.
• Note that both the leaf and the compound object inherit from the same interface.

4.4 Example

Consider the following hierarchy of employees in an organisation. Each employee has fields
for their ID (int) and name (string). Some employees may be supervising other employees.

1_Component

public interface IEmployee


{
int EmpID { get; set; }
string Name { get; set; }
} //interface IEmployee

Copyright: PJ Blignaut, 2022


15

• The component interface provides an abstraction of both leaves and container objects.

2_Leaf

public class BasicEmployee : IEmployee


{
public int EmpID { get; set; }
public string Name { get; set; }
} //public class BasicEmployee

• A leaf implements the interface and has no extra members..

3_Composite

public class Employee : IEmployee


{
private List<IEmployee> lstSubordinates = new List<IEmployee>();

public int EmpID { get; set; }


public string Name { get; set; }

public void AddSubordinate(IEmployee subordinate)


{
[Link](subordinate);
}

public void RemoveSubordinate(IEmployee subordinate)


{
[Link](subordinate);
}

public List<IEmployee> GetChildren()


{
return [Link];
}

public List<IEmployee> GetDescendants()


{
List<IEmployee> lstDescendants = new List<IEmployee>();
DFS(this, lstDescendants);
return lstDescendants;
}

private void DFS(IEmployee node, List<IEmployee> lstDescendants)


{
[Link](node);
if (node is Employee) //Not a leaf
foreach (IEmployee child in ((Employee)node).GetChildren())
DFS(child, lstDescendants);
} //public DFS
} //public class Employee

• The Employee class inherits from IEmployee. That means it should implement EmpID and
Name. Besides being a container for employees, it is an employee itself.
• The lstSubOrdinates is private and contains a list of all immediate subordinates of the
current employee. This is a recursive declaration since an employee can contain employees.
• The AddSubordinate method adds a subordinate to the list of immediate subordinates.
• The RemoveSubordinate method removes a subordinate from the list of immediate
subordinates if it exists in the list of immediate subordinates.
Copyright: PJ Blignaut, 2022
16

- If a subordinate in the middle of the tree is removed, all of its descendants are implicitly
removed as well.
- Note that removing a subordinate from a list of descendants does not remove the instance
from memory. The employees still exist, but they are disconnected from the tree.
• This will leave those subordinates disconnected from the tree.
• The GetChildren method returns the immediate children of the current employee.
• The GetDescendants method makes use of a depth recursion to list all subordinates on all
levels of the current employee.

4_Client

class Client
{
static void Main(string[] args)
{
//Create root
Employee Rahul = new Employee { EmpID = 1, Name = "Rahul" };

//First level of descendants


Employee Amit = new Employee { EmpID = 2, Name = "Amit" };
Employee Mohan = new Employee { EmpID = 3, Name = "Mohan" };
[Link](Amit);
[Link](Mohan);

//Second level of descendants


Employee Rita = new Employee { EmpID = 4, Name = "Rita" };
BasicEmployee Hari = new BasicEmployee { EmpID = 5, Name = "Hari" };
[Link](Rita);
[Link](Hari);
Employee Raj = new Employee { EmpID = 7, Name = "Raj" };
BasicEmployee Tim = new BasicEmployee { EmpID = 9, Name = "Tim" };
[Link](Raj);
[Link](Tim);

//Third level of descendants


BasicEmployee Kamal = new BasicEmployee { EmpID = 6, Name = "Kamal" };
BasicEmployee Sam = new BasicEmployee { EmpID = 8, Name = "Sam" };
[Link](Kamal);
[Link](Sam);

//List all descendants of Rahul


//foreach (IEmployee e in [Link]())
// [Link]([Link]);
[Link]([Link](", ",
[Link]()
.Select(d => [Link]).ToList()
) );

//Wait for user


[Link]("\n\tPress any key to exit ... ");
[Link]();
} //Main
} //class Client

• An employee can either be either Employee or BasicEmployee. BasicEmployee does not


have a list of subordinates. Employee has a list of employees although it might be empty.
• The output of the above program is given below.

Copyright: PJ Blignaut, 2022


17

Class diagram

Figure 8. Composite pattern applied to Employees tree

4.5 Other examples

• [Link]
• [Link]
• [Link]
• [Link]
composite-design-patter/
• [Link]
• Project 5b, 2020 (Employees)
• Exam 2, 2020, Question 5
• Project 5, 2023

4.6 Pros and cons

Advantages

• You can work with complex tree structures more conveniently: use polymorphism and
recursion to your advantage.
• Open/Closed Principle. You can introduce new element types into the app without breaking
the existing code, which now works with the object tree.

Disadvantage

Copyright: PJ Blignaut, 2022


18

• It might be difficult to provide a common interface for classes whose functionality differs
too much. In certain scenarios, you’d need to over-generalize the component interface,
making it harder to comprehend.

4.7 When to use

• Use the Composite pattern when you have to implement a tree-like object structure.
• Use the pattern when you want the client code to treat both simple and complex elements
uniformly.

4.8 Further reading

• Cardoso (p 189 )
• Cooper, Chapter 16
• Freeman, et al., p 356
• Gamma, Helm, Johnson, Vlissides (1995, p 212)
• Martin & Martin (2006, Chapter 31, p 590)
• Nesteruk (2019, p 131)
• Okhravi, C. 2017.
[Link]
UAVR9eMBpc&index=14
• Shvets, p 179

5. Decorator pattern

5.1 Introduction

Decorator is a structural design pattern that lets you attach new behaviours to objects by placing
these objects inside special wrapper objects that also contain the extra behaviours.

The decorator pattern is also known as the wrapper. A wrapper is an object that can be linked
with some target object. The wrapper contains the same set of methods as the target and
delegates to it all requests it receives. However, the wrapper may alter the result by doing
something either before or after it passes the request to the target.

Copyright: PJ Blignaut, 2022


19

5.2 Conceptual class diagram

Figure 9. Decorator pattern - conceptual

1. The IComponent interface declares the common interface for both wrappers and wrapped
objects.
2. ConcreteComponent is a class of objects being wrapped, the wrappee. It defines the basic
behaviour, which can be altered by decorators.
3. The BaseDecorator class is the wrapper. It contains a field with the wrapped object. The
field’s type should be declared as the component interface so it can contain both concrete
components and decorators. The base decorator delegates all operations to the wrapped
object (see arrow "calls").
4. ConcreteDecorators define extra behaviours that can be added to components
dynamically.
5. The Client can wrap components in decorators.

5.3 Example

Consider the scenario of a 3D shape with properties for size and area. Examples of such shapes
are cubes and spheres. For cubes, size refers to the side length and for spheres it is the radius.
The area is calculated with a formula based on the size of a cube or sphere respectively.

A client needs access to a shape's volume as well. It should not be necessary for a client to
calculate the volume itself and neither should the existing classes be changed. The solution is
to wrap the respective shape classes in covering classes that calculates the volume.

Component interface

public interface IShape


{
double Size { get; }
double Area { get; }
} //IShape

Copyright: PJ Blignaut, 2022


20

Concrete components
class Cube : IShape
{
public double Size { get; }
public Cube(double size) { [Link] = size; }
public double Area { get { return 6 * Size * Size; } }
} //Cube

class Sphere : IShape


{
public double Size { get; }
public Sphere(double size) { [Link] = size; }
public double Area { get { return 4 * [Link] * Size * Size; } }
} //Sphere

Base decorators

The base decorators are abstract classes that implement the basic component interface. It has a
private instance of the basic concrete component and the implemented members delegate their
functioning to the respective members of the concrete component. The expected new
functionality is added as abstract members that must be overridden in concrete decorators.

abstract class AShapeDecorator : IShape


{
//The decorator has a basic component as basis and extends it
protected IShape shape;

//Implemented properties refer to the basic component's properties


public double Size { get { return [Link]; } }
public double Area { get { return [Link]; } }

//Constructor - assigns the basic component on which the decorator builds


public AShapeDecorator(IShape shape)
{
[Link] = shape;
} //Constructor

public abstract double Volume { get; }


} //abstract class AShapeDecorator

Concrete decorators

The concrete decorators implement the respective base decorators by specifying the details of
the extra functionality. In this case, the respective formulas for Area are applied to return the
area of the shape. The constructor passes the original object that must be decorated through to
the base decorator.

class DecoratedCube : AShapeDecorator


{
public DecoratedCube(IShape shape) : base(shape) { }
public override double Volume
{ get { return Size * Size * Size; } }
} //class CubeDecorator

class DecoratedSphere : AShapeDecorator


{
public DecoratedSphere(IShape shape) : base(shape) { }
public override double Volume
{ get { return 4 * [Link] * Size * Size * Size / 3; } }
} //class CubeDecorator

Copyright: PJ Blignaut, 2022


21

Client

In the client example below, a basic cube is instantiated and the basic members used to display
the cube's dimensions. Then, a decorated cube instantiated based on the basic cube. Now, the
client can display the volume as well. The procedure is repeated for a sphere.

class Client
{
private static IShape cube1, sphere1; //Components - original objects being wrapped
private static DecoratedCube cube2; //Concrete decorators
private static DecoratedSphere sphere2;

static void Main(string[] args)


{
cube1 = new Cube(2);
[Link]("\tCube 1 size : " + [Link]("#.##"));
[Link]("\tCube 1 area : " + [Link]("#.##"));

cube2 = new DecoratedCube(cube1);


[Link]("\n\tCube 2 size : " + [Link]("#.##"));
[Link]("\tCube 2 area : " + [Link]("#.##"));
[Link]("\tCube 2 volume : " + [Link]("#.##"));

sphere1 = new Sphere(2);


[Link]("\n\tSphere 1 size : " + [Link]("#.##"));
[Link]("\tSphere 1 area : " + [Link]("#.##"));

sphere2 = new DecoratedSphere(sphere1);


[Link]("\n\tSphere 2 size : " + [Link]("#.##"));
[Link]("\tSphere 2 area : " + [Link]("#.##"));
[Link]("\tSphere 2 volume : " + [Link]("#.##"));

//Wait for user


[Link]("\n\tPress any key to exit ...");
[Link]();
} //Main
}

Class diagram

Copyright: PJ Blignaut, 2022


22

Figure 10. Decorator pattern for the Shapes application

5.4 Other examples

• Listing DP02 Decorator (Shvets Conceptual)


[Link]
• Listing DP03 Decorator (DoFactory)
[Link]
• Listing DP04 Decorator (Chauhan)
[Link]
• Project 5, 2020, Part A (json)
• Project 5, 2022 (Lists)
• Project 5, 2023 (Files)

5.5 Pros and cons

Advantages

• Open-Closed principle: You can extend an object's behaviour without altering the original
object.
• You can extend an object’s behaviour without making a new subclass.
• You can add or remove responsibilities from an object at runtime.
• You can combine several behaviours by wrapping an object into multiple decorators.
• Single Responsibility Principle. You can divide a huge class that implements many
possible variants of behaviour into several smaller classes.

Disadvantages

• It is hard to remove a specific wrapper from the wrappers stack.


• It’s hard to implement a decorator in such a way that its behaviour does not depend on the
order in the decorators stack.
• The initial configuration code of layers might look pretty ugly.

Copyright: PJ Blignaut, 2022


23

5.6 When to use

• Use the Decorator pattern when you need to be able to assign extra behaviours to objects
at runtime without breaking the code that uses these objects.
• Use the pattern when it is awkward or not possible to extend an object’s behaviour using
inheritance.

5.7 Further reading

• Cardodo, p 261
• Cooper, Chapter 17
• Freeman, et al., Chapter 3
• Gamma, Helm, Johnson, Vlissides (1995, p 227)
• Kayal, S. 2018. Decorator Design pattern. [Link]
[Link]/UploadFile/dacca2/design-pattern-for-beginners-part-4-decorator-design-
patt/
• Nesteruk (2019, p 140)
• Okhravi, C. 2017.
[Link]
AVR9eMBpc&index=3
• Okhravi (Comparison of Decorator and Composite patterns):
[Link]
AVR9eMBpc&index=15
• Shvets, p 192

Copyright: PJ Blignaut, 2022


24

6. Façade pattern

6.1 Introduction

SRP leads to lots of classes, each doing a small thing only. There may be lots of relations and
interactions between these classes. So, we have a highly decoupled system which is a good
thing. Such a plethora of classes might make it difficult for a client class, however.

The Façade pattern provides a simplified interface to a library, a framework, or any other
complex set of classes. The client can now interact with the façade, rather than with the
individual classes. A façade might provide limited functionality in comparison to working with
the subsystem directly, since it may include only those features that clients really care about.

6.2 Conceptual class diagram

Figure 11. Conceptual class diagram – Façade pattern

1. The Facade provides convenient access to a particular part of the subsystem’s functionality.
It knows where to direct the client’s request and how to operate all the moving parts.
2. An AdditionalFacade class can be created to prevent polluting a single façade with
unrelated features that might make it yet another complex structure. Additional facades can
be used by both clients and other facades.
3. The sub-system consists of dozens of various objects. Subsystem classes aren’t aware of the
facade’s existence. They operate within the system and work with each other directly.
4. The Client uses the façade instead of calling the subsystem objects directly.

6.3 Example

Scenario

Consider a system that simulates the working of printers, copiers, fax machines and multi-
function devices that combine the working of the former three devices. In order to comply
with ISP and SRP, the system may have a multitude of interfaces and inheritances as shown
in the class diagram below.

Copyright: PJ Blignaut, 2022


25

Figure 12. Multiple classes to simulate the interaction between printers, scanners,
faxers, and multifunction devices.

This plethora of classes and interfaces makes it difficult for a client application to work with
the sub-systems. If, for example, a client class wants to print a document, then scan it and then
fax it, it will have to create instances of Printer, Scanner and Faxer and then call the devices'
respective action methods.

Document d = new Document();


IPrinter printer = new Printer();
[Link](d);
IScanner scanner = new Scanner();
[Link](d);
IFaxer faxer = new Faxer();
[Link](d);

Façade class

A Façade class, DocumentHandler, is created to interface with the individual classes in Figure
12.

class DocumentHandler
{
IPrinter printer = new Printer();
IScanner scanner = new Scanner();
IFaxer faxer = new Faxer();
Photocopier copier = new Photocopier(new Printer(), new Scanner());
IMultiFunctionDevice mfp
= new MultiFunctionDevice(new Printer(), new Scanner(), new Faxer());

public void Print(Document d) { [Link](d); }


public void Scan (Document d) { [Link](d); }
public void Fax (Document d) { [Link](d); } //Fax

public void ScanAndPrint(Document d)


{

Copyright: PJ Blignaut, 2022


26

[Link](d);
[Link](d);
} //ScanAndPrint

public void PrintScanFax(Document d)


{
[Link](d);
[Link](d);
[Link](d);
} //PrintScanFax
} //class DocumentHandler

Client

Instead of creating a Printer object and then calling its Print method, the client creates a
DocumentHandler object and calls its Print method with the Document as parameter. The
document handler will, in the background, create a Printer object and calls its Print mehod
while relaying the Document object through as parameter.

Document d = new Document();


DocumentHandler handler = new DocumentHandler();
[Link](d);
[Link](d);
[Link](d);

Class organisation in Visual Studio

For the sake of clarity during projects, I request that you put all sub-system classes in a separate
folder. The façade class, in this case DocumentHandler, goes into a Faç[Link] file.

Figure 13. Class organisation of the Façade pattern in Visual Studio

Copyright: PJ Blignaut, 2022


27

Class diagram

Figure 14. Façade pattern for a document handling application. For the sake of
clarity, only the printer part of the sub-system is shown

6.4 Other examples

• [Link]
• [Link]
• [Link]
• [Link]
• Test 2, 2021, Question 2
• Project 5, 2021
• Project 6, 2023 (Media player)

6.5 Pros and cons

Advantage

• You can isolate your code from the complexity of a subsystem.

Disadvantage

• A façade can become a god object coupled to all classes of an app.


(A god object is an object that knows too much or does too much.)

6.6 When to use

• Use the Façade pattern when you need to have a limited but straightforward interface to a
complex subsystem.
• Use the Façade when you want to structure a subsystem into layers. Use an additional façade
for every layer.

6.7 Further reading

Copyright: PJ Blignaut, 2022


28

• Cooper, Chapter 18
• Freeman et al., p 258
• Gamma, Helm, Johnson, Vlissides (1995, p 239)
• Martin & Martin (2006), Chapter 23, p 415
• Nesteruk (2019, p 155)
• Okhravi, C. 2017.
[Link]
AVR9eMBpc&index=9
• Shvets, p 211

7. Flyweight pattern

7.1 Introduction

Typically, the Flyweight pattern is used if there are a very large number of very similar objects,
and you want to minimize the amount of memory that is dedicated to storing all these values.
The pattern allows common parts of state between multiple objects to share the same memory
instead of keeping all of the data in each object.

7.2 Conceptual class diagram

Figure 15. Flyweight pattern - conceptual

1. The Flyweight class contains the portion of the original object’s state that can be shared
between multiple objects.

2. The Context class contains the extrinsic state, unique across all original objects. When a
context is paired with one of the flyweight objects, it represents the full state of the original
object.

3. The FlyweightFactory class maintains a list (or array) of existing flyweights. The
GetFlyweight method is passed a state which is tested against the existing flyweights. If the
state is unique, a new object is created and added to the list of flyweights. If the state exists
already, the existing flyweight is returned.

4. The Client calculates or stores the extrinsic state of flyweights.

7.3 Example

Copyright: PJ Blignaut, 2022


29

Consider the scenario of a company with employees that report to supervisors. Employees in
the same department have the same supervisor. Therefore, the supervisor details are repeated
for employees who share supervisors.

The figure below shows an extract of a data file with employees and supervisor details.
Repeating supervisor details are highlighted with the same colour.

Figure 16. Data file with employees and supervisors

Flyweight

The supervisor details are seen as a flyweight since they may repeat over multiple employee
records.

class Supervisor
{
public int SupNumber { get; private set; }
public string SupSurname { get; private set; }
public string SupCell { get; private set; }
public DateTime SupDate { get; private set; }

//Constructor
public Supervisor(int Number, string Surname, string Cell, DateTime Date)
{
SupNumber = Number;
SupSurname = Surname;
SupCell = Cell;
SupDate = Date;
} //Constructor

public override string ToString()


{
//Return data as a comma-delimited string
return [Link]() + "," + SupSurname + "," + SupCell + ","
+ [Link]("dd/MM/yyyy");
} //ToString
} //class Supervisor

FlyweightFactory

The SupervisorFactory class maintains a list of existing supervisor records. The


GetSupervisor method is passed a state which is tested against the existing flyweights. If the
state is unique, a new Supervisor object is created and added to the list of flyweights. The
supervisor object is returned.

class SupervisorFactory
{
public static List<Supervisor> lstSupervisors = new List<Supervisor>();

Copyright: PJ Blignaut, 2022


30

public static Supervisor GetSupervisor(int Number, string Surname,


string Cell, DateTime Date)
{
Supervisor supervisor
= [Link](sp => [Link] == Number
&& [Link] == Surname
&& [Link] == Cell
&& [Link] == Date);
if (supervisor == null)
{
supervisor = new Supervisor(Number, Surname, Cell, Date);
[Link](supervisor);
}

return supervisor;
} //GetSupervisor
} //class SupervisorFactory

Context

The context is the class that contains the actual thing that we want to save, including unique
and repeating states. The repeating state is a pointer to a flyweight object. Different pointers
can point to the same flyweight object – thus saving memory space.

class Employee //Context


{
//Unique state
public int StaffNumber { get; private set; }
public string StaffName { get; private set; }
public int StaffAge { get; private set; }
public string StaffCell { get; private set; }

//Flyweight
public Supervisor supervisor { get; private set; }

//Constructor
public Employee(int empNumber, string empName, int empAge, string empCell,
int supNumber, string supSurname, string supCell,
DateTime supDate
)
{
StaffNumber = empNumber;
StaffName = empName;
StaffAge = empAge;
StaffCell = empCell;
[Link] = [Link](supNumber, supSurname,
supCell, supDate);
} //Constructor

public override string ToString()


{
return [Link]() + "," + StaffName + "," + StaffAge + ","
+ StaffCell + "," + [Link]();
} //ToString
} //class Employee

Client

class Client
{
static void Main(string[] args)

Copyright: PJ Blignaut, 2022


31

{
//Read csv file into list
string fileName = "[Link]";
List<string> lstLines = new List<string>([Link](fileName));

//Remove header line


[Link](0);

//Add employees to list


List<Employee> lstEmployees = new List<Employee>();
foreach (string line in lstLines)
{
string[] fields = [Link](new char[] { ',' },
[Link]);
//Create employee object
Employee employee = new Employee(//Employee fields
[Link](fields[0]), fields[1],
[Link](fields[2]), fields[3],
//Supervisor fields
[Link](fields[4]), fields[5],
fields[6], [Link](fields[7])
);
//Add employee to list
[Link](employee);
} //foreach line

//Hard code selected supervisor number


int supNumber = 7451;
Supervisor sup = [Link]
.Find(sp => [Link] == supNumber);
[Link]("\tEmployees reporting to: " + [Link]);
[Link]();

//Print employees that report to this supervisor


int i = 0;
foreach (Employee employee
in [Link] (e => [Link] == supNumber))
{
[Link]("\t" + (++i).ToString().PadLeft(3) + ".\t");
[Link](employee);
}

//Wait for user


[Link]();
} //Main
} //class Client

Copyright: PJ Blignaut, 2022


32

Class diagram

Figure 17. Flyweight pattern applied to specific scenario


7.4 Other examples

• Listing FW02 Flyweight (Shvets conceptual)


[Link]
• Listing FW03 Flyweight (Shvets p 228)
• Listing FW04 Flyweight (DoFactory) (I don’t think this is correct)
[Link]
• Listing FW04b Flyweight (DoFactory) (Adapted to follow the pattern in Shvets)
[Link]
• [Link]
• Project 6, 2020

7.5 Pros and cons

Advantage

• You can save lots of RAM, assuming your program has tons of similar objects.

Disadvantages

• There is a trade-off between RAM and CPU cycles because some of the context data needs
to be recalculated each time a flyweight method is called.
• The code becomes much more complicated.
• The code is somewhat opaque. New team members might wonder why the state of an entity
was separated in such a way.

7.6 When to use

The Flyweight pattern is merely an optimization. You should make sure that the problem cannot
be solved in any other meaningful way. Use the pattern only when your program must support
a huge number of objects which barely fit into available RAM.

7.7 A challenge

Copyright: PJ Blignaut, 2022


33

Consider a desktop background with several smaller shells. All shell images are identical, but
for their rotation and position. Develop a Windows Forms application and draw the desktop
background. Don't save a separate shell image for each shell on the screen.

Copyright: PJ Blignaut, 2022


34

7.8 Further reading

• Cooper, Chapter 19, p 331


• Gamma, Helm, Johnson, Vlissides (1995, p 251)
• Nesteruk (2019, p 163)
• Shvets, p 221

8. Proxy pattern (aka Surrogate)

8.1 Introduction

Proxy is a structural design pattern to provide a substitute or placeholder for another object.
The proxy class has the same interface as an original service object. Client access to an object
is through the proxy which creates a real service object and delegates all the work to it.

The benefit is that if something needs to be done either before or after the primary logic of the
class, the proxy does this without changing the service class. Since the proxy implements the
same interface as the original class, it can be passed to any client that expects a real service
object.

Although related, the Proxy pattern is different from the Decorator in that the proxy does not
add behaviour to the original class. The pre- or post-processing is not seen as a function of the
service class.

8.2 Types of proxies

• A protection proxy controls access to an existing object. Some operation can be done either
before or after the request gets through to the original object. For example, to control access
to a bank account, the proxy can check the user rights. Rights checking is not an inherent
part of the account class and therefore we cannot use the decorator pattern.
• Property proxies are stand-in objects that can replace fields and perform additional
operations during assignment, access, or both.
• A virtual proxy provides virtual access to the underlying object, and can implement lazy
object loading. You might feel like you’re working with a real object, but the underlying
implementation might not have been created yet, and can be loaded on demand.
• A communication proxy (aka remote proxy) allows to change the physical location of the
object, for example move it to the cloud, but uses the same interface.
• A logging proxy allows logging (recording of events) in addition to calling the underlying
functions.
• A caching proxy caches results of client requests and manage the life cycle of this cache,
especially if results are quite large. Instead of repeated submission of the same request to a
server in the cloud, it can be executed locally.

Copyright: PJ Blignaut, 2022


35

8.3 Conceptual class diagram

Figure 18. Proxy pattern - conceptual

1. The ServiceInterface declares the interface of the Service. The proxy must follow this
interface to be able to disguise itself as a service object.
2. The Service is a class that provides some useful business logic.
3. The Proxy class has a reference field that points to a service object. After the proxy finishes
its processing (e.g., lazy initialization, logging, access control, caching, etc.), it passes the
request to the service object.
4. The Client should work with a proxy via the same interface as it would have worked with
a service. This way you can pass a proxy into any code that expects a service object.

The only difference with the Decorator pattern is that the Proxy does not add behaviour that is
publicly available to the client. It does its work "behind the scenes".

8.4 Protection proxy example

The service interface below is merely conceptual to show the structure. The proxy maintains a
list of registered users. In a real-life scenario, this might be read from a database or securely
encrypted file.

Service interface

The service interface encapsulates all service operations that are expected by the client. The
concrete service and proxy classes implement the same interface.

interface IService
{
void DoSomething();
}

Service

The actual work is still done in the Service class.

class Service : IService


{
public void DoSomething()
{
[Link]("\tService does something");
}
}

Copyright: PJ Blignaut, 2022


36

Proxy

The operation requested by the client is only passed through to the service object if the user is
registered.
class Proxy : IService
{
List<string> lstUsers
= new List<string>(new string[] { "John", "Mike", "Peter", "Susan" });
private IService service;
private string userName;

public Proxy(string userName)


{
service = new Service();
[Link] = userName;
}

public void DoSomething()


{
if ([Link](userName))
[Link]();
else
[Link]("\tAccess denied");
}
} //class Proxy

Client

class Client
{
static void Main(string[] args)
{
//Create proxy
IService px = new Proxy("John");

//The client interacts with the proxy as if it is a real service object


[Link]();

//Wait for user


[Link]();
}
} //class Client

8.5 Virtual proxy example

In this example, we build a basic calculator program. The example is very limited. It does, for
example, no checking for division by zero. One can argue whether that should be a function of
the proxy or the original service class.

Service interface

public interface IMath


{
double Add(double x, double y);
double Sub(double x, double y);
double Mul(double x, double y);
double Div(double x, double y);
} //IMath

Copyright: PJ Blignaut, 2022


37

Service

class Math : IMath


{
public double Add(double x, double y) { return x + y; }
public double Sub(double x, double y) { return x - y; }
public double Mul(double x, double y) { return x * y; }
public double Div(double x, double y) { return x / y; }
} //class Math

Proxy

The proxy instantiates the underlying service only if it does not exist yet. For large objects this
is beneficial as memory is not utilised unless necessary.

class MathProxy : IMath


{
private Math _math = null;

public double Add(double x, double y)


{
if (_math == null) //The service is not instantiated unless being called
_math = new Math();
return _math.Add(x, y);
} //Add

public double Sub(double x, double y) { ... }


public double Mul(double x, double y) { ... }
public double Div(double x, double y) { ... }
} //class MathProxy

Client

class Client
{
static void Main(string[] args)
{
// Create math proxy
IMath proxy = new MathProxy();

// Do the math
[Link]("4 + 2 = " + [Link](4, 2));
[Link]("4 - 2 = " + [Link](4, 2));
[Link]("4 * 2 = " + [Link](4, 2));
[Link]("4 / 2 = " + [Link](4, 2));

// Wait for user


[Link]();
} //Main
} //class

8.6 Other examples

• [Link]
• [Link] (Calculator)
• [Link]
• Exam 1, 2021, Question 4
• Project 5b, 2021

Copyright: PJ Blignaut, 2022


38

8.7 Pros and cons

Advantages

• You can control the service object without clients knowing about it.
• You can manage the lifecycle of the service object when clients don’t care about it.
• The client works even if the service object isn’t ready or is not available.
• Open/Closed Principle. You can introduce new proxies without changing the service or
clients.

Disadvantages

• The code may become more complicated since you need to introduce a lot of new classes.
• The response from the service might get delayed.

8.8 When to use

• Lazy initialization (virtual proxy). This is when you have a heavyweight service object that
wastes system resources by being always up, even though you only need it from time to time.
• Access control (protection proxy). This is when you want only specific clients to be able to
use the service object; for instance, when your objects are crucial parts of an operating
system and clients are various launched applications (including malicious ones).
• Local execution of a remote service (remote proxy). This is when the service object is located
on a remote server.
• Logging requests (logging proxy). This is when you want to keep a history of requests to the
service object.
• Caching request results (caching proxy). This is when you need to cache results of client
requests and manage the life cycle of this cache, especially if results are quite large.
• Smart reference. This is when you need to be able to dismiss a heavyweight object once
there are no clients that use it.

8.9 Further reading

• Cardoso, p 203
• Cooper, Chapter 20, p 347
• Freeman, et al., Chapter 11, p 429
• Gamma, Helm, Johnson, Vlissides (1995, p 267)
• Martin & Martin (2006, Chapter 34, p 642)
• Nesteruk (2019, p 171)
• Okhravi, C. 2017. Proxy pattern.
[Link]
UAVR9eMBpc&index=10
• Shvets, p 235

9. Summary

• The Adapter pattern is about making two interfaces that are not compatible, compatible.
• The Bridge is used to split the details of a method into an implementation class hierarchy. The
abstraction and implementation can then be developed independently of each other.
• The Composite pattern is used when we need to treat a group of objects and a single object in
the same way.
Copyright: PJ Blignaut, 2022
39

• The Decorator allows addition of new behaviours to objects by placing these objects inside
special wrapper objects that also contain the extra behaviours.
• The Façade pattern is about taking a bunch of complex interactions and creating a façade that
you can use instead of dealing with all those complex objects and complex interactions.
• The Flyweight pattern is used if there are a very large number of very similar objects to
minimize the amount of memory that is dedicated to storing all these values.
• The Proxy is placed between the client and something that you want to call. So instead of
calling a method directly, you call the proxy who calls that method.

The structures of the various structural design patterns are very similar. They all share two
classes that are related like this. Table 1 summarises the functionality of these two classes for
each of the patterns.

Table 1. Comparison between structural patterns


Pattern Class A Class B Intent
Adapter Adapter Adaptee / Service To allow two incompatible interfaces to
collaborate. For already existing service
class.
Bridge Abstraction Implementation Decouple abstraction from implementation
so that they can vary independently. For
new systems with a large class.
Composite Composite Component interface Work with composite (containing) object as if it is
a single object.
Decorator Base decorator Component interface Add new behaviour
Façade Façade Sub-system class Provides a simplified interface to a complex set of
classes
Flyweight FW factory Flyweight[] Allow a huge number of objects to fit in available
RAM.
Proxy Proxy Service Replacement for service with some pre- or post-
processing.

See also:

• Okhravi, C. 2017. Structural patterns (comparison)


[Link]
VR9eMBpc&index=12
• Shvets, p245

10. References and Bibliography

• Cardoso, A. 2019. Implementing Design Patterns in C# and .NET 5: Build Scalable, Fast,
and Reliable .NET Applications Using the Most Common Design Patterns. English Edition.
BPB Publications. Kindle Edition.
• Cooper, J.W. 2002. C# Design Patterns: A Tutorial, Addison Wesley
• Freeman, E., Freeman, E., Bates, B., Sierra, K. 2004. Head First Design Patterns. O'Reilly.
• Gamma, E., Helm, R., Johnson, R., Vlissides, J. 1994. Design Patterns: Elements of
Reusable Object-Oriented Software. Addison- Wesley.
• Martin, R.C., Martin, M. 2006. Agile Principles, Patterns, and Practices in C#. Prentice Hall.

Copyright: PJ Blignaut, 2022


40

• Nesteruk, D. 2019. Design Patterns in .NET. Apress. Kindle Edition.


• Ohkravi, C. 2017. Design patterns.
[Link]
• Shvets, A. 2020. Dive into Design Patterns. [Link]

Copyright: PJ Blignaut, 2022


41

11. Quick reference: Class diagrams of structural design patterns

Adapter pattern
Proxy pattern

Composite pattern
Decorator pattern

Copyright: PJ Blignaut, 2022


42

Façade pattern Flyweight pattern

Bridge pattern

Copyright: PJ Blignaut, 2022

You might also like