Structural Patterns and it’s types
Structural patterns design are patterns that ease the design by identifying a simple way to realize relationships among entities. These patterns help ensure that if one part of a system changes, the entire system doesn’t need to change. They focus on how classes and objects can be composed to form larger structures. Flyweight Method Design Patterns and Proxy Method Design Patterns

Table of Contents
Types of Structural Patterns Design
1. Adapter Pattern
2. Bridge Pattern
3. Composite Pattern
4. Decorator Pattern
5. Facade Pattern
6. Flyweight Pattern
7. Proxy Pattern
1. Adapter Pattern
The Adapter Pattern allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces by converting the interface of a class into another interface that a client expects.
Advantages:
- Promotes code reusability by allowing existing classes to be used with new interfaces.
- Allows the use of legacy code with modern interfaces.
Disadvantages:
- Can introduce additional complexity.
- May slow down performance due to the additional level of indirection.
java
// Target interface
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee class
class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer.playVlc(fileName);
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer.playMp4(fileName);
}
}
}
// AdvancedMediaPlayer interface
interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
// Concrete implementations of AdvancedMediaPlayer
class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: " + fileName);
}
@Override
public void playMp4(String fileName) {
// do nothing
}
}
class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
// do nothing
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: " + fileName);
}
}
// Client class
class AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing mp3 file. Name: " + fileName);
} else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media. " + audioType + " format not supported");
}
}
}
// Usage
public class AdapterPatternDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}
2. Bridge Pattern
The Bridge Pattern decouples an abstraction from its implementation so that the two can vary independently. This pattern involves an interface which acts as a bridge making the functionality of concrete classes independent from interface implementer classes.
Advantages:
- Decouples implementation from abstraction.
- Improves scalability and extensibility by allowing changes to implementation without modifying client code.
Disadvantages
Can increase complexity by requiring multiple layers of abstraction.
Example:
java
// Implementor interface
interface DrawAPI {
void drawCircle(int radius, int x, int y);
}
// Concrete implementors
class RedCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: " + radius + ", x: " + x + ", y: " + y + "]");
}
}
class GreenCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: green, radius: " + radius + ", x: " + x + ", y: " + y + "]");
}
}
// Abstraction
abstract class Shape {
protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI) {
this.drawAPI = drawAPI;
}
public abstract void draw();
}
// Refined Abstraction
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);
}
}
// Usage
public class BridgePatternDemo {
public static void main(String[] args) {
Shape redCircle = new Circle(100, 100, 10, new RedCircle());
Shape greenCircle = new Circle(100, 100, 10, new GreenCircle());
redCircle.draw();
greenCircle.draw();
}
}
3. Composite Pattern
The Composite Pattern allows you to compose objects into tree structures to represent part-whole hierarchies. This pattern lets clients treat individual objects and compositions of objects uniformly.
Advantages
- Simplifies client code that interacts with the composite structure.
- Makes it easier to add new types of components.
Disadvantages
- Can make the system overly general.
- Can be difficult to restrict the types of components in the composite.
Example:
java
import java.util.ArrayList;
import java.util.List;
// Component interface
interface Employee {
void showEmployeeDetails();
}
// Leaf class
class Developer implements Employee {
private String name;
private long empId;
private String position;
public Developer(long empId, String name, String position) {
this.empId = empId;
this.name = name;
this.position = position;
}
@Override
public void showEmployeeDetails() {
System.out.println(empId + " " + name + " " + position);
}
}
// Leaf class
class Manager implements Employee {
private String name;
private long empId;
private String position;
public Manager(long empId, String name, String position) {
this.empId = empId;
this.name = name;
this.position = position;
}
@Override
public void showEmployeeDetails() {
System.out.println(empId + " " + name + " " + position);
}
}
// Composite class
class CompanyDirectory implements Employee {
private List<Employee> employeeList = new ArrayList<>();
@Override
public void showEmployeeDetails() {
for (Employee emp : employeeList) {
emp.showEmployeeDetails();
}
}
public void addEmployee(Employee emp) {
employeeList.add(emp);
}
public void removeEmployee(Employee emp) {
employeeList.remove(emp);
}
}
// Usage
public class CompositePatternDemo {
public static void main(String[] args) {
Developer dev1 = new Developer(100, "John Doe", "Pro Developer");
Developer dev2 = new Developer(101, "Jane Doe", "Entry Developer");
Manager manager1 = new Manager(200, "Mary Smith", "SEO Manager");
Manager manager2 = new Manager(201, "Sally Jones", "Content Manager");
CompanyDirectory directory = new CompanyDirectory();
directory.addEmployee(dev1);
directory.addEmployee(dev2);
directory.addEmployee(manager1);
directory.addEmployee(manager2);
directory.showEmployeeDetails();
}
}
4. Decorator Pattern
The Decorator Pattern allows behavior to be added to individual objects, dynamically, without affecting the behavior of other objects from the same class.
Advantages:
- More flexible than inheritance for extending functionality.
- Promotes the Single Responsibility Principle by allowing functionality to be divided between classes with unique areas of concern.
Disadvantages:
- Can lead to a large number of small classes that are hard to maintain.
- Complicates the instantiation process because you have to instantiate multiple decorators.
Example:
java
// Component interface
interface Car {
void assemble();
}
// Concrete Component
class BasicCar implements Car {
@Override
public void assemble() {
System.out.print("Basic Car.");
}
}
// Decorator class
abstract class CarDecorator implements Car {
protected Car decoratedCar;
public CarDecorator(Car car) {
this.decoratedCar = car;
}
public void assemble() {
this.decoratedCar.assemble();
}
}
// Concrete Decorators
class SportsCar extends CarDecorator {
public SportsCar(Car car) {
super(car);
}
@Override
public void assemble() {
super.assemble();
System.out.print(" Adding features of Sports Car.");
}
}
class LuxuryCar extends CarDecorator {
public LuxuryCar(Car car) {
super(car);
}
@Override
public void assemble() {
super.assemble();
System.out.print(" Adding features of Luxury Car.");
}
}
// Usage
public class DecoratorPatternDemo {
public static void main(String[] args) {
Car sportsCar = new SportsCar(new BasicCar());
sportsCar.assemble();
System.out.println("\n *");
Car sportsLuxuryCar = new SportsCar(new LuxuryCar(new BasicCar()));
sportsLuxuryCar.assemble();
}
}
5. Facade Pattern
The Facade Pattern provides a simplified interface to a complex subsystem. It hides the complexities of the system and provides an interface to the client using which the client can access the system Structural Patterns Design .
Advantages:
- Reduces complexity by hiding the internal implementation details.
- Makes the system easier to use and promotes decoupling.
Disadvantages
- Can become a god object, exposing too much functionality.
- May hide important details that could be important for debugging.
java
// Subsystem classes
class CPU {
public void freeze() {
System.out.println("CPU frozen");
}
public void jump(long position) {
System.out.println("CPU jumps to " + position);
}
public void execute() {
System.out.println("CPU executes");
}
}
class Memory {
public void load(long position, byte[] data) {
System.out.println("Memory loads data at position " + position);
}
}
class HardDrive {
public byte[] read(long lba, int size) {
System.out.println("HardDrive reads data from LBA " + lba + " of size " + size);
return new byte[size];
}
}
// Facade class
class Computer {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public Computer() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
}
public void start() {
cpu.freeze();
memory.load(0, hardDrive.read(0, 1024));
cpu.jump(0);
cpu.execute();
}
}
// Usage
public class FacadePatternDemo {
public static void main(String[] args) {
Computer computer = new Computer();
computer.start();
}
}
6. Flyweight Pattern
The Flyweight Pattern minimizes memory use by sharing as much data as possible with other similar objects. It is a way to use objects in large quantities when a simple repeated representation would use an unacceptable amount of memory.
Advantages:
- Reduces memory usage by sharing objects.
- Improves performance by reducing the number of objects created.
Disadvantages:
- Can introduce complexity due to the need to manage shared objects.
- Makes the system harder to understand and maintain.
Example:
java
import java.util.HashMap;
import java.util.Map;
// Flyweight interface
interface Shape {
void draw();
}
// Concrete Flyweight
class Circle implements Shape {
private String color;
private int x;
private int y;
private int radius;
public Circle(String color) {
this.color = color;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setRadius(int radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Circle: Draw() [Color : " + color + ", x : " + x + ", y :" + y + ", radius :" + radius);
}
}
// Flyweight Factory
class ShapeFactory {
private static final Map<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
Circle circle = (Circle) circleMap.get(color);
if (circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("Creating circle of color : " + color);
}
return circle;
}
}
// Usage
public class FlyweightPatternDemo {
private static final String colors[] = {"Red", "Green", "Blue", "White", "Black"};
public static void main(String[] args) {
for (int i = 0; i < 20; ++i) {
Circle circle = (Circle) ShapeFactory.getCircle(getRandomColor());
circle.setX(getRandomX());
circle.setY(getRandomY());
circle.setRadius(100);
circle.draw();
}
}
private static String getRandomColor() {
return colors[(int) (Math.random() * colors.length)];
}
private static int getRandomX() {
return (int) (Math.random() * 100);
}
private static int getRandomY() {
return (int) (Math.random() * 100);
}
}
7. Proxy Pattern
The Proxy Pattern provides a surrogate or placeholder for another object to control access to it. This can add an additional layer of security, or reduce the load on a system by lazy-initializing expensive objects.
Advantages:
- Controls access to an object, allowing different levels of access.
- Can introduce lazy initialization, improving performance by delaying the creation of expensive objects.
Disadvantages:
- Can introduce additional complexity.
- May introduce latency due to the extra level of indirection.
java
// Subject interface
interface Image {
void display();
}
// RealSubject
class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading " + fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
}
// Proxy
class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
// Usage
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
// Image will be loaded from disk
image.display();
System.out.println("");
// Image will not be loaded from disk
image.display();
}
}