views:

77

answers:

4

I have some simple scheduling of various events in my application and I used this class hierarchy:


abstract class ScheduledEvent
{
    public DateTime When{ get; set; }

    public abstract void Trigger();
}

class WhateverEvent : ScheduledEvent
{
  public override void Trigger();
}

class AnotherEvent : ScheduledEvent
{
  public override void Trigger();
}

class Scheduler
{
  public AddEvent(ScheduledEvent event)
}

My application works fine but I don't like the design because whenever I want to add a new event I have to add a new descendant to the hierarchy and all books say prefer composition over inheritance so I tried this approach:


interface ITriggerable
{
    void Trigger();
}

class WhateverEvent : ITriggerable
{
  public void Trigger();
}

abstract class ScheduledEvent
{
    private ITriggerable triggerable;
    public ScheduledEvent(ITriggerable t) { 
        triggerable = t; 
    }
    public DateTime When{ get; set; }

    public void Trigger() { triggerable.Trigger(); }
}

But problem occurred immediately. For every type of event there was a form for editing of its properties like this:


class WhateverForm : Form
{
    WhateveverForm(WhateverEvent ev) { ... }
}

This form had available all properties of WhateverEvent and also property When of ScheduledEvent. With my new hierarchy this property is not available anymore. I can of course add reference to ScheduledEvent to WhateverEvent but I don't like it.

My question is how would you design your class hierarchy and why? It may seem trivial for you but I don't have much real experience with design and I want to do it right.

A: 

I won't know what's the best for you, but a good design needs to provide encapsulation while balance the amount of cohesion and coupling. I wouldn't follow the any rule which favours composition over inheritance too closely. There are reasons for either design decision.

Andrew Keith
A: 

You can't tell with just random names like this. Subclasses should follow the 'is a' rule: say Truck is a subclass of Vehicle, then a truck should be a vehicle.

But don't get too carried away, either - think about code reuse most of all, and don't just create a deep hierarchy because you can, only create it if it will lead to simpler code, better encapsulation and more code reuse.

Peter
+1  A: 

I think the old design using inheritance is perfectly fine, and less complicated. You're using polymorphism the way it is supposed to be used.

My interpretation of "prefer composition over inheritance" is (roughly) that if you don't need polymorphic behavior, then you should use composition instead of inheritance.

With the ITriggerable approach, you're not solving the problem of having to write a subclass for every new event. You're just using an interface instead of a base class. It's not technically inheritance, but it's pretty much the same thing.

Tom Dalling
A: 

The advice to prefer composition over inheritance concerns how one should approach achieving reuse. Composition provides both the ability to reuse data and/or behavior across different class hierarchies as well as the ability to substitute the particular behavior of a type at run time. Composition doesn't eliminate the need to create new types for different behavior.

Concerning a model for scheduling, the approach I've taken in the past was to separate the concepts of task, schedule, schedule monitor, and scheduling service. In my case, I needed schedule to encapsulate more than just a fixed point in time, so it contained additional properties for specifying recurrence interval, recurrence count, etc. I did not encapsulate the schedule within the task because tasks aren't something that are inherently concerned with when they occur.For example, an oil change is a task while every 3 months is a schedule which may be applied to oil changes as well as other maintenance tasks. By separating schedule from task, a single monitor could be maintained for multiple tasks which happened to share the same schedule.

Derek Greer