It's not just a Gear, it is a Clock!

Not just a Gear, a Clock!

“A deep model contains the central concepts and abstractions that can succinctly and flexibly express essential knowledge of the users’ activities, their problems and their solutions.”

“Ultimately … this should make the software more in tune with the way the domain experts think and more responsive to the users’ needs”

Domain-Driven Design, by Eric Evans

Domain-Driven Design situates the domain model in a central and convergent role, tightly articulating semantics and syntax for problem definition, solution design and software implementation.

In this post, we bring the domain semantics to our refactoring process. We want a deeper perspective to analyse modularity problems and to direct improvements towards greater relevance to our design objectives. So, let’s resume the Fowler’s Video Store refactoring and explore alternative design decisions based on greater emphasis on model semantics.

Fowler’s Video Store didactic program has two main functions: rental calculations and customer statement printing. This post focuses on the domain concepts that are most relevant to the rental calculations. Next posts will explore additional design alternatives for rental calculations and customer statement printing.

Initial Rental Calculations Design

Rental calculations include charge and frequent renter points. Both calculations are a function of the rental period and whether the video is either a new release, for children or regular.

The initial program defines a price code attribute for each video; this information is used to select the right calculation formulas embedded in switch programming statements inside the customer statement method. The complexity of such design grows as new price codes are added. It becomes specially dangerous if the statement method is duplicated to implement the HTML format.

Refactoring with Inheritance and Polymorphism

As we saw previously, the refactoring book evolves this design solution first by extracting the calculations to the methods getCharge and getPoints and moving them to the Video class.  This eliminates the need of duplicating the formulas when creating a new statement method.

The next step is to isolate the formulas from the Video class, replacing the price code with a new Price class. This class is then further specialised into the NewRelease, Children or Regular subclasses, so that the price code can be replaced by a polymorphic Price reference to an object of the respective subclass.

The following figure shows the design solution adopted by the refactoring book.

Video Store - Price Polymorphism

Video Store - Price Specialisation

The recommended refactoring improves modularity by using inheritance and polymorphism to adopt a typical object oriented syntax, that implements the strategy design pattern. The refactoring steps end up constituting the Replace Conditional Logic with Strategy refactoring pattern.

It looks like the right way to go. Doesn’t it? Well, let’s explore a design alternative.

Finding Relevant Implicit Concepts

The rental model for prices and terms is implicitly codified in the following formulas scattered along the Price class hierarchy implementation:

regularCharge = 2.0;
if (daysRented > 2) regularPrice += (daysRented - 2) * 1.5;

childrensCharge = 1.5;
if (daysRented > 3) childrensCharge += (daysRented - 3) * 1.5;

newReleaseCharge = daysRented * 3;

If we look closely enough at these formulas, we discover they are actually three instances of the same formula:

charge = initialPrice;
if (daysRented > initialTerm)
    charge += (daysRented - initialTerm) * extraPricePerDay;

The implicit concepts of initial term, initial price and extra price per day have just emerged as relevant to our design! They define a clearer, explicit, flexible and unified pricing model, replacing three static functions by a single meaningfully parameterised function. The same is true for the frequent renter points calculations. The emerged concepts can be articulated as rental conditions.

Naturally, all relevant domain abstractions must be institutionalised as part of the language effectively practised in the business and expressed in the software. Therefore, emerging concepts must be presented, discussed, reviewed where necessary and validated with the business domain experts.

Making the Relevant Concepts Explicit in the Design

As part of this this convergence process, we propose to explicitly define the RentalConditions value object as a substitute for the Price class hierarchy. The parameters initial term, initial price, extra price and extra points are invariant for regular, children’s and new releases. Therefore they can be defined at construction time:

RentalConditions regular    = new RentalConditions(2, 2.0, 1.5, 0);
RentalConditions childrens  = new RentalConditions(3, 1.5, 1.5, 0);
RentalConditions newRelease = new RentalConditions(1, 3.0, 3.0, 1);

The parameter daysRented is defined at calling time for the functions getCharge and getPoints.

public class RentalConditions {

  private final int    _initialTerm;
  private final double _initialPrice;
  private final double _extraPricePerDay;
  private final int    _INITIAL_POINTS = 1;
  private final int    _extraPoints;

  public RentalConditions (int initialTerm, double initialPrice,
                           double extraPricePerDay, int extraPoints) {

      _initialTerm      = initialTerm;
      _initialPrice     = initialPrice;
      _extraPricePerDay = extraPricePerDay;
      _extraPoints      = extraPoints;
  }

  public double getCharge (int daysRented) {

      double charge = _initialPrice;
      if (daysRented > _initialTerm)
          charge += (daysRented - _initialTerm) * _extraPricePerDay;
      return (charge);
  }

  public int getPoints (int daysRented) {

      int points = _INITIAL_POINTS;
      if (daysRented > _initialTerm)
          points += _extraPoints;
      return (points);
  }
}

After all changes the new domain model implementation is:

Video Store - Rental Conditions

Video Store - Rental Conditions

Conclusions

Refactoring at the semantic level improves the matching between business, model and implementation, adds flexibility where needed and addresses the major stakeholders’ concerns. As usual, we should always listen the domain experts, strive for simplicity and use a maintenance need as an opportunity for refactoring.

The initial program calculates rental charges and frequent renter points as conditional expressions. Standard object oriented design practice focuses on the conditions to specialise the expressions and implement the strategy design pattern for each calculation. This post focuses instead on the expressions themselves to identify the relevant domain concepts that generalise the expressions.

Bringing domain semantics to the process enabled us to propose a Extract Value Object refactoring, as an alternative to the Replace Conditional Logic with Strategy refactoring, with the following benefits:

  • decoupling: duplication in the multiple formulas is eliminated;
  • expressiveness: changes can be more clearly and easily applied;
  • simplicity: the program’s design structure is simplified.

Programs that instantiate an Anaemic Domain Model can be greatly improved by simple and straightforward refactoring aimed at adopting proper object oriented syntax. Additional improvements, however, require deeper thinking and carefully considering the domain model semantics in order to select the course of action from multiple design options.

Picture by Readerwalker’s photo stream. Original at Flickr.

7 Responses to “Revisiting Fowler’s Video Store: Making Implicit Concepts Explicit”

Comments (5) Pingbacks (2)
  1. Hi

    Nice post.
    I generally agree with you and I know that this is a simple example and that the context is very important when coming up with alternate designs.

    There are a few issues with your implementation though (and admittedly with the initial implementation).

    1. The RentalConditions have tied together 2 concepts (price and renter points) which most likely have different rates of change. (Kent Beck talks about this in his book Implementation Patterns).
    2. The concept of ‘movie classification’ is not explicit in both versions which plays a role in deciding what the price is.

    An alternative design could be a double dispatch model (as the business rules will most likely not sit with just one object).
    A movie could have a classification and the price determined by the rental type and the movie classification. There may be a monthly rental contracts, yearly etc which would affect pricing.

    Yes admittedly the requirements are not there but this is just an example to show that the new design does not really exhibit any real benefit over the first. They are both pretty easy to understand and change although your design is more parametrized which ties more closely to open-closed principle.

    So for completeness, an example of the double dispatch design which would be suitable say if the decision of price varies over more than one type of object i.e. by movie type and rental type…(in Smalltalk)

    rental price.

    Rental>>price
    ^aMovie classification priceForRental: self.

    Classification>>priceFor: rentalDetails
    self subclassResponsibility.

    NewReleaseMovie>>priceFor: rentalDetails
    ^rentalDetails priceForNewRelease.

    ChildrenMovie>>priceFor: rentalDetails
    ^rentalDetails priceForChildrenMovie.

    RegularMovie>>priceFor: rentalDetails
    ^rentalDetails priceForRegularMovie.

    Good article though and I agree with your sentiments behind DDD.

  2. Hi, Carlo

    Thanks for your comments. They helped me clarify my article.

    Now my specific comments about your comments:

    –> “1. The RentalConditions have tied together 2 concepts (price and renter points) which most likely have different rates of change.”

    The RentalConditions are (immutable) value objects. They never change, they are just replaced by new ones whenever necessary. Therefore, the also called Single Responsibility Principle doesn’t apply here.

    –> “2. The concept of ‘movie classification’ is not explicit in both versions which plays a role in deciding what the price is.”

    You are right. We will focus our attention on this relevant concept in the next post.

    –> “the new design does not really exhibit any real benefit over the first”

    The benefits from the alternative design are stated in the Conclusions.

    Greetings,
    Rafael

  3. Hi

    With point 1 what I meant was that, and again this comes down to YAGNI and what the current requirements state, ideally these 2 concepts would not be tied together.
    For example imagine the charge algorithm changes (e.g. takes into consideration how many movies are already out) then the RentalConditions class will be modified and although the code for renter points is not modified it needs to be re-tested even though it only depends on initialTerm and extraPoints. Ideally these 2 concern would be separated.

    Regarding the alternative design benefit comment, I know what you are trying to say but for this (deliberately) narrow design example its not obvious to see the benefits. One could argue that the first design is easier to change as although the current requirements make the algorithm be duplicated in multiple places, a future requirement which changes in a different way (not using the parameters int the RentalConditions class i.e. initialTerm etc) for only one ‘type’ of movie (say NewReleases). The NewReleases class changes and the others are left alone.

    With the alternative design we would have to either subclass the Value Object and use a Factory to build the correct ‘version’ of the RentalConditions class.

    Again I look forward to your next article.
    Cheers

  4. Hi, Carlo

    Thanks again for the comments.

    The price and frequent points concerns are already separated by distinct value object functions. When necessary they can be further isolated to distinct value object classes.

    We should always be able to adapt the domain model in order to explicitly reproduce the new business reasoning.

    If the calculation rules change fundamentally, I would first try to preserve the current solution benefits, by defining new corresponding unified and meaningfully parameterised functions.

    Only if the first approach proves inviable, I would consider a class hierarchy and the strategy design pattern.

    Cheers,
    Rafael

  5. @”needs to be re-tested even though it only depends on”

    eh, programming is risky, so i would never say less needs to be tested, so i don’t see that as such a solid argument.

    however, more ‘atomic’ classes will make things like “who changed this and why?” studies easier in our version control system, and will make it easier to reuse such classes in other contexts perhaps. (the latter going for or against yagni depending on your interpretation of it.)

    sincerely.

Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Copyright © 2010 The Sympriser Blog Suffusion theme by Sayontan Sinha