Structural Design Patterns: Decorator Pattern
Previously we altered the behaviour of our abstract objects using the bridge pattern and we implemented a tree like structure for our components using the composite pattern and delegating the requests.
The decorator pattern allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.
So imagine the scenario of applying various discounts to one of our products.
We will start with the discount interface to specify the discount action.
package com.gkatzioura.design.structural.decorator; import java.math.BigDecimal; public interface Discount { BigDecimal apply(BigDecimal originalPrice); }
An object which shall implement the discount will apply the discount and give back the discounted price.
One of the discounts that we want to apply is a special discount for newly registered members.
Newly registered members will have 20% off on their first order
package com.gkatzioura.design.structural.decorator; import java.math.BigDecimal; public class NewlyRegisteredDiscount implements Discount { public static final BigDecimal SEVENTY_FIVE = new BigDecimal(75); public static final BigDecimal ONE_HUNDRED = new BigDecimal(100); @Override public BigDecimal apply(BigDecimal originalPrice) { return originalPrice.multiply(SEVENTY_FIVE).divide(ONE_HUNDRED); } }
As you have experienced sometimes more than one discounts are applied.
Our system also provides our customers with coupon discounts of 5 dollars off.
It is very comon to apply multiple discounts and our systems has to support this action. It is common to create discount packages which in reality are just a compination of packages.
To do so we will utilize the decorator pattern.
We will create the discount decorator.
package com.gkatzioura.design.structural.decorator; import java.math.BigDecimal; public class DiscountDecorator implements Discount { protected Discount discount; public DiscountDecorator(Discount discount) { this.discount = discount; } @Override public BigDecimal apply(BigDecimal originalPrice) { return discount.apply(originalPrice); } }
So we want to have a first purchase discount and a 5 dollars of discount for new users referenced by other users.
We will call this discount as ‘referenced user’ discount.
The goal is to have 5 dollars off and a discount applied to the original price.
package com.gkatzioura.design.structural.decorator; import java.math.BigDecimal; public class ReferencedUserDiscount extends DiscountDecorator { public static final BigDecimal FIVE = new BigDecimal(5); public ReferencedUserDiscount(Discount discount) { super(discount); } @Override public BigDecimal apply(BigDecimal originalPrice) { BigDecimal discountedPrice = super.apply(originalPrice); if(discountedPrice.compareTo(FIVE)<=0) { return discountedPrice; } return discountedPrice.subtract(FIVE); } }
Imagine now the scenario of a loyalty discount. We want a two year plan for our users with a discount of 5% for each one of their orders.
package com.gkatzioura.design.structural.decorator; import java.math.BigDecimal; public class TwoYearPlanDiscount extends DiscountDecorator { public static final BigDecimal NINETY_NINE = new BigDecimal(95); public static final BigDecimal ONE_HUNDRED = new BigDecimal(100); public TwoYearPlanDiscount(Discount discount) { super(discount); } @Override public BigDecimal apply(BigDecimal originalPrice) { return super.apply(originalPrice).multiply(NINETY_NINE).divide(ONE_HUNDRED); } }
As you can see both in the cases of the referenced user discount and both in the case of the two years plan discount we didn’t alter our original discount for our newly registered users. What we did is decorating the original discount and giving back the discount by taking into account the various options.
The decorator pattern is about
- Add Responsibilities or remove them from an object dynamically at run-time.
- Extend functionality in a flexible way without subclassing
So let’s put our plan into action.
package com.gkatzioura.design.structural.decorator; import java.math.BigDecimal; public class DecoratorScenario { public static void main(String args[]) { NewlyRegisteredDiscount newlyRegisteredDiscount = new NewlyRegisteredDiscount(); ReferencedUserDiscount referencedUserDiscount = new ReferencedUserDiscount(newlyRegisteredDiscount); TwoYearPlanDiscount twoYearPlanDiscount = new TwoYearPlanDiscount(referencedUserDiscount); BigDecimal discountPrice = twoYearPlanDiscount.apply(new BigDecimal(100)); } }
Our newly registered users will have three types of discounts.
You can find the sourcecode on github.
Published on Java Code Geeks with permission by Emmanouil Gkatziouras, partner at our JCG program. See the original article here: Structural Design Patterns: Decorator Pattern Opinions expressed by Java Code Geeks contributors are their own. |