Decorator Design Pattern Overview
- Attaches additional responsibilities to an object dynamically.
- Provides a flexible alternative to subclassing for extending functionality.
- Minimizes class explosion (1 class for unique combination of functionality/stats).
Output for both Before and After
$0.99 : Dark Roast
$1.19 : Dark Roast with Mocha
$1.39 : Dark Roast with Double Mocha
$1.09 : Dark Roast with Steamed Milk
$1.49 : Dark Roast with Double Mocha and Steamed Milk
$1.99 : Espresso
$2.19 : Espresso with Mocha
$2.39 : Espresso with Double Mocha
$2.09 : Espresso with Steamed Milk
$2.49 : Espresso with Double Mocha and Steamed Milk
Before
class Beverage {
String description() { return 'Unknown Beverage'; };
float cost() { return 0; }
}
class DarkRoast extends Beverage {
@Override
String description() {
return 'Dark Roast';
}
@Override
float cost() {
return 0.99;
}
}
class DarkRoastWithDoubleMocha extends Beverage{
@Override
String description() {
return 'Dark Roast with Double Mocha';
}
@Override
float cost() {
return 0.99 + 0.20 + 0.20;
}
}
class DarkRoastWithDoubleMochaAndSteamedMilk extends Beverage{
@Override
String description() {
return 'Dark Roast with Double Mocha and Steamed Milk';
}
@Override
float cost() {
return 0.99 + 0.20 + 0.20 + 0.10;
}
}
class DarkRoastWithMocha extends Beverage{
@Override
String description() {
return 'Dark Roast with Mocha';
}
@Override
float cost() {
return 0.99 + 0.20;
}
}
class DarkRoastWithSteamedMilk extends Beverage{
@Override
String description() {
return 'Dark Roast with Steamed Milk';
}
@Override
float cost() {
return 0.99 + 0.10;
}
}
class Espresso extends Beverage{
@Override
String description() {
return 'Espresso';
}
@Override
float cost() {
return 1.99;
}
}
class EspressoWithDoubleMocha extends Beverage{
@Override
String description() {
return 'Espresso with Double Mocha';
}
@Override
float cost() {
return 1.99 + 0.20 + 0.20;
}
}
class EspressoWithDoubleMochaAndSteamedMilk extends Beverage{
@Override
String description() {
return 'Espresso with Double Mocha and Steamed Milk';
}
@Override
float cost() {
return 1.99 + 0.20 + 0.20 + 0.10;
}
}
class EspressoWithMocha extends Beverage{
@Override
String description() {
return 'Espresso with Mocha';
}
@Override
float cost() {
return 1.99 + 0.20;
}
}
class EspressoWithSteamedMilk extends Beverage{
@Override
String description() {
return 'Espresso with Steamed Milk';
}
@Override
float cost() {
return 1.99 + 0.10;
}
}
import java.text.DecimalFormat
class Simulator {
static void main(String[] args) {
display(new DarkRoast());
display(new DarkRoastWithMocha());
display(new DarkRoastWithDoubleMocha());
display(new DarkRoastWithSteamedMilk());
display(new DarkRoastWithDoubleMochaAndSteamedMilk());
display(new Espresso());
display(new EspressoWithMocha());
display(new EspressoWithDoubleMocha());
display(new EspressoWithSteamedMilk());
display(new EspressoWithDoubleMochaAndSteamedMilk());
}
static void display(Beverage b) {
DecimalFormat df = new DecimalFormat("#.##");
System.out.println('$' + df.format(b.cost()) + ' : ' + b.description());
}
}
After
class Beverage {
String description() { return 'Unknown Beverage'; };
float cost() { return 0; }
}
class Condiment extends Beverage {
Beverage beverage;
Condiment(Beverage b) {
beverage = b;
}
@Override
float cost() {
return beverage.cost();
}
}
class DarkRoast extends Beverage {
@Override
String description() {
return 'Dark Roast';
}
@Override
float cost() {
return 0.99;
}
}
class Espresso extends Beverage {
@Override
String description() {
return 'Espresso';
}
@Override
float cost() {
return 1.99;
}
}
class Mocha extends Condiment {
Mocha(Beverage b) {
super(b);
}
@Override
String description() {
return beverage.description() + ', Mocha';
}
@Override
float cost() {
return beverage.cost() + 0.20;
}
}
class SteamedMilk extends Condiment {
SteamedMilk(Beverage b) {
super(b);
}
@Override
String description() {
return beverage.description() + ', Steamed Milk';
}
@Override
float cost() {
return beverage.cost() + 0.10;
}
}
class PrettyPrintBeverage extends Condiment {
private static final String[] tuple = ['', 'Double', 'Triple', 'Quad', 'Max'];
PrettyPrintBeverage(Beverage b) {
super(b);
}
@Override
String description() {
String[] descriptions = beverage.description().split(',');
if (descriptions.size() < 2) return beverage.description();
HashMap ingredients = getIngredients(descriptions)
StringBuilder ingredientDesc = getIngredientDescription(ingredients)
return ingredientDesc.toString();
}
private StringBuilder getIngredientDescription(HashMap ingredients) {
int totalIngredients = ingredients.size();
StringBuilder ingredientDesc = new StringBuilder('');
int tupleSize = tuple.size();
int count = 0;
for (Map.Entry ingredient : ingredients.entrySet()) {
count++;
if (count == 2) {
ingredientDesc.append(' with ');
} else if (count > 2 && totalIngredients - count == 0){
ingredientDesc.append(' and ');
} else if (count > 2) {
ingredientDesc.append(', ');
};
int quantity = ingredient.getValue();
String quantityName = quantity > tupleSize ? tuple[tupleSize - 1] : tuple[quantity - 1];
String ingredientName = ingredient.getKey();
String tempDesc = quantityName.trim() == '' ? ingredientName : quantityName + ' ' + ingredientName;
ingredientDesc.append(tempDesc);
}
return ingredientDesc;
}
private HashMap getIngredients(String[] descriptions) {
int totalDescriptions = descriptions.size();
Map ingredients = new HashMap();
for (int i = 0; i < totalDescriptions; i++) {
String desc = descriptions[i].trim();
if (ingredients[desc] == null) {
ingredients[desc] = 1;
} else {
ingredients[desc] += 1;
}
}
return ingredients;
}
}
import java.text.DecimalFormat
class Simulator {
static void main(String[] args) {
display(new DarkRoast());
display(new Mocha(new DarkRoast()));
display(new Mocha(new Mocha(new DarkRoast())));
display(new SteamedMilk(new DarkRoast()));
display(new SteamedMilk(new Mocha(new Mocha(new DarkRoast()))));
display(new Espresso());
display(new Mocha(new Espresso()));
display(new Mocha(new Mocha(new Espresso())));
display(new SteamedMilk(new Espresso()));
display(new SteamedMilk(new Mocha(new Mocha(new Espresso()))));
}
static void display(Beverage b) {
DecimalFormat df = new DecimalFormat("#.##");
PrettyPrintBeverage prettyPrint = new PrettyPrintBeverage(b);
System.out.println('$' + df.format(b.cost()) + ' : ' + prettyPrint.description());
}
}