UNIT-3(OOPS)
UNIT-3(OOPS)
UNIT-3(OOPS)
Design patterns
Reusable solutions for typical software design challenges are
known as design patterns. Expert object-oriented software
engineers use these best practices to write more structured,
manageable, and scalable code. Design patterns provide a
standard terminology and are specific to particular scenarios and
problems. Design patterns are not finished code but templates or
blueprints only.
Key Characteristics of Design Patterns
Reusability: Patterns can be applied to different projects and
problems, saving time and effort in solving similar issues.
Standardization: They provide a shared language and
understanding among developers, helping in communication
and collaboration.
Efficiency: By using these popular patterns, developers can
avoid finding the solution to same recurring problems, which
leads to faster development.
Flexibility: Patterns are abstract solutions/templates that can
be adapted to fit various scenarios and requirements.
Why Learn Design Patterns?
There are multiple reasons to learn design patterns:
Code that is simpler to comprehend, update, and expand is
produced with the help of design patterns.
They offer solutions that have been tried and tested as well as
best practices.
Learning this enables them to quickly and effectively address
similar challenges in various projects.
Developers can produce reusable components that can be
utilized in a variety of applications by implementing design
patterns.
This reduces redundancy and saves development time.
Types of Software Design Patterns
There are three types of Design Patterns:
Creational Design Pattern
Structural Design Pattern
Behavioral Design Pattern
1. Creational Design Patterns
Creational Design Patterns focus on the process of object
creation or problems related to object creation. They help in
making a system independent of how its objects are created,
composed and represented.
1. Static Member:
The Singleton pattern or pattern Singleton employs a static
member within the class. This static member ensures that
memory is allocated only once, preserving the single instance of
the Singleton class.
Java
// Private constructor to
// prevent external instantiation
class Singleton {
// Making the constructor as Private
private Singleton()
{
// Initialization code here
}
}
3. Static Factory Method:
A crucial aspect of the Singleton pattern is the presence of a
static factory method. This method acts as a gateway, providing
a global point of access to the Singleton object. When someone
requests an instance, this method either creates a new instance
(if none exists) or returns the existing instance to the caller.
Java
// Library classes
abstract class Vehicle {
public abstract void printVehicle();
}
// Factory Interface
interface VehicleFactory {
Vehicle createVehicle();
}
// Client class
class Client {
private Vehicle pVehicle;
// Driver program
public class GFG {
public static void main(String[] args) {
VehicleFactory twoWheelerFactory = new
TwoWheelerFactory();
Client twoWheelerClient = new
Client(twoWheelerFactory);
Vehicle twoWheeler = twoWheelerClient.getVehicle();
twoWheeler.printVehicle();
// Client Code
public class CarFactoryClient {
public static void main(String[] args) {
// Creating cars for North America
CarFactory northAmericaFactory = new NorthAmericaCarFactory();
Car northAmericaCar = northAmericaFactory.createCar();
CarSpecification northAmericaSpec =
northAmericaFactory.createSpecification();
northAmericaCar.assemble();
northAmericaSpec.display();
@Override
public void manufacture()
{
System.out.print("Car ");
workShop1.work();
workShop2.work();
}
}
@Override
public void manufacture()
{
System.out.print("Bike ");
workShop1.work();
workShop2.work();
}
}
1. Flyweight Interface/Class:
Defines the interface through which flyweight objects can
receive and act on extrinsic state.
2. Concrete Flyweight Classes:
Implements the Flyweight interface and represents objects
that can be shared.
Stores intrinsic state (state that can be shared) and
provides methods to manipulate intrinsic state if needed.
3. Flyweight Factory:
Manages a pool of flyweight objects.
Provides methods for clients to retrieve or create flyweight
objects.
Ensures flyweight objects are shared appropriately to
maximize reusability.
4. Client:
Uses flyweight objects to perform operations.
Maintains or passes extrinsic state to flyweight objects
when needed.
Does not manage the lifecycle of flyweight objects directly
but interacts with them via the factory.
How to implement Flyweight Design Pattern?
To implement the Flyweight Design Pattern, follow these simple
steps:
Step 1: Identify Shared and Unique Data: Arrange the
data in your objects first. Determine which information is
specific to each object (known as extrinsic data) and which
can be shared across objects (known as intrinsic data).
Step 2: Create a Flyweight Class: This class will hold the
intrinsic (shared) data. All instances of this class represent
objects with similar data.
Step 3: Build a Flyweight Factory: This factory class
manages instances of the Flyweight objects. When a new
object is needed, the factory checks if an object with the same
shared data already exists. If it does, it reuses that object; if
not, it creates a new one.
Step 4: Pass Unique Data as Needed: The extrinsic data,
or data specific to that instance(extrinsic data), should be
passed as a parameter when using an object. In this manner,
the object can act in a unique way without storing all of the
data.
Step 5: Use Flyweights Instead of Creating New
Objects: Now, instead of creating new objects directly, always
request them through the factory. The factory will manage all
shared instances and reuse them where possible.
Example Implementation of Flyweight Design Pattern
Below is the problem statement to understand flyweight design
pattern:
Imagine a graphical user interface (GUI) application where
multiple icons of different types (e.g., file icons, folder icons)
need to be displayed on a screen. Each icon type has a specific
appearance and behavior, such as different images and positions
on the screen. However, displaying numerous icons of the same
type can consume significant memory if each icon object stores
its unique properties independently.
How Flyweight Design Pattern will help to solve this problem?
The Flyweight design pattern can optimize memory usage by
sharing common parts of the icons (intrinsic state), such as
the image and basic properties (like size and color), among
multiple icon instances.
Each icon instance then stores only its unique properties
(extrinsic state), such as its position on the screen.
This approach reduces the memory footprint and enhances
performance, especially when dealing with a large number of
similar objects.
Complete code of the flyweight design pattern:
import java.util.HashMap;
import java.util.Map;
// Flyweight interface
interface Icon {
void draw(int x, int y); // Method to draw the icon at given
coordinates
}
@Override
public void draw(int x, int y) {
// Simulated logic to load and draw image
System.out.println("Drawing " + type + " icon with image
" + imageName + " at position (" + x + ", " + y + ")");
}
}
And if it were some other collection like set, tree, etc. way of
iterating would change slightly. Now, what if we build an iterator
that provides a generic way of iterating over a collection
independent of its type.
Java
// Create an iterator
Iterator iterator = notificationList.createIterator();
// It wouldn’t matter if list is Array or ArrayList or
// anything else.
while (iterator.hasNext())
{
Notification notification = iterator.next());
}
Components of Iterator Design Pattern
The Iterator design pattern consists of several components:
1. Iterator Interface/Abstract Class
Defines the interface for accessing and traversing elements in
the collection. It typically includes methods like hasNext(), next(),
and optionally remove().
2. Concrete Iterator
Implements the Iterator interface and maintains the current
position in the traversal of the aggregate. It provides the actual
implementation for the traversal operations defined in the
Iterator interface.
3. Aggregate Interface/Abstract Class
Defines the interface for creating an Iterator object. It typically
includes a method like createIterator() that returns an Iterator
object for the collection.
4. Concrete Aggregate
Implements the Aggregate interface and represents the
collection of objects. It provides the implementation for creating
an Iterator object that can traverse its elements.
Iterator Design Pattern example
Problem Statement:
Let’s say we have a collection of employees in a company, and
we want to iterate over the employees to calculate their total
salary. However, the employees are stored in different types of
collections (arrays, lists, etc.), and we want to iterate over them
without exposing the underlying collection types.
import java.util.*;
// Employee class
class Employee {
private String name;
private double salary;
// Iterator interface
interface Iterator<T> {
boolean hasNext();
T next();
}
// Aggregate interface
interface Aggregate<T> {
Iterator<T> createIterator();
}
// Concrete Iterator
class EmployeeIterator implements Iterator<Employee> {
private int currentIndex = 0;
private List<Employee> employees;
@Override
public boolean hasNext() {
return currentIndex < employees.size();
}
@Override
public Employee next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return employees.get(currentIndex++);
}
}
// Concrete Aggregate
class Company implements Aggregate<Employee> {
private List<Employee> employees;
@Override
public Iterator<Employee> createIterator() {
return new EmployeeIterator(employees);
}
}
// Main class
public class Main {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 50000));
employees.add(new Employee("Bob", 60000));
employees.add(new Employee("Charlie", 70000));
double totalSalary = 0;
while (iterator.hasNext()) {
totalSalary += iterator.next().getSalary();
}
Subject:
o The subject maintains a list of observers (subscribers
or listeners).
o It Provides methods to register and unregister
observers dynamically and defines a method to notify
observers of changes in its state.
Observer:
o Observer defines an interface with an update method
that concrete observers must implement and ensures
a common or consistent way for concrete observers to
receive updates from the subject.
ConcreteSubject:
o ConcreteSubjects are specific implementations of the
subject. They hold the actual state or data that
observers want to track. When this state changes,
concrete subjects notify their observers.
o For instance, if a weather station is the subject,
specific weather stations in different locations would
be concrete subjects.
ConcreteObserver:
o Concrete Observer implements the observer interface.
They register with a concrete subject and react when
notified of a state change.
o When the subject’s state changes, the concrete
observer’s update() method is invoked, allowing it to
take appropriate actions.
o For example, a weather app on your smartphone is a
concrete observer that reacts to changes from a
weather station.
Observer Design Pattern Example
To understand observer design pattern, lets take an example:
Consider a scenario where you have a weather monitoring
system. Different parts of your application need to be updated
when the weather conditions change.
Challenges or difficulties while implementing this system
without Observer Design Pattern
Components interested in weather updates would need direct
references to the weather monitoring system, leading to tight
coupling.
Adding or removing components that react to weather
changes requires modifying the core weather monitoring
system code, making it hard to maintain.
How Observer Pattern helps to solve above challenges?
The Observer Pattern facilitates the decoupling of the weather
monitoring system from the components that are interested in
weather updates (via interfaces). Every element can sign up as
an observer, and observers are informed when the weather
conditions change. The weather monitoring system is thus
unaffected by the addition or removal of components.
import java.util.ArrayList;
import java.util.List;
// Observer Interface
interface Observer {
void update(String weather);
}
// Subject Interface
interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// ConcreteSubject Class
class WeatherStation implements Subject {
private List<Observer> observers = new ArrayList<>();
private String weather;
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(weather);
}
}
// ConcreteObserver Class
class PhoneDisplay implements Observer {
private String weather;
@Override
public void update(String weather) {
this.weather = weather;
display();
}
// ConcreteObserver Class
class TVDisplay implements Observer {
private String weather;
@Override
public void update(String weather) {
this.weather = weather;
display();
}
weatherStation.addObserver(phoneDisplay);
weatherStation.addObserver(tvDisplay);
// Output:
// Phone Display: Weather updated - Sunny
// TV Display: Weather updated - Sunny
}
}
Output
Phone Display: Weather updated - Sunny
TV Display: Weather updated - Sunny
1. Model
The Model component in the MVC (Model-View-Controller)
design pattern demonstrates the data and business logic of
an application. It is responsible for managing the
application’s data, processing business rules, and
responding to requests for information from other
components, such as the View and the Controller.
2. View
Displays the data from the Model to the user and sends
user inputs to the Controller. It is passive and does not
directly interact with the Model. Instead, it receives data
from the Model and sends user inputs to the Controller for
processing.
3. Controller
Controller acts as an intermediary between the Model and
the View. It handles user input and updates the Model
accordingly and updates the View to reflect changes in the
Model. It contains application logic, such as input validation
and data transformation.
Communication between the Components
This below communication flow ensures that each component is
responsible for a specific aspect of the application’s functionality,
leading to a more maintainable and scalable architecture
User Interaction with View: The user interacts with the
View, such as clicking a button or entering text into a form.
View Receives User Input: The View receives the user input
and forwards it to the Controller.
Controller Processes User Input: The Controller receives
the user input from the View. It interprets the input, performs
any necessary operations (such as updating the Model), and
decides how to respond.
Controller Updates Model: The Controller updates the
Model based on the user input or application logic.
Model Notifies View of Changes: If the Model changes, it
notifies the View.
View Requests Data from Model: The View requests data
from the Model to update its display.
Controller Updates View: The Controller updates the View
based on the changes in the Model or in response to user
input.
View Renders Updated UI: The View renders the updated UI
based on the changes made by the Controller.
Example of the MVC Design Pattern
Below is the code of above problem statement using MVC
Design Pattern:
Let’s break down into the component wise code:
class StudentView {
public void printStudentDetails(String studentName, String studentRollNo) {
System.out.println("Student:");
System.out.println("Name: " + studentName);
System.out.println("Roll No: " + studentRollNo);
}
}
class StudentController {
private Student model;
private StudentView view;
controller.updateView();
controller.setStudentName("Vikram Sharma");
controller.updateView();
}
Student:
Name: Lokesh Sharma
Roll No: 15UCS157
Student:
Name: Vikram Sharma
Roll No: 15UCS157