The Bridge pattern is a structural design pattern that decouples an abstraction from its implementation so that the two can vary independently. It involves creating two separate hierarchies: one for the abstraction and one for the implementation. The abstraction contains a reference to the implementation, allowing it to delegate some of its responsibilities to the implementation object.
The Adapter pattern is also a structural design pattern that allows objects with incompatible interfaces to collaborate. It acts as a bridge between two incompatible interfaces, converting the interface of one class into another interface that clients expect. The Adapter pattern lets classes work together that couldn’t otherwise because of incompatible interfaces.
Here is a simple Java code example to illustrate the Bridge pattern:
// Implementor interface
interface Color {
void applyColor();
}
// Concrete Implementor
class RedColor implements Color {
@Override
public void applyColor() {
System.out.println("Applying red color");
}
}
// Concrete Implementor
class BlueColor implements Color {
@Override
public void applyColor() {
System.out.println("Applying blue color");
}
}
// Abstraction
abstract class Shape {
protected Color color;
public Shape(Color color) {
this.color = color;
}
abstract void draw();
}
// Refined Abstraction
class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
void draw() {
System.out.print("Drawing a circle. ");
color.applyColor();
}
}
// Refined Abstraction
class Square extends Shape {
public Square(Color color) {
super(color);
}
@Override
void draw() {
System.out.print("Drawing a square. ");
color.applyColor();
}
}
// Client code
public class BridgePatternExample {
public static void main(String[] args) {
Shape redCircle = new Circle(new RedColor());
redCircle.draw();
Shape blueSquare = new Square(new BlueColor());
blueSquare.draw();
}
}
In this example, the Color
interface is the implementor, and the RedColor
and BlueColor
classes are concrete implementors. The Shape
abstract class is the abstraction, and the Circle
and Square
classes are refined abstractions. The Shape
class contains a reference to the Color
object, allowing it to delegate the color application responsibility to the Color
object.
Here is a Java code example to illustrate the Adapter pattern:
// Target interface
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee
class AdvancedMediaPlayer {
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: " + fileName);
}
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: " + fileName);
}
}
// Adapter
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMediaPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMediaPlayer = new AdvancedMediaPlayer();
}
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMediaPlayer.playVlc(fileName);
}
}
}
// Concrete Target
class AudioPlayer implements MediaPlayer {
private 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")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media type");
}
}
}
// Client code
public class AdapterPatternExample {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "song.mp3");
audioPlayer.play("vlc", "video.vlc");
}
}
In this example, the MediaPlayer
interface is the target, the AdvancedMediaPlayer
class is the adaptee, and the MediaAdapter
class is the adapter. The AudioPlayer
class uses the MediaAdapter
to play VLC files, which it couldn’t do directly because of the incompatible interfaces.
In summary, the Bridge pattern and the Adapter pattern are both important structural design patterns in Java. The Bridge pattern is used to decouple an abstraction from its implementation, allowing them to vary independently. On the other hand, the Adapter pattern is used to make incompatible interfaces work together. By understanding the fundamental concepts, usage methods, common practices, and best practices of these patterns, Java developers can make informed decisions about which pattern to use in different scenarios, leading to more robust and maintainable code.