Ayende @ Rahien

It's a girl

Limit your abstractions: Application Events–Proposed Solution #2–Cohesion

In my previous post, I spoke about ISP and how we can replace the following code with something that is easier to follow:

image_thumb3_thumb

I proposed something like:

public interface IHappenOn<T>
{
   void Inspect(T item);
}

Which would be invoked using:

container.ExecuteAll<IHappenOn<Cargo>>(i=>i.Inspect(cargo));

Or something like that.

Which lead us to the following code:

public class CargoArrived : IHappenedOn<Cargo>
{
  public void Inspect(Cargo cargo)
  {
    if(cargo.Delivery.UnloadedAtDestination == false)
      return;
      
    // handle event
  }
}

public class CargoMisdirected : IHappenedOn<Cargo>
{
  public void Inspect(Cargo cargo)
  {
    if(cargo.Delivery.Misdirected == false)
      return;
      
    // handle event
  }
}

public class CargoHandled : IHappenOn<HandlingEvent>
{
   // etc
}

public class EventRegistrationAttempt : IHappenedOn<HandlingEventRegistrationAttempt>
{
  // etc
}

But I don’t really like this code, to be perfectly frank. It seems to me like there isn’t really a good reason why CargoArrived and CargoMisdirected are located in different classes. It is likely that there is going to be a lot of commonalities between the different types of handling events on cargo. We might as well merge them together for now, giving us:

public class CargoHappened : IHappenedOn<Cargo>
{
  public void Inspect(Cargo cargo)
  {
    if(cargo.Delivery.UnloadedAtDestination)
      CargoArrived(cargo);
      
    
    if(cargo.Delivery.Misdirected)
      CargoMisdirected(cargo);
      
  }
  
  public void CargoArrived(Cargo cargo)
  {
    // handle event
  }
  
  public void CargoMisdirected(Cargo cargo)
  {
    //handle event
  }
}

This code put a lot of the cargo handling in one place, making it easier to follow and understand. At the same time, the architecture gives us the option to split it to different classes at any time. We aren’t going to end up with a God class for Cargo handling. But as long as it make sense, we can keep them together.

I like this style of event processing, but we can probably do better job at if if we actually used event processing semantics here. I’ll discuss that in my next post.

Comments

Mark
02/09/2012 03:17 PM by
Mark

Much easier to understand! What we need to remember is while some of us may be rock stars when it comes to coding, junior developers (or noobs) are not. As much as you "want"/"push" them to learn these higher level concepts it doesn't happen. Keeping code simple goes a long way in my opinion.

Sony Mathew
02/09/2012 03:35 PM by
Sony Mathew

Tried to post this on your previous post but looks like it didn't succeed.

IHappenOn amounts to the mediated Event/Listener or Observer pattern e.g. Observer or Listener or even Processor mediated by a central Mediator. Unfortunately in Java (not sure about C#) using Generics gets in the way because implementations generally implement multiple Listener and Java Type erasure will cause a compile failure. Hence the approach I took with Event classes inlining their Mediator, Listener and optionally Processor interfaces as shown in my pasted code from a few posts ago:

class CargoDeliveryStatusEvent extends Event {
  interface Mediator {  
    void fire(CargoDeliveryStatusEvent e);
    void register(Listener l);
  }
  
  interface Listener {
    void handle(CargoDeliveryStatusEvent e);
  }
  
  CargoDeliveryStatusEvent(Cargo.DeliveryStatus deliveryStatus) {
    ...
  }
}
Sony Mathew
02/09/2012 03:44 PM by
Sony Mathew

Argh...my generics where stripped out..

shawn
02/09/2012 06:42 PM by
shawn

I'd be curious to see container.ExecuteAll, unless this is a new feature in Windsor 3 that I've totally overlooked somehow or something like that. This precise scenario is something that comes up a lot in my apps and I am always curious to see how different people implement it.

Ayende Rahien
02/09/2012 06:48 PM by
Ayende Rahien

Shawn, It is an extension method:

public static void ExecuteAll{T}(this IKernel kernel, Action{T} action) { foreach(var item in kernel.ResolveAll{T}) { action(item); } }

Stephen Shen
02/10/2012 02:29 AM by
Stephen Shen

This is very cool. I like the extension method. I am wondering do we really need generic T abstraction in the IHappenedOn?

Will this interface be used to inspect another type other than Cargo? Say yes if in another context, shall we do another specific interface eg IHappendOnCargo, & IHappendedOnApple, vs IHappenedOn for Cargo, and Apple?

Shall we limit this sort of abstraction?

llyys
02/15/2012 10:24 PM by
llyys

Why add another event abstraction for executing simple business logic?

If you could just add some simple entity logic directly on to the entity. And invoke entity method "manually" by executing method when something happens.

For example

class Cargo{ public virtual string DeliveryStatus {get;set;} public virtual Delivery Delivery {get;set;} ... #region business logic public void onCargoArrived(){ DeliveryStatus=DeliveryStatus.Ready; }

public void onCargoMisdirected(){ DeliveryStatus=DeliveryStatus.Misdirected; CargoRecievedDate=null;
}

}

Ayende Rahien
02/15/2012 09:36 PM by
Ayende Rahien

Ilyys, who calls those methods?

Comments have been closed on this topic.