Understanding the Decorator Pattern with Phone Renovation Example
Overview
The Decorator Pattern enables dynamic extension of an object's functionality without altering its structure. This pattern offers two primary implementation strategies:
Approach 1: Extend the original class through inheritance to add supplementary features while preserving existing functionality.
Approach 2: Create a decorator wrapper class that encapsulates the original object and provides additional behavior while maintaining the original interface contract.
This article demontsrates both approaches using a practical scenario: renovating an old phone into a new one.
Approach 1: Inheritance-Based Decoration
Defining the Base Abstract Class
/**
* Abstract base class representing a legacy device
*/
public abstract class LegacyDevice {
/**
* Renovation operation: transforming old into new
*/
public abstract void renovate();
}
First-Level Decoration: Replacing the Shell
/**
* Concrete decorator: device with new exterior
*/
public class RenovatedDeviceTier1 extends LegacyDevice {
public void replaceShell() {
System.out.println("Replacing external shell");
}
@Override
public void renovate() {
this.replaceShell();
}
}
Second-Level Decoration: Components and Packaging
Building upon the first level, this class adds component replacement and repackaging:
/**
* Advanced renovation: components and packaging
*/
public class RenovatedDeviceTier2 extends RenovatedDeviceTier1 {
public void replaceComponents() {
System.out.println("Replacing internal components");
}
public void updatePackaging() {
System.out.println("Updating product packaging");
}
@Override
public void renovate() {
this.replaceComponents();
super.replaceShell();
this.updatePackaging();
}
}
Testing the Inheritance Approach
/**
* Verification test for inheritance-based decoration
*/
public class Application {
public static void main(String[] args) {
LegacyDevice device1 = new RenovatedDeviceTier1();
device1.renovate();
System.out.println("Market Value: $200");
System.out.println("");
LegacyDevice device2 = new RenovatedDeviceTier2();
device2.renovate();
System.out.println("Market Value: $350");
}
}
Approach 2: Wrapper-Based Decoration
This approach uses composition rather than inheritance, providing greater flexibility.
Defining the Base Interface
/**
* Contract for device renovation operations
*/
public abstract class Device {
/**
* Transformation from old to new state
*/
public abstract void transform();
}
Concrete Component: Basic Renovated Device
/**
* Basic renovation implementation
*/
public class BasicRenovation extends Device {
public void replaceShell() {
System.out.println("Replacing external shell");
}
@Override
public void transform() {
this.replaceShell();
}
}
Abstract Decorator Class
/**
* Base decorator providing wrapper functionality
*/
public abstract class DeviceDecorator extends Device {
protected Device wrappedDevice;
public DeviceDecorator(Device device) {
this.wrappedDevice = device;
}
@Override
public void transform() {
this.wrappedDevice.transform();
}
}
Concrete Decorators: Independent Operations
Each decorator handles a specific enhancement independently:
/**
* Decorator for component replacement
*/
public class ComponentReplacement extends DeviceDecorator {
public ComponentReplacement(Device device) {
super(device);
}
public void replaceParts() {
System.out.println("Replacing internal components");
}
@Override
public void transform() {
super.transform();
this.replaceParts();
}
}
/**
* Decorator for packaging updates
*/
public class PackagingUpdate extends DeviceDecorator {
public PackagingUpdate(Device device) {
super(device);
}
public void refreshPackage() {
System.out.println("Refreshing product packaging");
}
@Override
public void transform() {
super.transform();
this.refreshPackage();
}
}
Testing the Wrapper Approach
/**
* Verification test for wrapper-based decoration
*/
public class Application {
public static void main(String[] args) {
Device device = new BasicRenovation();
device = new ComponentReplacement(device);
device = new PackagingUpdate(device);
device.transform();
System.out.println("Market Value: $350");
}
}
Comparison
| Aspect | Inheritance Approach | Wrapper Approach |
|---|---|---|
| Flexibility | Limited by class hierarchy | Runtime composition |
| Code Reuse | Requires deep inheritance | Independent decorators |
| Modification | Requires new subclasses | Add new decorators freely |
| Coupling | Tight coupling to parent | Loose coupling |
The wrapper-based approach provides superior flexibility for dynamic feature composition, while inheritance offers simplicity for static hierarchies.