Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design pattern that can replace chained switch/goto?

People also ask

What is chain of responsibility design pattern C#?

Chain of Responsibility is behavioral design pattern that allows passing request along the chain of potential handlers until one of them handles request. The pattern allows multiple objects to handle the request without coupling sender class to the concrete classes of the receivers.


Well, if we want to be "object oriented", why not let the objects-do-the-talking?

var updates = availableUpdates.Where(u => u.version > ver).OrderBy(u => u.version);
foreach (var update in updates) {
  update.apply();
}

In the example the version is increasing and always calling the earlier ones in sequence. I think that a set of if statements is probably more appropriate here

if (version == 1001 ) { 
  updateTo1002();
}

if (version <= 1002) {
  updateTo1003();
}

if (version <= 1003) {
  updateTo1004(); 
}

if (version <= 1004) {
  updateTo1005();
}

Some have commented that this approach is unmaintainable as the number of versions gets higher (think 50 or so). In that case here is an easier to maintain version

private List<Tuple<int, Action>> m_upgradeList;

public Program()
{
    m_upgradeList = new List<Tuple<int, Action>> {
        Tuple.Create(1001, new Action(updateTo1002)),
        Tuple.Create(1002, new Action(updateTo1003)),
        Tuple.Create(1003, new Action(updateTo1004)),
        Tuple.Create(1004, new Action(updateTo1005)),
    };
}

public void Upgrade(int version)
{
    foreach (var tuple in m_upgradeList)
    {
        if (version <= tuple.Item1)
        {
            tuple.Item2();
        }
    }
}

I hate blank statements that don't provide supporting information, but goto is fairly universally panned (for good reason) and there are better ways to achieve the same results. You could try the Chain of Responsibility pattern that will achieve the same results without the "spaghetti-ish" goo that a goto implementation can turn into.

Chain of Responsibility pattern.


goto is always considered as bad practice. If you use goto it is usually harder to read code and you always can write your code differently.

For example you could used linked list to create a chain of methods and some processor class that processes the chain. (See pst's answer for good example.). It is much more object oriented and maintainable. Or what if you have to add one more method call beetween 1003 and case 1004?

And of course see this question.

alt text


I would suggest a variation of the command pattern, with each command being self-validating:

interface IUpgradeCommand<TApp>()
{
    bool UpgradeApplies(TApp app);
    void ApplyUpgrade(TApp app);
}

class UpgradeTo1002 : IUpgradeCommand<App>
{
    bool UpgradeApplies(App app) { return app.Version < 1002; }

    void ApplyUpgrade(App app) {
        // ...
        app.Version = 1002;
    }
}

class App
{
    public int Version { get; set; }

    IUpgradeCommand<App>[] upgrades = new[] {
        new UpgradeTo1001(),
        new UpgradeTo1002(),
        new UpgradeTo1003(),
    }

    void Upgrade()
    {
        foreach(var u in upgrades)
            if(u.UpgradeApplies(this))
                u.ApplyUpgrade(this);
    }
}

why not:

int version = 1001;

upgrade(int from_version){
  switch (from_version){
    case 1000:
      upgrade_1000();
      break;
    case 1001:
      upgrade_1001();
      break;
    .
    .
    .
    .
    case 4232:
      upgrade_4232();
      break;
  }
  version++;
  upgrade(version);
 }

Sure, all this recursion creates overhead, but not all that much (with a call to the carbage collector only a context and an int), and it's all packaged up to go.

Note, I don't mind the goto's much here, and the tuple (int:action) variations have their merits too.

EDIT:

For those who don't like recursion:

int version = 1001;
int LAST_VERSION = 4233;

While (version < LAST_VERSION){
  upgrade(version);
  version++;
}

upgrade(int from_version){
  switch (from_version){
    case 1000:
      upgrade_1000();
      break;
    case 1001:
      upgrade_1001();
      break;
    .
    .
    .
    .
    case 4232:
      upgrade_4232();
      break;
  }

}