Structural Design Patterns
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
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);
}
}
@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);
}
}
}
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.
// ConcreteComponent
class SimpleCoffee implements Coffee {
@Override
public double cost() {
return 5.0; // Base cost of a simple coffee
}
@Override
public String ingredients() {
return "Coffee";
}
}
// 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());
@Override
public void drawSquare(int side, int x, int y) {
System.out.println("Drawing Red Square at (" + x + ", " + y + ") with side " + side);
}
}
@Override
public void drawSquare(int side, int x, int y) {
System.out.println("Drawing Green Square at (" + x + ", " + y + ") with side " + side);
}
}
@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;
@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
}
}
To decouple a client from the subsystem, which makes it easier to maintain and extend
the system.
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.
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
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");
}
class Lights {
public void dim() {
System.out.println("Lights are dimming");
}
class SoundSystem {
public void on() {
System.out.println("Sound system is on");
}
class DVDPlayer {
public void on() {
System.out.println("DVD player is on");
}
// 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();
// Watch a movie
homeTheater.watchMovie();
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");
}
}
// 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)
}
}
// 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.
// 1. Subject Interface
interface Subject {
void request();
}
@Override
public void request() {
System.out.println("RealSubject: Handling request...");
}
}
@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.