0% found this document useful (0 votes)
37 views

Structural Design Patterns

The document discusses structural design patterns in Java, focusing on how objects and classes can be composed to create larger structures. It details several commonly used patterns such as Adapter, Decorator, and Bridge, explaining their purposes, key components, and providing real-world examples. Each pattern is illustrated with code snippets to demonstrate their implementation and usage in practical scenarios.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
37 views

Structural Design Patterns

The document discusses structural design patterns in Java, focusing on how objects and classes can be composed to create larger structures. It details several commonly used patterns such as Adapter, Decorator, and Bridge, explaining their purposes, key components, and providing real-world examples. Each pattern is illustrated with code snippets to demonstrate their implementation and usage in practical scenarios.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 18

Structural Design Patterns

• deal with how objects and classes are composed to form larger structures
• simplify the design by identifying simple ways to realize relationships between entities

Most Commonly Used Structural Design Patterns in Java


• Adapter: Converts one interface to another that a client expects, allowing
incompatible interfaces to work together.
• Decorator: Dynamically adds additional behavior to an object at runtime, without
altering its structure.
• Bridge: Separates an abstraction from its implementation, so that both can change
independently.
• Facade: Provides a simplified interface to a complex subsystem of classes, making it
easier to use.
• Composite: Allows you to compose objects into tree structures to represent part-whole
hierarchies.
• Proxy: Provides a surrogate or placeholder object that controls access to another
object, often to manage resource-intensive operations.

ADAPTER DESIGN PATTERN


The Adapter Design Pattern is a structural pattern that allows incompatible interfaces
to work together. It acts as a bridge between two interfaces, converting the interface of a
class into another interface that the client expects. Essentially, the adapter translates the
requests from one interface to another, allowing objects with incompatible interfaces to
interact.

Purpose of the Adapter Pattern:


The Adapter pattern is useful when:
1. You need to integrate a new class into an existing system that is incompatible with the
existing interface.
2. You want to reuse existing code but the interface is not compatible with the system’s
interface.
3. You need to provide a common interface for multiple classes with different interfaces.

Key Components of the Adapter Pattern


1. Target Interface:
o This is the interface that the client expects to work with. It defines the
operations that the client will call.
2. Client:
o The Client is the code that interacts with the Target interface. It expects the
methods defined in the Target interface to be available.
3. Adaptee:
o The Adaptee is an existing class with an incompatible interface that needs to be
adapted to the Target interface. The Adaptee implements its own version of
methods that are different from those required by the Target interface.
4. Adapter:
o The Adapter is the class that implements the Target interface and translates
requests from the Target to the Adaptee. The adapter delegates the calls to the
Adaptee's methods and ensures that the client can use the Adaptee without
knowing the underlying differences.

Real-world Example:
Imagine a scenario where you have an old media player application that only plays .mp3 files. Now,
you want to extend it so that it can play .vlc and .mp4 files, but the classes for .vlc and .mp4 files do not
implement the same interface as the .mp3 player. To make these files playable, you can use an Adapter
pattern to make them compatible with the existing system.
// Target Interface
interface MediaPlayer {
void play(String audioType, String fileName);
}

// Adaptee: A class that can play VLC files but doesn't match the Target interface
class VLCPlayer {
public void playVLC(String fileName) {
System.out.println("Playing VLC file: " + fileName);
}
}

// Adapter: Implements MediaPlayer interface and adapts VLCPlayer


class MediaAdapter implements MediaPlayer {
private VLCPlayer vlcPlayer;

public MediaAdapter(String audioType) {


if(audioType.equalsIgnoreCase("vlc")) {
vlcPlayer = new VLCPlayer();
}
}

@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("vlc")) {
vlcPlayer.playVLC(fileName);
}
}
}

// Client: Uses MediaPlayer interface and depends on Adapter to handle different types of
media
class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;

@Override
public void play(String audioType, String fileName) {
// Play MP3 files directly
if(audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing MP3 file: " + fileName);
}
// Use adapter for other types (e.g., VLC)
else if(audioType.equalsIgnoreCase("vlc")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media type: " + audioType);
}
}
}

// Test the Adapter Pattern


public class AdapterPatternExample {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();

// Play MP3 file


audioPlayer.play("mp3", "beyond the horizon.mp3");

// Play VLC file using the Adapter


audioPlayer.play("vlc", "mind me.vlc");

// Play invalid media type


audioPlayer.play("avi", "movie.avi");
Explanation:
1. MediaPlayer (Target Interface):
o This is the common interface that the client (AudioPlayer) will use. It defines
the play() method.
2. VLCPlayer (Adaptee):
o The VLCPlayer can play .vlc files but does not implement the MediaPlayer
interface. It has its own method, playVLC(), that is not directly compatible with
the MediaPlayer interface.
3. MediaAdapter (Adapter):
o The MediaAdapter class implements the MediaPlayer interface and adapts the
VLCPlayer to the MediaPlayer interface. When the play() method is called, it
checks if the media type is "vlc" and delegates the task to the VLCPlayer.
4. AudioPlayer (Client):
o The AudioPlayer is the client that expects the MediaPlayer interface. For .mp3
files, it plays directly, and for .vlc files, it uses the MediaAdapter.

DECORATOR DESIGN PATTERN


The Decorator Design Pattern is a structural design pattern that allows you to
dynamically add behavior or responsibilities to an object without modifying its existing
structure. It is a way to extend the functionalities of an object by "wrapping" it with
additional functionality, instead of using inheritance.
The decorator pattern provides a flexible alternative to subclassing for extending
functionality. It is particularly useful when you need to add behavior to individual objects in
a system, without affecting other objects of the same class.

Key Concepts:
1. Component:
o This is the common interface that defines the behavior that can be extended by
the concrete objects and decorators. It can be an abstract class or an interface.
2. ConcreteComponent:
o This is the class that implements the Component interface. It is the object to
which we will add new functionality.
3. Decorator:
o The Decorator class implements the Component interface and contains a
reference to a Component object. The decorator delegates calls to the wrapped
object, and it can also add new functionality either before or after calling the
wrapped object's methods.
4. ConcreteDecorator:
o The ConcreteDecorator class extends the Decorator class and adds specific
behavior to the object, extending its functionality.

Purpose of the Decorator Pattern:


The Decorator Pattern is used when:
1. You want to add responsibilities to an object dynamically.
2. You want to avoid subclassing for adding new functionality.
3. You need to extend the functionality of classes in a flexible and reusable way.
When to Use the Decorator Pattern:
 When you have an existing class and want to add new behavior without changing the
class itself.
 When you need to add responsibilities to individual objects without affecting others.
 When inheritance is not flexible enough to add or remove behaviors.
 When you want to avoid a large number of subclasses that result from multiple
combinations of behaviors.
Example:
Consider a simple Coffee example, where you can add different types of toppings (like milk, sugar,
etc.) to a basic coffee. Each topping adds functionality, but instead of creating many subclasses, we can use
decorators to extend the functionality dynamically.
// Component Interface
interface Coffee {
double cost();
String ingredients();
}

// ConcreteComponent
class SimpleCoffee implements Coffee {
@Override
public double cost() {
return 5.0; // Base cost of a simple coffee
}

@Override
public String ingredients() {
return "Coffee";
}
}

// Decorator Class (Abstract)


abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;

public CoffeeDecorator(Coffee coffee) {


this.decoratedCoffee = coffee;
}

public double cost() {


return decoratedCoffee.cost();
}

public String ingredients() {


return decoratedCoffee.ingredients();
}
}

// ConcreteDecorator 1 - Milk
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}

@Override
public double cost() {
return decoratedCoffee.cost() + 1.0; // Adds cost for milk
}

@Override
public String ingredients() {
return decoratedCoffee.ingredients() + ", Milk";
}
}

// ConcreteDecorator 2 - Sugar
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}

@Override
public double cost() {
return decoratedCoffee.cost() + 0.5; // Adds cost for sugar
}

@Override
public String ingredients() {
return decoratedCoffee.ingredients() + ", Sugar";
}
}

// Client
public class DecoratorPatternExample {
public static void main(String[] args) {
Coffee simpleCoffee = new SimpleCoffee();
System.out.println("Simple Coffee: " + simpleCoffee.ingredients() + " | Cost: $" + simpleCoffee.cost());

Coffee milkCoffee = new MilkDecorator(simpleCoffee); // Add milk to the coffee


System.out.println("Milk Coffee: " + milkCoffee.ingredients() + " | Cost: $" + milkCoffee.cost());

Coffee milkSugarCoffee = new SugarDecorator(milkCoffee); // Add sugar to the milk coffee


System.out.println("Milk and Sugar Coffee: " + milkSugarCoffee.ingredients() + " | Cost: $" + milkSugarCoffee.cost());
}

Explanation of the Code:


1. Component Interface (Coffee):
o This defines the common interface that both the SimpleCoffee and decorators
will implement. It contains methods cost() and ingredients(), which are the core
functionalities that we want to extend.
2. ConcreteComponent (SimpleCoffee):
o This is the basic implementation of the Coffee interface. It represents a simple
coffee with a base cost and ingredients.
3. Decorator (CoffeeDecorator):
o This abstract class implements the Coffee interface and holds a reference to a
Coffee object. It delegates calls to the wrapped object, allowing for extended
behavior in subclasses.
4. ConcreteDecorators (MilkDecorator and SugarDecorator):
o These classes extend CoffeeDecorator and add extra behavior to the cost() and
ingredients() methods. The milk decorator adds the cost of milk and includes
"Milk" in the ingredients, while the sugar decorator adds the cost of sugar and
includes "Sugar" in the ingredients.
5. Client (DecoratorPatternExample):
o The client code demonstrates how the decorators are applied to the base object
(SimpleCoffee). It starts with a basic coffee, then adds milk and sugar using
decorators.

BRIDGE DESIGN PATTERN


The Bridge Design Pattern is a structural design pattern that allows you to separate
abstraction from implementation. By doing this, it helps to decouple the interface
(abstraction) from the actual implementation, allowing both to evolve independently. This
pattern is particularly useful when both the class definition and the behavior can change
frequently.
In simpler terms, the Bridge Pattern is used to "bridge" the gap between abstraction and its
implementation, so that changes in one do not affect the other.

When to Use the Bridge Pattern:


1. When you need to decouple an abstraction from its implementation so that both can
vary independently.
2. When you have multiple variants of classes that can be extended in different ways, and
you need to ensure that their functionality is independent.
3. When you need to provide a way to extend functionality without modifying existing
code.

Key Components of the Bridge Pattern:


1. Abstraction:
o This is the high-level interface or abstract class that defines the operations that
can be implemented. It usually holds a reference to the Implementor and
delegates the task to it.
2. RefinedAbstraction:
o This is a concrete class that extends the Abstraction class and provides specific
behavior or additional functionality.
3. Implementor:
o The Implementor defines the interface for the implementation classes. It is a
base interface for all the concrete implementations.
4. ConcreteImplementor:
o These are the classes that implement the Implementor interface. They contain
the actual behavior that is executed when an operation is called.
Example Scenario:
Suppose you have a system where different shapes (e.g., circles, squares) need to be drawn
in different colors (e.g., red, green). Instead of creating many subclasses for every
combination of shape and color, you can use the Bridge pattern to decouple the shape from
the color, allowing them to evolve independently.

// Implementor: The interface for drawing shapes


interface DrawAPI {
void drawCircle(int radius, int x, int y);
void drawSquare(int side, int x, int y);
}

// ConcreteImplementor 1: Red color drawing implementation


class RedDrawAPI implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Red Circle at (" + x + ", " + y + ") with radius " + radius);
}

@Override
public void drawSquare(int side, int x, int y) {
System.out.println("Drawing Red Square at (" + x + ", " + y + ") with side " + side);
}
}

// ConcreteImplementor 2: Green color drawing implementation


class GreenDrawAPI implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Green Circle at (" + x + ", " + y + ") with radius " + radius);
}

@Override
public void drawSquare(int side, int x, int y) {
System.out.println("Drawing Green Square at (" + x + ", " + y + ") with side " + side);
}
}

// Abstraction: Abstract class Shape that uses a DrawAPI for drawing


abstract class Shape {
protected DrawAPI drawAPI;

// Constructor initializes the implementation (DrawAPI)


protected Shape(DrawAPI drawAPI) {
this.drawAPI = drawAPI;
}

// Abstract method to draw the shape


public abstract void draw();
}

// RefinedAbstraction: Circle class extending Shape


class Circle extends Shape {
private int x, y, radius;

public Circle(int x, int y, int radius, DrawAPI drawAPI) {


super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}

@Override
public void draw() {
drawAPI.drawCircle(radius, x, y); // Delegates drawing task to DrawAPI
}
}
// RefinedAbstraction: Square class extending Shape
class Square extends Shape {
private int x, y, side;

public Square(int x, int y, int side, DrawAPI drawAPI) {


super(drawAPI);
this.x = x;
this.y = y;
this.side = side;
}

@Override
public void draw() {
drawAPI.drawSquare(side, x, y); // Delegates drawing task to DrawAPI
}
}

// Client Code: Using the Bridge Pattern to draw shapes in different colors
public class BridgePatternExample {
public static void main(String[] args) {
// Creating shapes with different colors (Red and Green)
Shape redCircle = new Circle(100, 100, 10, new RedDrawAPI());
Shape greenCircle = new Circle(200, 200, 20, new GreenDrawAPI());
Shape redSquare = new Square(300, 300, 15, new RedDrawAPI());
Shape greenSquare = new Square(400, 400, 25, new GreenDrawAPI());

// Drawing shapes
System.out.println("Drawing Shapes with Bridge Pattern:");
redCircle.draw(); // Red Circle
greenCircle.draw(); // Green Circle
redSquare.draw(); // Red Square
greenSquare.draw(); // Green Square
}
}

Explanation of the Code:


 DrawAPI (Implementor Interface): This interface defines a drawCircle() method which
is used by different concrete implementors (e.g., RedCircle, GreenCircle) to provide
the specific implementation of drawing a circle in a specific color.
 RedCircle and GreenCircle (Concrete Implementors): These classes provide concrete
implementations of the drawCircle() method, each drawing the circle in a different
color.
 Shape (Abstraction): This is the abstract class that holds a reference to the DrawAPI
(the implementor). The Shape class uses this reference to call the appropriate
implementation.
 Circle (Refined Abstraction): This is a concrete class that extends the Shape class. It
defines the specific circle attributes (position and radius) and delegates the task of
drawing to the DrawAPI.
 Main class: The main class demonstrates how the Bridge pattern allows for flexibility
in drawing shapes in different colors.

FACADE DESIGN PATTERN


The Facade Design Pattern is a structural design pattern that provides a simplified
interface to a complex subsystem or a set of interfaces. It defines a higher-level interface
that makes the subsystem easier to use and hides its complexities. Essentially, it acts as a
"front face" to the system, enabling the client to interact with it in a simple and unified
manner without needing to understand its internal workings.

Purpose of the Facade Pattern:

 To provide a simpler interface for a complex system or library.

To decouple a client from the subsystem, which makes it easier to maintain and extend
the system.

 To reduce the number of objects that a client needs to interact with.

The Facade Pattern is particularly useful when you have a complex system or a group of
classes that require client interaction, and you want to reduce the number of objects the
client needs to know about or interact with.

Key Components of the Facade Pattern:

1. Facade:

This is the main class that provides a simplified interface to the client. It
typically delegates calls to the appropriate classes in the subsystem.
o

2. Subsystem Classes:

These are the classes that provide the actual functionality in the system. They
are more complex and usually provide a set of methods or functionalities, but
o

the client doesn't interact with them directly.

When to Use the Facade Pattern:

1. When you want to provide a simple interface to a complex system or library.

2. When you want to decouple the client from a subsystem, making the system easier to
understand and maintain.

3. When you need to provide a higher-level interface that makes interacting with a set of
complex classes simpler and more intuitive.

4. When you have a system that consists of a large number of interdependent classes and
want to make it easier to use.

Example Scenario:

Consider a home theater system where we have multiple subsystems like a projector, lights,
sound system, and DVD player. Instead of having the client interact with all these individual
components, we can create a Facade to simplify the process and provide a single-entry point
to control the entire system.
// Subsystem classes

class Projector {
public void on() {
System.out.println("Projector is on");
}

public void off() {


System.out.println("Projector is off");
}
}

class Lights {
public void dim() {
System.out.println("Lights are dimming");
}

public void on() {


System.out.println("Lights are on");
}
}

class SoundSystem {
public void on() {
System.out.println("Sound system is on");
}

public void off() {


System.out.println("Sound system is off");
}

public void setVolume(int level) {


System.out.println("Sound system volume set to " + level);
}
}

class DVDPlayer {
public void on() {
System.out.println("DVD player is on");
}

public void off() {


System.out.println("DVD player is off");
}

public void play() {


System.out.println("DVD is playing");
}
}
// Facade class that simplifies the interaction with subsystems
class HomeTheaterFacade {
private Projector projector;
private Lights lights;
private SoundSystem soundSystem;
private DVDPlayer dvdPlayer;

public HomeTheaterFacade(Projector projector, Lights lights, SoundSystem soundSystem,


DVDPlayer dvdPlayer) {
this.projector = projector;
this.lights = lights;
this.soundSystem = soundSystem;
this.dvdPlayer = dvdPlayer;
}

public void watchMovie() {


System.out.println("Get ready to watch a movie...");
lights.dim();
projector.on();
soundSystem.on();
soundSystem.setVolume(10);
dvdPlayer.on();
dvdPlayer.play();
}

public void endMovie() {


System.out.println("Movie has ended.");
lights.on();
projector.off();
soundSystem.off();
dvdPlayer.off();
}
}

// Client code
public class Client {
public static void main(String[] args) {
Projector projector = new Projector();
Lights lights = new Lights();
SoundSystem soundSystem = new SoundSystem();
DVDPlayer dvdPlayer = new DVDPlayer();

// Create the facade


HomeTheaterFacade homeTheater = new HomeTheaterFacade(projector, lights,
soundSystem, dvdPlayer);

// Watch a movie
homeTheater.watchMovie();

// End the movie


homeTheater.endMovie();
}
}
Explanation:
1. Subsystem Classes:
o Projector, Lights, SoundSystem, and DVDPlayer represent the different
components in the home theater system. Each class has methods to control
various aspects of the system (e.g., turning on/off, adjusting volume, etc.).
2. Facade Class (HomeTheaterFacade):
o This class acts as the facade, providing simple methods like watchMovie() and
endMovie() to control the entire system. The client interacts only with the
HomeTheaterFacade, which in turn delegates the work to the appropriate
subsystem classes.
3. Client Code (Client class):
o The client creates the subsystems (Projector, Lights, SoundSystem, and
DVDPlayer) and passes them to the HomeTheaterFacade.
o The client then interacts with the HomeTheaterFacade to watch or end a movie,
without needing to worry about the internal workings of each subsystem.

COMPOSITE PATTERN DESIGN


The Composite Pattern is a structural design pattern used to represent objects that
are part of a tree-like structure, where individual objects and compositions of objects are
treated uniformly. This pattern allows you to treat both individual objects (leaf nodes) and
compositions of objects (composite nodes) in the same way.
It’s useful when you want to work with a tree structure where each node can either be a
simple object or a collection of objects (i.e., it may have child elements). The key idea is to
allow the client to interact with individual objects and groups of objects in a uniform way,
without needing to distinguish between them.

Main Components of the Composite Pattern:


1. Component: This is the common interface for all objects in the composition, both
individual objects and composites. It declares methods for performing actions or
retrieving data (like add(), remove(), getChild()).
2. Leaf: This represents the individual objects in the composition. They implement the
Component interface and do not have any children.
3. Composite: This represents a collection of objects that also implements the
Component interface. It can have one or more child components, which can either be
other composites or leaf objects.

When to Use the Composite Pattern:


 When you have a hierarchical tree structure (like a file system, organization structure,
or graphic objects) and need to represent individual objects and their compositions
uniformly.
When you want to treat individual objects and compositions of objects in the same
way.

Example of the Composite Pattern:


The example simulates a simple graphic drawing system where individual shapes (like
circles and rectangles) and composite groups of shapes (like a drawing group) can be
treated uniformly.
Java Code Example:

import java.util.ArrayList;
import java.util.List;

// Component interface: Defines a common interface for both leaf and composite objects
interface Graphic {
void draw(); // Common method to draw the graphic
}

// Leaf class: Represents individual objects (simple shapes like Circle and Rectangle)
class Circle implements Graphic {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}

class Rectangle implements Graphic {


@Override
public void draw() {
System.out.println("Drawing a Rectangle");
}
}

// Composite class: Represents groups of objects (can contain other individual shapes or composites)
class Drawing implements Graphic {
private List<Graphic> graphics = new ArrayList<>();

@Override
public void draw() {
System.out.println("Drawing the complete group of shapes:");
for (Graphic graphic : graphics) {
graphic.draw(); // Delegate the drawing to each component (leaf or composite)
}
}

// Method to add a graphic (either individual or composite)


public void add(Graphic graphic) {
graphics.add(graphic);
}

// Method to remove a graphic


public void remove(Graphic graphic) {
graphics.remove(graphic);
}
}

// Client code to test the Composite Design Pattern


public class CompositePatternExample {
public static void main(String[] args) {
// Create individual shapes (Leaf objects)
Graphic circle1 = new Circle();
Graphic rectangle1 = new Rectangle();

// Create a composite group (Composite object)


Drawing drawing1 = new Drawing();
drawing1.add(circle1);
drawing1.add(rectangle1);

// Create another composite group with different shapes


Drawing drawing2 = new Drawing();
drawing2.add(new Circle());
drawing2.add(new Rectangle());

// Create a final composite group that contains other groups


Drawing finalDrawing = new Drawing();
finalDrawing.add(drawing1); // Adding first composite group
finalDrawing.add(drawing2); // Adding second composite group

// Draw the final group, which will draw all the components (individual shapes and groups)
finalDrawing.draw();
}
}
Explanation:
1. Component Interface (Graphic): This interface defines a draw() method, which will be
implemented by both individual shapes (Circle, Rectangle) and composite objects
(Drawing).
2. Leaf Classes (Circle, Rectangle): These are the leaf components that implement the
Graphic interface. They each have their own draw() method that outputs a specific
shape.
3. Composite Class (Drawing): This class can contain either individual shapes or other
composite groups. It also implements the Graphic interface and uses a List<Graphic>
to store its child components. The draw() method delegates the drawing to each
contained component (whether it's a leaf or another composite).
4. Client Code (CompositePatternExample): In this class, individual shapes are created
and grouped into composite objects. The final composite object (finalDrawing) is made
up of other composite objects, which in turn contain individual shapes. When draw() is
called on the finalDrawing, it recursively draws all components.

PROXY PATTERN DESIGN


The Proxy Design Pattern is a structural design pattern that provides an object
representing another object. It acts as an intermediary or placeholder to control access to
the real object. The proxy can perform additional actions such as lazy initialization, access
control, logging, or caching before delegating the request to the real object.

Components of the Proxy Design Pattern:


1. Subject (Interface):
o This is the common interface that both the real object and the proxy implement.
It defines the operations that can be performed.
2. RealSubject:
o This is the real object that performs the actual work. It implements the Subject
interface and contains the core business logic.
3. Proxy:
o This object represents the real object and controls access to it. It implements
the same interface as the RealSubject and can perform additional tasks (e.g.,
lazy initialization, access control, logging) before or after delegating the work to
the RealSubject.
Example:
Let’s demonstrate the Proxy Design Pattern in Java with an example of a Virtual Proxy that
controls the access to a RealSubject object by delaying its creation until it is needed (lazy
initialization).
Java Code Example

// 1. Subject Interface
interface Subject {
void request();
}

// 2. RealSubject: The real object that performs the actual work


class RealSubject implements Subject {
public RealSubject() {
// Simulate expensive initialization
System.out.println("RealSubject: Creating the real subject...");
}

@Override
public void request() {
System.out.println("RealSubject: Handling request...");
}
}

// 3. Proxy: Controls access to the RealSubject


class Proxy implements Subject {
private RealSubject realSubject;

@Override
public void request() {
// Lazy initialization: create the real subject only when needed
if (realSubject == null) {
System.out.println("Proxy: Initializing real subject...");
realSubject = new RealSubject(); // Delayed creation
}
System.out.println("Proxy: Delegating request to real subject...");
realSubject.request(); // Delegate the request to the real object
}
}

// Client Code
public class ProxyPatternExample {
public static void main(String[] args) {
// Client only interacts with the Proxy, not the RealSubject
Subject proxy = new Proxy();

// The real subject is created only when its request() method is called
System.out.println("Client: Making a request to the proxy...");
proxy.request();

// The real subject won't be created again since it's already initialized
System.out.println("Client: Making another request to the proxy...");
proxy.request();
}
}
Explanation:
1. Subject Interface:
o This interface (Subject) defines a request() method, which will be implemented
by both the RealSubject and Proxy classes.
2. RealSubject Class:
o This class implements the Subject interface and provides the core functionality
of the request() method. It simulates the creation of a real subject with an
expensive operation in the constructor (RealSubject: Creating the real
subject...).
3. Proxy Class:
o The Proxy also implements the Subject interface and controls access to the
RealSubject. It performs lazy initialization (the RealSubject is created only when
the request() method is first called). When the request() method is called, the
proxy delegates the task to the RealSubject.
4. Client Code:
o The client interacts with the Proxy class. It calls the request() method on the
proxy, which internally decides when to create the RealSubject and delegates
the request.

How It Works:
1. When the client calls request() on the Proxy, the proxy checks if the RealSubject has
been created.
2. If the RealSubject is not created yet, the proxy will initialize it (lazy initialization) and
then delegate the request() to it.
3. After the RealSubject has been created, subsequent requests are handled directly by
the proxy, and no further initialization of the RealSubject is required.

Advantages of Proxy Design Pattern:


1. Lazy Initialization: The real object is only created when it is needed, saving resources
if the object is expensive to create.
2. Access Control: The proxy can perform actions such as logging, authentication, or
access control before delegating to the real object.
3. Separation of Concerns: The proxy separates concerns like caching, logging, or access
control from the core business logic of the real object.

Use Cases for Proxy Pattern:


 Virtual Proxy: Delaying the creation of resource-intensive objects.
 Remote Proxy: Representing objects in different address spaces, such as remote
objects in distributed systems.
 Protective Proxy: Providing access control, such as ensuring that only authorized
users can access certain operations.
 Cache Proxy: Caching the results of expensive operations to improve performance by
avoiding redundant computation.

You might also like