Advanced Design Patterns in Java: from Bridge to Visitor
\n\nDesign patterns are proven solutions to typical software architecture problems. While basic patterns (Singleton, Factory, Observer) are familiar to every Java developer, advanced patterns often remain behind the scenes. In this article, we will cover three powerful patterns: Bridge, Visitor, and Memento. You will learn when to apply them and get ready-to-use code examples in Java.
\n\n1. Bridge — Separating Abstraction from Implementation
\n\nThe Bridge pattern is used when you need to separate an abstraction from its implementation so that they can vary independently. This is especially useful when you have multiple dimensions of change (e.g., different types of devices and different remote controls).
\n\nWhen to apply: when you want to avoid "class explosion" due to combinations of different variants.
\n\nExample: Device Control
\n\nImagine we have remotes (Remote) and devices (Device). We want one remote to work with different devices (TV, Radio).
\n\n// Implementation (Device)\ninterface Device {\n void turnOn();\n void turnOff();\n void setVolume(int percent);\n}\n\nclass TV implements Device {\n @Override\n public void turnOn() { System.out.println("TV turned on"); }\n @Override\n public void turnOff() { System.out.println("TV turned off"); }\n @Override\n public void setVolume(int percent) { System.out.println("TV volume: " + percent + "%"); }\n}\n\nclass Radio implements Device {\n @Override\n public void turnOn() { System.out.println("Radio turned on"); }\n @Override\n public void turnOff() { System.out.println("Radio turned off"); }\n @Override\n public void setVolume(int percent) { System.out.println("Radio volume: " + percent + "%"); }\n}\n\n// Abstraction (Remote)\nabstract class Remote {\n protected Device device;\n\n public Remote(Device device) {\n this.device = device;\n }\n\n public abstract void togglePower();\n public abstract void volumeUp();\n public abstract void volumeDown();\n}\n\nclass BasicRemote extends Remote {\n private boolean isOn = false;\n private int volume = 10;\n\n public BasicRemote(Device device) {\n super(device);\n }\n\n @Override\n public void togglePower() {\n if (isOn) { device.turnOff(); isOn = false; }\n else { device.turnOn(); isOn = true; }\n }\n\n @Override\n public void volumeUp() {\n volume += 5;\n device.setVolume(volume);\n }\n\n @Override\n public void volumeDown() {\n volume -= 5;\n device.setVolume(volume);\n }\n}\n\n// Usage\npublic class BridgeDemo {\n public static void main(String[] args) {\n Device tv = new TV();\n Remote remote = new BasicRemote(tv);\n remote.togglePower(); // TV turned on\n remote.volumeUp(); // TV volume: 15%\n\n Device radio = new Radio();\n remote = new BasicRemote(radio);\n remote.togglePower(); // Radio turned on\n remote.volumeUp(); // Radio volume: 15%\n }\n}\n\nAdvantage: we can add new types of devices or new remotes without changing existing code. The Bridge pattern is ideal for cross-platform applications (e.g., UI on different OS).
\n\n2. Visitor — New Operations Without Changing Classes
\n\nThe Visitor pattern allows you to add new operations to a group of objects without modifying the classes of those objects themselves. This is a classic way to bypass the limitations of single dispatch in Java.
\n\nWhen to apply: when you have a stable class hierarchy but frequently add new operations (e.g., export to different formats, report generation).
\n\nExample: Exporting Shapes to Different Formats
\n\nWe have shapes