Monday, July 30, 2007

Configurable Decorators using AOP

Sometime back I had posted on the increasing misuse (or overuse) of aspects by today's developers. In many of those cases, plain old decorators would have just been fine. However, very recently, I used a combination of decorators and aspects to add another degree of configurability to a domain model.

Decorators, by design, should be lightweight objects which can be attached to the skin of other domain objects to add to the roles and responsibilities of the latter. They are added and/or removed dynamically from original objects - hence we say that decorators offer an added level of flexibility over static inheritance. Consider the following aggregate, which is modeled as a Composite :

Here is the base abstraction for the Composite, which models the Salary components of an employee :


public abstract class Salary {
  public Salary add(final Salary component) {
    throw new UnsupportedOperationException("not supported at this level");
  }

  public Salary remove(final Salary component) {
    throw new UnsupportedOperationException("not supported at this level");
  }

  public Salary getChild(final int i) {
    throw new UnsupportedOperationException("not supported at this level");
  }

  public abstract double calculate();
}



and here is the composite ..


public class SalaryComposite extends Salary {
  private List<Salary> components = new ArrayList<Salary>();

  @Override
  public Salary add(Salary component) {
    components.add(component);
    return this;
  }

  @Override
  public double calculate() {
    double total = 0;
    for(Salary salary : components) {
      total += salary.calculate();
    }
    return total;
  }

  @Override
  public Salary getChild(int i) {
    return components.get(i);
  }

  @Override
  public Salary remove(Salary component) {
    components.remove(component);
    return this;
  }
}



and a couple of leaf level components ..


public class BasicPay extends Salary {
  private double basicPay;

  public BasicPay(double basicPay) {
    this.basicPay = basicPay;
  }

  @Override
  public double calculate() {
    return basicPay;
  }
}

public class HouseRentAllowance extends Salary {
  private double amount;

  public HouseRentAllowance(double amount) {
    this.amount = amount;
  }

  @Override
  public double calculate() {
    return amount;
  }
}



Decorators and Composites - a Marriage

Decorators work nicely with Composites, and I have some decorators to allow users add behaviors to the Composite elements dynamically. In order to have decorators for the Composite, I need to have the Decorator share the base class with the Composite.


public abstract class SalaryDecorator extends Salary {
  private Salary component;

  public SalaryDecorator(final Salary component) {
    this.component = component;
  }

  @Override
  public double calculate() {
    return component.calculate();
  }
}



and a couple of concrete decorators ..

a senior citizen adjustment which can be applied to some of the components of the salary ..


public class SeniorCitizenAdjustment extends SalaryDecorator {
  protected double adjustmentFactor;
  public SeniorCitizenAdjustment(final Salary component, double adjustmentFactor) {
    super(component);
    this.adjustmentFactor = adjustmentFactor;
  }

  @Override
  public double calculate() {
    //.. calculation of absolute amount based on adjustmentFactor
    //.. complex logic
    adjustment = ...
    return super.calculate() + adjustment;
  }
}



and a city compensatory allowance which varies based on the city where the employee leaves ..


public class CityCompensatoryAdjustment extends SalaryDecorator {
  private double adjustmentFactor;

  public CityCompensatoryAdjustment(final Salary component, double adjustmentFactor) {
    super(component);
    this.adjustmentFactor = adjustmentFactor;
  }

  @Override
  public double calculate() {
    //.. calculation of absolute amount based on adjustmentFactor
    //.. complex logic
    adjustment = ...
    return super.calculate() + adjustment;
  }
}



Now my clients can make use of the Composite and decorate them using the decorators designed to compose the individual salary components ..


//..
Salary s = new SalaryComposite();
s.add(new SeniorCitizenAdjustment(new BasicPay(1000), 100))
 .add(new CityCompensatoryAdjustment(new HouseRentAllowance(300), 150));
//..



In the above example, the use of decorators provide the flexibility that the particular design pattern promises and allows instance level customization of individual components of the composite.

Decorating the Decorators

How do you handle fine grained variations within decorators ? In our case many of the decorators had fine grained variations within themselves, which calls for either inheritance hierarchies between them or some other way to decorate the decorators (super-decorators ?). The problem with static inheritance hierarchies is well-known. They have to be defined during compile time and the codebase for each deployment need to have the explosion of all possible variations modeled as subclasses. Looks like the problem that decorators are supposed to solve and that which we have already solved for the Composite Salary aggregate. Now we have the same problem for the decorators themselves.

We rejected the idea of static inheritance hierarchies and decided to model the variations as yet another level of decorators. We did not want to make the original decorators too complex and focused on making small composable, additive abstractions that can be tagged on transparently onto the domain objects and decorators alike.

e.g. in an implementation we found that the CityCompensatoryAdjustment has another fine grained variation. When CityCompensatoryAdjustment is applied, we have the additional variation that states that if the employee's residence is in one of a group of premium cities, then he is eligible for an additional premium allowance on top of the normal CityCompensatoryAdjustment. We rejected the idea of changing CityCompensatoryAdjustment to incorporate the new variations for 2 reasons :

  1. Trying to incorporate too much logic into decorators will make them complicated, which defeats the basic design motivation for the decorators

  2. These variations change across deployments - hence it will be a bloat trying to incorporate everything into a single class


Modeling the new requirement as another decorator (PremiumCityAdjustment), we have the following usage of the above snippet ..


//..
Salary s = new SalaryComposite();
s.add(new SeniorCitizenAdjustment(new BasicPay(1000), 100))
 .add(new PremiumCityAdjustment(
          new CityCompensatoryAdjustment(
              new HouseRentAllowance(300), 150),
          adjustmentFactor));
//..



But the Variations are Deployment specific!

We cannot change the main codebase with the above fine-grained variations, since they are deployment specific. We need to find out ways to externalize the invocation of these decorators. We found out that these finer variations are policies that need to change the behavior of the original decorators whenever they are applied. In the above example, every invocation of CityCompensatoryAdjustment needs to be decorated with PremiumCityAdjustment.


public class PremiumCityAdjustment extends SalaryDecorator {
  public PremiumCityAdjustment(final Salary component) {
    super(component);
  }

  @Override
  public double calculate() {
    //.. calculation of adjustment amount
    adjustment = ..
    return super.calculate() + adjustment;
  }
}



Aspects again !

We decided to implement the finer grained variations as decorators which would be applied through aspects to the original decorators. This is the base aspect for all such SuperDecorators ..


public abstract aspect SalarySuperDecoratorBase<extends SalaryDecorator> {

  pointcut decoratorCalculate(T s) : target(s)
        && execution(double T.calculate());

  abstract pointcut myAdvice();
}



and here is the implementation for adding PremiumCityAdjustment as a decorator for CityCompensatoryAdjustment ..


public aspect SuperDecoratorCityCompensatoryAdjustment
    extends SalarySuperDecoratorBase<CityCompensatoryAdjustment> {

  pointcut myAdvice(): adviceexecution()
    && within(SuperDecoratorCityCompensatoryAdjustment);

  Object around(CityCompensatoryAdjustment s) : decoratorCalculate(s) && !cflow(myAdvice()) {
    return new PremiumCityAdjustment(s).calculate();
  }
}



This way we had a set of core decorators for the main domain model and another set of deployment specific decorators which decorated the core ones through aspect weaving. By adopting this strategy we were able to keep the decorators lightweight, avoided deep inheritance hierarchies and managed to keep customizable code base completely separate from the core one.

1 comment:

Anonymous said...

情趣用品,情趣用品,情趣用品,情趣用品,情趣,情趣,情趣,情趣,按摩棒,震動按摩棒,微調按摩棒,情趣按摩棒,逼真按摩棒,G點,跳蛋,跳蛋,跳蛋,性感內衣,飛機杯,充氣娃娃,情趣娃娃,角色扮演,性感睡衣,SM,潤滑液,威而柔,香水,精油,芳香精油,自慰套,自慰,性感吊帶襪,吊帶襪,情趣用品加盟AIO交友愛情館,情人歡愉用品,美女視訊,情色交友,視訊交友,辣妹視訊,美女交友,嘟嘟成人網,成人網站,A片,A片下載,免費A片,免費A片下載愛情公寓,情色,舊情人,情色貼圖,情色文學,情色交友,色情聊天室,色情小說,一葉情貼圖片區,情色小說,色情,色情遊戲,情色視訊,情色電影,aio交友愛情館,色情a片,一夜情,辣妹視訊,視訊聊天室,免費視訊聊天,免費視訊,視訊,視訊美女,美女視訊,視訊交友,視訊聊天,免費視訊聊天室,情人視訊網,影音視訊聊天室,視訊交友90739,成人影片,成人交友,美女交友,微風成人,嘟嘟成人網,成人貼圖,成人電影,A片,豆豆聊天室,聊天室,UT聊天室,尋夢園聊天室,男同志聊天室,UT男同志聊天室,聊天室尋夢園,080聊天室,080苗栗人聊天室,6K聊天室,女同志聊天室,小高聊天室,上班族聊天室,080中部人聊天室,同志聊天室,聊天室交友,中部人聊天室,成人聊天室,一夜情聊天室,情色聊天室,寄情築園小遊戲情境坊歡愉用品,情境坊歡愉用品,情趣用品,成人網站,情人節禮物,情人節,AIO交友愛情館,情色,情色貼圖,情色文學,情色交友,色情聊天室,色情小說,七夕情人節,色情,情色電影,色情網站,辣妹視訊,視訊聊天室,情色視訊,免費視訊聊天,美女視訊,視訊美女,美女交友,美女,情色交友,成人交友,自拍,本土自拍,情人視訊網,視訊交友90739,生日禮物,情色論壇,正妹牆,免費A片下載,AV女優,成人影片,色情A片,成人論壇,情趣,免費成人影片,成人電影,成人影城,愛情公寓,成人影片,保險套,舊情人,微風成人,成人,成人遊戲,成人光碟,色情遊戲,跳蛋,按摩棒,一夜情,男同志聊天室,肛交,口交,性交,援交,免費視訊交友,視訊交友,一葉情貼圖片區,性愛,視訊,視訊聊天,A片,A片下載,免費A片,嘟嘟成人網,寄情築園小遊戲,女同志聊天室,免費視訊聊天室,一夜情聊天室,聊天室