The Decorator Pattern is part of the structural design pattern and this is a pattern which attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub classing for extending functionality. Its build on the design principle “Classes should be open for extension, but closed for modification.”
The goal is to allow classes to be easily extended to incorporate new behavior without modifying existing code. So our design will be tough to change and flexible enough to take on new functionality to meet changing requirements. The purpose of the decorator design pattern is to add functionality to objects without extending them and without affecting the behavior of other objects from the same class.
The decorator design pattern works by wrapping the decorated object and it can be applied during runtime. Decorators are extremely useful in the cases where there could be multiple extensions of a class and they could be combined in various ways. Instead of writing all the possible combinations which can lead into class explosion, decorators can be created and additional functionality can be applied at runtime.
Below is the generic class diagram for decorator pattern
Lets say we wanted to apply some calibration on the data we get from ems and what calibration to apply is decided on the basis of what vendor type so when we started this design pattern below is the class diagram we implemented
But after sometime there was additional requirement where we needed to apply additional calibration based on the techtype,policytype and device type also. Initially we thought of creating a one class for each combination of vendor and property type but then we realized that it would create too many classes and also there was a high possibility that there will be additional properties would be added to the list which would cause in creating many more classes.So here we considered using decorator pattern so that we can dynamically add functionality into objects.
Below is the class diagram
So we create an abstract CalibrationDecorator which extends VendorCalibration and then we add the calibrate method to each of the property classes and we delegate the functionality from each property classes to calculate the calibration for the vendor type . Below is a snippet so in the below snippet we first apply the calibration based on the vendor type and then based on techtype and then finally on the devicetype.
double calibratedData = new DeviceTypeCalibration(new TechTypeCalibration(new HuwCalibration())) .calibrate(rawData);
Lets code the example
public abstract class VendorCalibration { public abstract double calibrate(Double rawdata); }
public class HuwCalibration extends VendorCalibration { @Override public double calibrate(Double rawdata) { // TODO Auto-generated method stub return rawdata * 0.945; } }
public class BrocadeCalibration extends VendorCalibration { @Override public double calibrate(Double rawdata) { // TODO Auto-generated method stub return rawdata * 0.967; } }
public class CiscoCalibration extends VendorCalibration { @Override public double calibrate(Double rawdata) { // TODO Auto-generated method stub return rawdata * 0.8567; } }
Lets code the decorator abstract class and the concrete classes
public abstract class CalibrationDecorator extends VendorCalibration { }
public class PolicyCalibration extends CalibrationDecorator{ public VendorCalibration vendorCalibration; public PolicyCalibration(VendorCalibration vendorCalibration) { super(); this.vendorCalibration = vendorCalibration; } public PolicyCalibration() { super(); // TODO Auto-generated constructor stub } public VendorCalibration getVendorCalibration() { return vendorCalibration; } public void setVendorCalibration(VendorCalibration vendorCalibration) { this.vendorCalibration = vendorCalibration; } @Override public double calibrate(Double rawdata) { // TODO Auto-generated method stub return vendorCalibration.calibrate(rawdata) * 0.998; } }
Here we delegate the calibration to be calculated based on the vendor type using the composed vendorCalibration
public class TechTypeCalibration extends CalibrationDecorator { public VendorCalibration vendorCalibration; public TechTypeCalibration(VendorCalibration vendorCalibration) { super(); this.vendorCalibration = vendorCalibration; } public TechTypeCalibration() { super(); // TODO Auto-generated constructor stub } public VendorCalibration getVendorCalibration() { return vendorCalibration; } public void setVendorCalibration(VendorCalibration vendorCalibration) { this.vendorCalibration = vendorCalibration; } @Override public double calibrate(Double rawdata) { // TODO Auto-generated method stub return vendorCalibration.calibrate(rawdata) * 0.978; } }
public class DeviceTypeCalibration extends CalibrationDecorator { public VendorCalibration vendorCalibration; public DeviceTypeCalibration(VendorCalibration vendorCalibration) { super(); this.vendorCalibration = vendorCalibration; } public DeviceTypeCalibration() { super(); // TODO Auto-generated constructor stub } public VendorCalibration getVendorCalibration() { return vendorCalibration; } public void setVendorCalibration(VendorCalibration vendorCalibration) { this.vendorCalibration = vendorCalibration; } @Override public double calibrate(Double rawdata) { // TODO Auto-generated method stub return vendorCalibration.calibrate(rawdata) * 0.98898; } }
public class Driver { public static void main(String[] args) { double rawData = 2.345; double calibratedData = new DeviceTypeCalibration(new TechTypeCalibration(new HuwCalibration())) .calibrate(rawData); System.out.println(calibratedData); } }