You can code it, I can help!

Snapping Strategy Part II

New inspiration

One of many great things about blogging is that you get feedback from other developers. My good friend Sebastian (@paraseba) suggested to use a Chain-of-Responsibility instead of a Composite to find the an active snapping strategy. If u have no idea what I'm talking about, please read the previous post about "Implementing a Snapping Strategy".

CoR Pattern

In the Design Patterns book, the authors describe the Chain of Responsibility pattern like this:
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.
In our case this means that we should give the opportunity to each strategy to do the snapping and if no snapping is available should call the “chained” strategy to see if the point should be snapped. The solution it's working, good time to refactor (Red, Green, Refactor) to add more flexibility to our strategies. Let’s explore what changes do we need to do in order to be able to chain the strategies.

Chained Strategies

I don’t think we should modify the interface, no one needs to know which pattern are we implemented, so I am going to create a new abstract class called ChainedSnappingStrategy. This class will be the base implementation for all the other strategies with two responsibilities:
  • Call the next strategy in the chain if no snapping was applied
  • If it’s the end of the chain, return the same point.
Our hierarchy updated will look something like this: image I added a new abstract method called SnapImpl to implement in each concrete class the snapping (same as we have now). The Snap method should call the implementation first and then check if the point was snapped or not, if not should go to the next strategy. Here is the code:
   1: public abstract class ChainedSnappingStrategy : ISnappingStrategy
   2: {
   3:     public ISnappingStrategy Next { get; set; }
   5:     public Point Snap(Point point, IDrawingContext context)
   6:     {
   7:         var result = this.SnapImpl(point, context);
   9:         if (this.Next != null && result == point)
  10:         {
  11:             result = this.Next.Snap(point, context);
  12:         }
  14:         return result;
  15:     }
  17:     protected abstract Point SnapImpl(Point point, IDrawingContext context);
  18: }
And voila, our strategies are chained!