13

This is a bit of an odd oop question. I want to create a set of objects (known at design time) that each have certain functions associated with them. I can either do this by giving my objects properties that can contain 'delegates':

public class StateTransition {
    Func<bool> Condition { get; set; }
    Action ActionToTake { get; set; }
    Func<bool> VerifyActionWorked { get; set; }
}

StateTransition foo = new StateTransition {
    Condition = () => {//...}
    // etc
};

Alternatively I can use an abstract class and implement this for each object I want to create:

public abstract class StateTransition {
    public abstract bool Condition();
    public abstract void ActionToTake();
    public abstract bool VerifyActionWorked();
}

class Foo : StateTransition {
    public override bool Condition() {//...}
    // etc
}

Foo f = new Foo();

I realise the practical consequences (creating at design time vs run time) of these two methods are quite different.

How can I choose which method is appropriate for my application?

umlcat
  • 4,091
  • 3
  • 19
  • 29
Flash
  • 15,945
  • 13
  • 70
  • 98

7 Answers7

6

The first approach looks more suited to events than raw delegates, but... whatever.

The key factor between them is: who controls what happens?

If the caller can legitimately do anything there, then the event approach would be fine. The system doesn't force you to subclass a Button just to add what happens when you click it, after all (although you can do it that way).

If the "things that can happen" are pretty controlled, and you wouldn't want every caller doing different things, then a sub-class approach is more suitable. This also avoids the need for every caller to have to tell it what to do, when the "things to do" might actually be a very small number of options. The base-type approach also gives the ability to control the subclasses, for example by only having an internal contructor on the base-class (so that only types in the same assembly, or in assemblies noted via [InternalsVisibleTo(...)], can subclass it).

You could also combine the two (override vs event) via:

public class StateTransition {
    public event Func<bool> Condition;
    protected virtual bool OnCondition() {
        var handler = Condition;
        return handler == null ? false : handler();
    }
    public event Action ActionToTake;
    protected virtual void OnActionToTake() {
        var handler = ActionToTake;
        if(handler != null) handler();
    }
    public event Func<bool> VerifyActionWorked;
    protected virtual bool OnVerifyActionWorked() {
        var handler = VerifyActionWorked;
        return handler == null ? true : handler();
    }
    // TODO: think about default return values
}

Another thing to consider with the delegate/event approach is: what do you do if the delegate is null ? If you need all 3, then demanding all 3 in a constructor would be a good idea.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • It seems that if I want to create a `StateTransition` inline (anonymously, without naming every single one) I must use delegates. In Java I could declare an anonymous inner class to implement my interface (since Java doesn't have delegates), but there is no such thing in C#. The interface approach would lead to hundreds of useless lines of `class Foo : StateTransition` cruft. Is this reason enough to use the delegate approach? – Flash Jan 11 '13 at 06:16
  • @Andrew meh... the amount of code shouldn't change much there – Marc Gravell Jan 11 '13 at 07:41
2

The delegate solution would be useful if you:

  • want to create objects dynamically, i.e. choosing implemenation for each method depending on some conditions.
  • want to change the implementation during the life time of the object.

For other cases I would recommend the object oriented approach.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
1

How can I choose which method is appropriate for my application?

Does your application requires to define new transition objects that have additional different properties, or additional different methods ? Then making new subclasses, overrding methods, ("Polymorphism") is better.

Or.

Does your application requires to define transition objects that only change method behaviour ? Then Method Delegates or Methods Events are better.

Summary

Overriding Methods ("Polymorphism") is better when your application requires to add different features, like properties or methods, for different subclasses, not just changing the implementation of methods.

umlcat
  • 4,091
  • 3
  • 19
  • 29
0

Provided that this is more an opinion than a rock-solid answer...

I think that the two would be more or less equivalent if you had just one delegate or abstract/virtual method. After all, you can think of a delegate as a handy shortcut to avoid creating an implementing an interface for just one method.

In this case, where you have three methods, the base class approach would be the most practical.

To make the two things completely equivalent, you could use a base non-abstract class, with empty virtual methods, so that if a derived class does not override it, it's the same as having a null delegate property.

Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193
0

If you are saying that your objects known at design time, then it sounds like you do not need to alter behavior of objects dynamically (at run time). So I think there is no reason for you to to use 'delegate' approach.

SergeyS
  • 3,515
  • 18
  • 27
0
  • Solution 1 has more moving parts, which allows for finer-grained separation of concerns. You could have one object decide what the Condition for a given StateTransition should be, another object define the ActionToTake, and so on. Or you could have one object decide them all, but based on different criteria. Not the most useful approach most of the time IMO - especially considering the slight additional complexity cost.

  • In solution 2, each StateTransition derivative is a cohesive whole, the way it checks the condition cannot be separated from the way it does the action or verifies it.

Both solutions can be used to accomplish Inversion of Control - in other words, they both allow you to say that StateTransition's direct consumer will not control which flavor of StateTransition it is going to use but that the decision will instead be delegated to an external object.

guillaume31
  • 13,738
  • 1
  • 32
  • 51
0

How can I choose which method is appropriate for my application?

Method with delegates makes code more functional because it moves from classic Object Oriented Programming techniques like inheritance and polymorphism to Functional Programming techniques like passing functions and using closures.

I tend to use method with delegates everywhere I can because

  1. I prefer composition over inheritance

  2. I find method with delegates to require less code to write

For example, a concrete StateTransition instance can be created in 5 lines of code from delegates and closures using standard .NET initialization mechanism :

dim PizzaTransition as new StateTransition with {
    .Condition = function() Pizza.Baked,
    .ActionToTake = sub() Chef.Move(Pizza, Plate),
    .VerifyActionWorked = function() Plate.Contains(Pizza)
}
  1. I find it easy to build Fluent API around a class with a set of additional methods implemented as Extension methods or inside the class.

For example, if methods Create, When, Do and Verify are added to the StateTransition class:

public class StateTransition
    public property Condition as func(of boolean)
    public property ActionToTake as Action
    public property VerifyActionWorked as func(of boolean)

    public shared function Create() as StateTransition
        return new StateTransition
    end function

    public function When(Condition as func(of boolean)) as StateTransition
        me.Condition = Condition
        return me
    end function

    public function Do(Action as Action) as StateTransition
        me.ActionToTake = Action
        return me
    end function

    public function Verify(Verify as func(of boolean)) as StateTransition
        me.VerifyActionWorked = Check
        return me
    end function
end class

Then method chaining can be also be used to create a concrete StateTransition instance:

dim PizzaTransition = StateTransition.Create.
    When(function() Pizza.Baked).
    Do(sub() Chef.Move(Pizza, Plate).
    Verify(function() Plate.Contains(Pizza))
Lightman
  • 1,078
  • 11
  • 22