Chapter 31
Chapter 31
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.
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.
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
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.
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
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];
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);
[Link]();
} //Main
} //Client
Class diagram
Advantages
• Single Responsibility Principle. You can separate the interface or data conversion code
from the primary business logic of the program.
• 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.
• 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.
• 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
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.
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:
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.
The derived abstractions (Savings, Cheque and Credit) are referred to as refined abstractions
and the derived implementations (Withdraw, Deposit, etc.) as concrete implementations.
Abstraction
Refined abstractions
• 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
Concrete implementations
}
} //class Deposit
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));
• 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
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.
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.
Advantages
Disadvantage
• You might make the code more complicated by applying the pattern to a highly cohesive
class.
• 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.
• 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.
• 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
• The component interface provides an abstraction of both leaves and container objects.
2_Leaf
3_Composite
• 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" };
Class diagram
• [Link]
• [Link]
• [Link]
• [Link]
composite-design-patter/
• [Link]
• Project 5b, 2020 (Employees)
• Exam 2, 2020, Question 5
• Project 5, 2023
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
• 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.
• 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.
• 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.
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
Concrete components
class Cube : IShape
{
public double Size { get; }
public Cube(double size) { [Link] = size; }
public double Area { get { return 6 * Size * Size; } }
} //Cube
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.
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.
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;
Class diagram
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
• 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.
• 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
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.
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.
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.
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());
[Link](d);
[Link](d);
} //ScanAndPrint
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.
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.
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
• [Link]
• [Link]
• [Link]
• [Link]
• Test 2, 2021, Question 2
• Project 5, 2021
• Project 6, 2023 (Media player)
Advantage
Disadvantage
• 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.
• 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.
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.
7.3 Example
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.
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
FlyweightFactory
class SupervisorFactory
{
public static List<Supervisor> lstSupervisors = new List<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.
//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
Client
class Client
{
static void Main(string[] args)
{
//Read csv file into list
string fileName = "[Link]";
List<string> lstLines = new List<string>([Link](fileName));
Class diagram
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.
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
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.
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.
• 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.
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".
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
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;
Client
class Client
{
static void Main(string[] args)
{
//Create proxy
IService px = new Proxy("John");
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
Service
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.
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));
• [Link]
• [Link] (Calculator)
• [Link]
• Exam 1, 2021, Question 4
• Project 5b, 2021
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.
• 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.
• 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.
See also:
• 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.
Adapter pattern
Proxy pattern
Composite pattern
Decorator pattern
Bridge pattern