SSD Lab Manual 3 2024
SSD Lab Manual 3 2024
Objective:
Introduction:
Design patterns describe the relations and interactions of different class or objects or types. They do not specify the
final class or types that will be used in any software code, but give an abstract view of the solution. Patterns show us
how to build systems with good object-oriented design qualities by reusing successful designs and architectures.
Expressing proven techniques speed up the development process and make the design patterns, more accessible to
developers of new system.
Good object-oriented software design requires considering issues that may not become visible until later in the
implementation. Reusing design patterns helps to prevent subtle issues that can cause major problems, and it also
improves code readability for coders and architects who are familiar with the patterns and we can also avoid alternatives
that compromise reusability.
Design patterns are reusable solutions to common problems that developers encounter while designing software. They
provide a structured approach to solving design issues and help improve the quality, maintainability, and scalability of
software systems. Design patterns are categorized into three main categories: Creational, Structural, and Behavioral
patterns.
Creational Patterns: Creational patterns focus on the process of object creation. They provide ways to create objects
while hiding the creation logic, making the system more flexible and decoupled from the specific classes it creates.
Some examples of creational patterns include:
Singleton Pattern: Ensures a class has only one instance and provides a global point of access to that instance.
Factory Method Pattern: Defines an interface for creating objects, but let’s subclasses decide which class to
instantiate.
Abstract Factory Pattern: Provides an interface for creating families of related or dependent objects without
specifying their concrete classes.
Builder Pattern: Separates the construction of a complex object from its representation, allowing the same
construction process to create different representations.
Prototype Pattern: Creates new objects by copying an existing instance, often improving performance and
memory usage.
Structural Patterns: Structural patterns deal with the composition of classes and objects to form larger structures.
They focus on how objects are connected, ensuring flexibility and efficient communication between components.
Some examples of structural patterns include:
Adapter Pattern: Converts the interface of a class into another interface clients expect.
Bridge Pattern: Separates abstraction from implementation, allowing them to vary independently.
Composite Pattern: Composes objects into tree structures to represent part-whole hierarchies.
Decorator Pattern: Dynamically adds responsibilities to objects without altering their code.
Facade Pattern: Provides a simplified interface to a set of interfaces in a subsystem.
Proxy Pattern: Provides a surrogate or placeholder for another object to control access.
Behavioral Patterns: Behavioral patterns focus on how objects interact and communicate with each other. They
emphasize the communication patterns between objects, making the system more flexible and efficient.
Some examples of behavioral patterns include:
Chain of Responsibility Pattern: Passes a request along a chain of handlers until it is handled.
Command Pattern: Turns a request into a stand-alone object, allowing parameterization and queuing of
requests.
Interpreter Pattern: Provides a way to evaluate language grammar or expressions.
Iterator Pattern: Provides a way to access elements of a collection sequentially without exposing its underlying
representation.
Mediator Pattern: Defines an object that controls how a set of objects interact, reducing direct connections
between them.
Observer Pattern: Defines a dependency between objects so that when one object changes state, its dependents
are notified and updated.
These basic categories of design patterns provide developers with a toolbox of solutions for common design challenges.
By using these patterns appropriately, developers can create more maintainable, flexible, and modular software
systems.
Factory Method:
Intent
Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows
subclasses to alter the type of objects that will be created.
Problem
Imagine that you’re creating a logistics management application. The first version of your app can only handle
transportation by trucks, so the bulk of your code lives inside the Truck class.
After a while, your app becomes pretty popular. Each day you receive dozens of requests from sea transportation
companies to incorporate sea logistics into the app.
Adding a new class to the program isn’t that simple if the rest of the code is already coupled to existing classes.
Great news, right? But how about the code? At present, most of your code is coupled to the Truck class.
Adding Ships into the app would require making changes to the entire codebase. Moreover, if later you decide to add
another type of transportation to the app, you will probably need to make all of these changes again.
As a result, you will end up with pretty nasty code, riddled with conditionals that switch the app’s behavior depending
on the class of transportation objects.
Solution
The Factory Method pattern suggests that you replace direct object construction calls (using the new operator) with calls
to a special factory method. Don’t worry: the objects are still created via the new operator, but it’s being called from
within the factory method. Objects returned by a factory method are often referred to as products.
Subclasses can alter the class of objects being returned by the factory method.
At first glance, this change may look pointless: we just moved the constructor call from one part of the program to
another. However, consider this: now you can override the factory method in a subclass and change the class of products
being created by the method.
There’s a slight limitation though: subclasses may return different types of products only if these products have a
common base class or interface. Also, the factory method in the base class should have its return type declared as this
interface.
Structure
Pseudocode
This example illustrates how the Factory Method can be used for creating cross-platform UI elements without coupling
the client code to concrete UI classes.
The cross-platform dialog example.
The base Dialog class uses different UI elements to render its window. Under various operating systems, these elements
may look a little bit different, but they should still behave consistently. A button in Windows is still a button in Linux.
When the factory method comes into play, you don’t need to rewrite the logic of the Dialog class for each operating
system. If we declare a factory method that produces buttons inside the base Dialog class, we can later create a subclass
that returns Windows-styled buttons from the factory method. The subclass then inherits most of the code from the base
class, but, thanks to the factory method, can render Windows-looking buttons on the screen.
For this pattern to work, the base Dialog class must work with abstract buttons: a base class or an interface that all
concrete buttons follow. This way the code within Dialog remains functional, whichever type of buttons it works with.
Of course, you can apply this approach to other UI elements as well. However, with each new factory method you add to
the Dialog, you get closer to the Abstract Factory pattern. Fear not, we’ll talk about this pattern later.
class Application is
field dialog: Dialog
Structural Pattern:
Structural design patterns explain how to assemble objects and classes into larger structures, while keeping these
structures flexible and efficient.
Bridge Method:
Intent
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.
Problem
Abstraction? Implementation? Sound scary? Stay calm and let’s consider a simple example.
Say you have a geometric Shape class with a pair of subclasses: Circle and Square. You want to extend this class
hierarchy to incorporate colors, so you plan to create Red and Blue shape subclasses. However, since you already have
two subclasses, you’ll need to create four class combinations such as BlueCircle and RedSquare.
Number of class combinations grows in geometric progression.
Adding new shape types and colors to the hierarchy will grow it exponentially. For example, to add a triangle shape
you’d need to introduce two subclasses, one for each color. And after that, adding a new color would require creating
three subclasses, one for each shape type. The further we go, the worse it becomes.
Solution
This problem occurs because we’re trying to extend the shape classes in two independent dimensions: by form and by
color. That’s a very common issue with class inheritance.
The Bridge pattern attempts to solve this problem by switching from inheritance to the object composition. What this
means is that you extract one of the dimensions into a separate class hierarchy, so that the original classes will reference
an object of the new hierarchy, instead of having all of its state and behaviors within one class.
You can prevent the explosion of a class hierarchy by transforming it into several related hierarchies.
Following this approach, we can extract the color-related code into its own class with two subclasses: Red and Blue.
The Shape class then gets a reference field pointing to one of the color objects. Now the shape can delegate any color-
related work to the linked color object. That reference will act as a bridge between the Shape and Color classes. From
now on, adding new colors won’t require changing the shape hierarchy, and vice versa.
Note that we’re not talking about interfaces or abstract classes from your programming language. These aren’t the same
things.
When talking about real applications, the abstraction can be represented by a graphical user interface (GUI), and the
implementation could be the underlying operating system code (API) which the GUI layer calls in response to user
interactions.
Generally speaking, you can extend such an app in two independent directions:
Have several different GUIs (for instance, tailored for regular customers or admins).
Support several different APIs (for example, to be able to launch the app under Windows, Linux, and macOS).
In a worst-case scenario, this app might look like a giant spaghetti bowl, where hundreds of conditionals connect
different types of GUI with various APIs all over the code.
Making even a simple change to a monolithic codebase is pretty hard because you must understand the entire thing very
well. Making changes to smaller, well-defined modules is much easier.
You can bring order to this chaos by extracting the code related to specific interface-platform combinations into separate
classes. However, soon you’ll discover that there are lots of these classes. The class hierarchy will grow exponentially
because adding a new GUI or supporting a different API would require creating more and more classes.
Let’s try to solve this issue with the Bridge pattern. It suggests that we divide the classes into two hierarchies:
Structure
Pseudocode
This example illustrates how the Bridge pattern can help divide the monolithic code of an app that manages devices and
their remote controls. The Device classes act as the implementation, whereas the Remotes act as the abstraction.
The original class hierarchy is divided into two parts: devices and remote controls.
The base remote control class declares a reference field that links it with a device object. All remotes work with the
devices via the general device interface, which lets the same remote support multiple device types.
You can develop the remote-control classes independently from the device classes. All that’s needed is to create a new
remote subclass. For example, a basic remote control might only have two buttons, but you could extend it with
additional features, such as an extra battery or a touchscreen.
The client code links the desired type of remote control with a specific device object via the remote’s constructor.
Behavioral design patterns are concerned with algorithms and the assignment of responsibilities between objects.
Observer:
Intent
Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about
any events that happen to the object they’re observing.
Problem
Imagine that you have two types of objects: a Customer and a Store. The customer is very interested in a particular brand
of product (say, it’s a new model of the iPhone) which should become available in the store very soon.
The customer could visit the store every day and check product availability. But while the product is still en route, most
of these trips would be pointless.
It looks like we’ve got a conflict. Either the customer wastes time checking product availability or the store wastes
resources notifying the wrong customers.
Solution
The object that has some interesting state is often called subject, but since it’s also going to notify other objects about the
changes to its state, we’ll call it publisher. All other objects that want to track changes to the publisher’s state are
called subscribers.
The Observer pattern suggests that you add a subscription mechanism to the publisher class so individual objects can
subscribe to or unsubscribe from a stream of events coming from that publisher. Fear not! Everything isn’t as
complicated as it sounds. In reality, this mechanism consists of 1) an array field for storing a list of references to
subscriber objects and 2) several public methods which allow adding subscribers to and removing them from that list.
Real apps might have dozens of different subscriber classes that are interested in tracking events of the same publisher
class. You wouldn’t want to couple the publisher to all of those classes. Besides, you might not even know about some
of them beforehand if your publisher class is supposed to be used by other people.
That’s why it’s crucial that all subscribers implement the same interface and that the publisher communicates with them
only via that interface. This interface should declare the notification method along with a set of parameters that the
publisher can use to pass some contextual data along with the notification.
Publisher notifies subscribers by calling the specific notification method on their objects.
If your app has several different types of publishers and you want to make your subscribers compatible with all of them,
you can go even further and make all publishers follow the same interface. This interface would only need to describe a
few subscription methods. The interface would allow subscribers to observe publishers’ states without coupling to their
concrete classes.
Real-World Analogy
The publisher maintains a list of subscribers and knows which magazines they’re interested in. Subscribers can leave the
list at any time when they wish to stop the publisher sending new magazine issues to them.
Structure
Pseudocode
In this example, the Observer pattern lets the text editor object notify other service objects about changes in its state.
Notifying objects about events that happen to other objects.
The list of subscribers is compiled dynamically: objects can start or stop listening to notifications at runtime, depending
on the desired behavior of your app.
In this implementation, the editor class doesn’t maintain the subscription list by itself. It delegates this job to the special
helper object devoted to just that. You could upgrade that object to serve as a centralized event dispatcher, letting any
object act as a publisher.
Adding new subscribers to the program doesn’t require changes to existing publisher classes, as long as they work with
all subscribers through the same interface.
constructor Editor() is
events = new EventManager()
method saveFile() is
file.write()
events.notify("save", file.name)
// ...
method update(filename) is
log.write(replace('%s',filename,message))
method update(filename) is
system.email(email, replace('%s',filename,message))
Task 1:
Write the code for the example shown below using factory method.
Task 2:
Write down the code for the given bridge method to draw different color circle.
Task 3:
Write down the code for the given observer method.