[definition, purpose, use cases, design, example, implementation]
This is helpful when you have a legacy system or an external library that doesn’t match your application’s interfaces.
Adaptee interface: The existing interface that needs adapting.Target interface: Defines the domain-specific interface that the client uses.Adapter class: The class that implements the target interface and translates the requests from the target to the adaptee.Imagine you have an application that works with AudioPlayer, but now you need to integrate a VideoPlayer.
Instead of modifying the existing AudioPlayer class,
you can create an adapter for VideoPlayer to work with AudioPlayer without altering the original structure.
Target interface (the new interface your client expects)interface AdvancedMediaPlayer {
void play(String audioType, String fileName);
}
Adaptee (The class that needs to be adapted)class LegacyMediaPlayer {
void playMp4(String fileName) {
System.out.println("Playing mp4 file: " + fileName);
}
void playVlc(String fileName) {
System.out.println("Playing vlc file: " + fileName);
}
}
Adapter Class - The adapter class implements the AdvancedMediaPlayer interface and uses the LegacyMediaPlayer to provide the functionality.class MediaAdapter implements AdvancedMediaPlayer {
LegacyMediaPlayer legacyMediaPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
legacyMediaPlayer = new LegacyMediaPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
legacyMediaPlayer = new LegacyMediaPlayer();
}
}
@Override
void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
legacyMediaPlayer.playVlc(fileName);
} else if (audioType.equalsIgnoreCase("mp4")) {
legacyMediaPlayer.playMp4(fileName);
}
}
}
Client code that uses the Adapterpublic class AudioPlayer implements AdvancedMediaPlayer {
MediaAdapter mediaAdapter;
@Override
void play (String fileType, String fileName) {
// Playing mp3 directly
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing mp3 file: " + fileName);
}
// Use adapter to play other file formats
else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media type: " + audioType + " format not supported");
}
}
}
public static void main (String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "song.mp3"); // Playing mp3 file: song.mp3
audioPlayer.play("mp4", "video.mp4"); // Playing mp4 file: video.mp4
audioPlayer.play("vlc", "movie.vlc"); // Playing vlc file: movie.vlc
audioPlayer.play("avi", "film.avi"); // Invalid media type: avi format not supported
}
</br>
A design pattern that separates an abstraction from its implementation, allowing the two to vary independently.
This is useful when you want to avoid a permanent binding between an abstraction and its implementation.
Abstraction: The high-level interface that defines the abstraction.Implementor: The interface that defines the implementation part.RefinedAbstraction: A concrete implementation of the abstraction.ConcreteImplementor: A concrete implementation of the implementor interface.Let’s consider a simple example involving shapes and colors. Shapes don’t know how to color themselves.
Implementor Interfaceinterface Color {
void applyColor();
}
Concrete Implementorsclass RedColor implements Color {
@Override
public void applyColor() {
System.out.println("Applying red color.");
}
}
class GreenColor implements Color {
@Override
public void applyColor() {
System.out.println("Applying green color.");
}
}
abstract class Shape {
protected Color color;
protected Shape(Color color) {
this.color = color;
}
abstract void draw();
}
Refined Abstractionsclass Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
void draw() {
System.out.print("Drawing Circle. ");
color.applyColor();
}
}
class Square extends Shape {
public Square(Color color) {
super(color);
}
@Override
void draw() {
System.out.print("Drawing Square. ");
color.applyColor();
}
}
Client code to demonstrate the usagepublic static void main(String[] args) {
Shape redCircle = new Circle(new RedColor());
Shape greenSquare = new Square(new GreenColor());
redCircle.draw(); // Output: Drawing Circle. Applying red color.
greenSquare.draw(); // Output: Drawing Square. Applying green color.
}
</br>
When you want to create a tree structured to composition of classes, while treating the individual objects and the composition uniformly.
Let’s create a file system example where directories can contain files and other directories.
Component Interfaceinterface FileSystemComponent {
void showDetails();
}
Leaf Classclass File implements FileSystemComponent {
private String name;
public File(String name) {
this.name = name;
}
@Override
public void showDetails() {
System.out.println("File: " + name);
}
}
Composite Classclass Directory implements FileSystemComponent {
private String name;
private List<FileSystemComponent> components = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
public void addComponent(FileSystemComponent component) {
components.add(component);
}
public void removeComponent(FileSystemComponent component) {
components.remove(component);
}
@Override
public void showDetails() {
System.out.println("Directory: " + name);
for (FileSystemComponent component : components) {
component.showDetails();
}
}
}
Client code to demonstrate the usagepublic static void main(String[] args) {
FileSystemComponent file1 = new File("File1.txt");
FileSystemComponent file2 = new File("File2.txt");
Directory directory1 = new Directory("Directory1");
directory1.addComponent(file1);
directory1.addComponent(file2);
FileSystemComponent file3 = new File("File3.txt");
Directory directory2 = new Directory("Directory2");
directory2.addComponent(directory1);
directory2.addComponent(file3);
// Display the file system structure
directory2.showDetails();
// Output-------
/*
Directory: Directory2
Directory: Directory1
File: File1.txt
File: File2.txt
File: File3.txt
*/
}
</br>
</br>
Purpose: Dynamically adds responsibilities to objects by wrapping them in additional functionality without altering the object itself. It provides an alternative to subclassing for extending behavior.
Application: Adding additional features (e.g., scrollbars or borders) to GUI components.
Example: Say you want to take an order for a pizza, with many possible combinations of toppings. Creating subclass to superclass Pizza for each combination would create Class Explosion. So we create a Decorative Layer on top that could accommodate multiple combinations but return the same class ultimately. This decorating layer will have both ‘has-a’ & ‘is-a’ relationships with the base class.

</br>
This pattern is particularly useful when you want to reduce the complexity of interacting with multiple classes or libraries.
Facade Class: This class provides a simplified interface to the complex subsystem.Subsystem Classes: These are the classes that the Facade interacts with. They contain the actual business logic.Let’s create an example for a home theater system that consists of several components: a DVD player, a projector, and a sound system.
Subsystem Classesclass DVDPlayer {
public void on() {
System.out.println("DVD Player is ON");
}
public void play(String movie) {
System.out.println("Playing movie: " + movie);
}
public void stop() {
System.out.println("DVD Player stopped");
}
public void off() {
System.out.println("DVD Player is OFF");
}
}
class Projector {
public void on() {
System.out.println("Projector is ON");
}
public void setInput(String input) {
System.out.println("Projector input set to: " + input);
}
public void off() {
System.out.println("Projector is OFF");
}
}
class SoundSystem {
public void on() {
System.out.println("Sound System is ON");
}
public void setVolume(int level) {
System.out.println("Sound System volume set to: " + level);
}
public void off() {
System.out.println("Sound System is OFF");
}
}
Facade Classclass HomeTheaterFacade {
private DVDPlayer dvdPlayer;
private Projector projector;
private SoundSystem soundSystem;
public HomeTheaterFacade(DVDPlayer dvdPlayer, Projector projector, SoundSystem soundSystem) {
this.dvdPlayer = dvdPlayer;
this.projector = projector;
this.soundSystem = soundSystem;
}
public void watchMovie(String movie) {
System.out.println("Get ready to watch a movie...");
projector.on();
projector.setInput("DVD");
soundSystem.on();
soundSystem.setVolume(10);
dvdPlayer.on();
dvdPlayer.play(movie);
}
public void endMovie() {
System.out.println("Shutting movie theater down...");
dvdPlayer.stop();
dvdPlayer.off();
soundSystem.off();
projector.off();
}
}
Client code to demonstrate the usagepublic static void main(String[] args) {
DVDPlayer dvdPlayer = new DVDPlayer();
Projector projector = new Projector();
SoundSystem soundSystem = new SoundSystem();
HomeTheaterFacade homeTheater = new HomeTheaterFacade(dvdPlayer, projector, soundSystem);
homeTheater.watchMovie("Inception");
homeTheater.endMovie();
}
</br>
A pattern that aims to minimize memory usage by sharing common data among multiple objects.
Flyweight class: The shared object that contains the intrinsic state (common data).Intrinsic State: The part of the state that can be shared across multiple objects.Extrinsic State: The part of the state that cannot be shared and is passed to the Flyweight object.Let’s create a simple example of a text editor that manages different font styles.
Flyweight Interfaceinterface Font {
void display(int size, String color);
}
Concrete Flyweight Classesclass ConcreteFont implements Font {
private String fontType; // Intrinsic state
public ConcreteFont(String fontType) {
this.fontType = fontType;
}
@Override
public void display(int size, String color) {
System.out.println("Font: " + fontType + ", Size: " + size + ", Color: " + color);
}
}
Flyweight Factoryclass FontFactory {
private static final HashMap<String, Font> fontMap = new HashMap<>();
public static Font getFont(String fontType) {
Font font = fontMap.get(fontType);
if (font == null) {
font = new ConcreteFont(fontType);
fontMap.put(fontType, font);
System.out.println("Creating new font: " + fontType);
}
return font;
}
}
public static void main(String[] args) {
Font font1 = FontFactory.getFont("Arial");
font1.display(12, "Red");
Font font2 = FontFactory.getFont("Arial");
font2.display(14, "Blue");
Font font3 = FontFactory.getFont("Times New Roman");
font3.display(16, "Green");
// Both font1 and font2 point to the same Arial font instance
System.out.println("font1 == font2: " + (font1 == font2));
}
</br>
Subject: An interface that defines the common interface for the RealObject and the Proxy.RealObject: The actual object that the proxy represents and delegates requests to.Proxy: The class that implements the Subject interface and contains a reference to the RealObject. It controls access to the RealObject.Let’s illustrate the Proxy Design Pattern with a simple example involving image loading.
Subject Interfacepublic interface Image {
void display();
}
RealObjectpublic class RealImage implements Image {
private String filename;
public RealImage(String filename) {
loadImageFromDisk();
this.filename = filename;
}
private void loadImageFromDisk() {
System.out.println("Loading " + filename);
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
}
Proxypublic 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();
}
}
Client code to demonstrate the usagepublic static void main(String[] args) {
Image image1 = new ProxyImage("image1.jpg");
Image image2 = new ProxyImage("image2.jpg");
// Image will be loaded from disk
image1.display();
System.out.println();
// Image will not be loaded from disk, already loaded
image1.display();
System.out.println();
// Image will be loaded from disk
image2.display();
}