Variablity! Variability!
Making the domain model flexible and generative is all about managing the variabilities within the model. The better you manage the variable components of the model, the more configurable, customizable and flexible it becomes. In the last post I talked about the configuration knowledge, which will act as the generator and do the plumbing for you. It is this variability that makes the most of your configuration knowledge. The aspects of your design, specific implementations, lifecycles, scoped instances, algorithms, strategies etc. are the aspects that you would like NOT to be hardwired within your Java / C++ codebase.
Strategically Speaking ..
Over the years, I have found many variants of the Strategy pattern implementation being used to manage variabilities within the model. GOF discusses two variations in C++ - one using runtime polymorphism and virtual functions, while the other using templates and compile time polymorphism. Each has its own merits and can be applied to solve specific problems in a specific context.
The following snippet shows how the Strategy Design Pattern can be applied through runtime polymorphism to externalize the domain process of accrual calculation ..
class AccrualCalculation {
private CalculationStrategy strategy;
// setter injection
public void setStrategy(CalculationStrategy strategy) {
this.strategy = strategy;
}
// delegate to strategy
public BigDecimal calculate(...) {
return strategy.calculate(...);
}
}
interface CalculationStrategy {
BigDecimal calculate(...);
}
class DefaultCalculationStrategy implements CalculationStrategy {
public BigDecimal calculate(...) {
// implementation
}
}
// externalize the variability through the configuration knowledge
<bean id="calcStrategy" class="com.x.y.DefaultCalculationStrategy"/>
<bean id="accrual" class="com.x.y.AccrualCalculation">
<property name="strategy">
<ref bean="calcStrategy"/>
</property>
</bean>
Compare this with the same pattern implemented using generics, where the class AccrualCalculation can be designed as being parameterized on the strategy.
class AccrualCalculation<S extends CalculationStrategy> {
private S strategy;
// delegate to strategy
public BigDecimal calculate(...) {
return strategy.calculate(...);
}
}
// usage
interest =
new AccrualCalculation<DefaultCalculationStrategy>().calculate(...);
Traits techniques, the else-if-then of types (ah! I love the name) have also been used extensively by the C++ community to manage variations at the type level. The traits classes and traits templates along with killer metaprogramming capabilities have been used to develop blazing applications in C++. Another variant of compile time strategy is the Policy Based Design of Alexandrescu. Policy classes, along with their capabilities of having an unbounded number of implementations, provide an ideal alternative for plugging in compile time strategies to the domain model.
The Strategy Design Pattern provides an ideal mechanism to encapsulate the coarse-grained variabilities of your domain model. Spruce up the pluggability of your model by externalizing the strategy implementation into an IoC container or any other generator (as the configuration knowledge).
Fine Grained Variability with Template Method
The Template Method Design Pattern is meant for plugging in fine-grained variabilities of your algorithm / strategy within the commonality framework. Use this pattern in your model if the macro level flow of your domain process is invariant, while there are customizable bits and pieces that need to be hooked and externalized. I have used this pattern with great success in modeling rule based big financial applications. The following is an example for implementing fine grained variability using the Template Method Design Pattern. Here, the overall accrual calculation algorithm is final, while there are some hooks that need to be plugged in by the derived classes. These hooks are kept as abstract methods in the base class.
abstract class AccrualCalculation {
public final BigDecimal calculate(...) {
// hook
int days = calculateAccruedDays(...);
if (days != 0) {
// invariant logic
} else {
// hook
boolean stat = isDebitAccrualAllowed(...);
if (stat) {
// invariant logic
}
// invariant logic
}
}
protected abstract int calculateAccruedDays(...);
protected abstract boolean isDebitAccrualAllowed(...);
}
Negative Variability
Jim Coplien first introduced this term in the design and analysis space with his work on Multiparadigm Design in C++. When some of the members of an otherwise common hierarchy violate a few of the base class assumptions, he calls this negative variability. Cope goes on to say
Negative variability violate rules of variation by attacking the underlying commonality - they are the exceptions to the rules.
There are quite a few techniques for implementing negative variability viz. template specialization in C++, conditional compilation etc. The Bridge Design Pattern is also known to address this problem very well. Coplien's Multiparadigm Design book details a lot of them - have a look at the deep insights of this guru. In real life, a domain model often displays this behavior and you need to take proper care through usage of appropriate patterns and idioms.
No comments:
Post a Comment