28

I have a code for updating my application resources to current application version. This code is called after application update.

int version = 1002;   // current app version

switch(version)
{
   case 1001:
      updateTo1002();
      goto case 1002;

   case 1002:
      updateTo1003();
      goto case 1003;

   case 1003:
      updateTo1004();
      goto case 1004;
      break;

   case 1004:
      updateTo1005();
      break;
}

Here we have a cascade method calling by jumping to specified case block. I wonder - is that good practice to use go to (often considered as such bad practise!) in this case? I do not want to call method one by other - like this:

updateTo1002()
{
   // do the job
   updateTo1003();
}
updateTo1003()
{
   // do the job
   updateTo1004();
}

It there any design pattern describes such an issue?

Merlyn Morgan-Graham
  • 58,163
  • 16
  • 128
  • 183
Przemysław Michalski
  • 9,627
  • 7
  • 31
  • 37
  • 14
    Argh! So many dead kittens! On a more serious note, why are you against chaining the method calls but gotos seem ok? I'm guessing this example doesn't reflect the true nature of your code... – Justin Niessner Oct 27 '10 at 19:37
  • Haven't used C#, but it doesn't allow you to do `case:` fallthroughs? – Nick T Oct 27 '10 at 21:08
  • 4
    @Nick C# doesn't allow case fallthroughs on purpose, as _most_ of the time a case fallthrough is a hard-to-spot bug and the few times it's actually desirable there are suitable alternatives. – Michael Stum Oct 27 '10 at 21:23
  • @Michael: Well, C# _could_ allow you to fall through explicitly. Unfortunately, it's also disallowing that. – sbi Oct 27 '10 at 21:26
  • 3
    It's actually kind of neat self-documenting and all, but you'll get no end of crap from people who can't see beyond the word "goto" that they learned in school was evil. My point? Even if you can find a very valid use for goto, it won't be worth the crap you'll take. – Bill K Oct 27 '10 at 21:42
  • @UGEEN Why aren't you updating straight to the newest version? – jasonh Oct 27 '10 at 22:14
  • @jasonh because I need to call methods sequential to avoid code repeats – Przemysław Michalski Oct 28 '10 at 06:49
  • take a look at my late answer – necromancer Oct 30 '11 at 01:58

13 Answers13

58

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();
}
  • 10
    Finally, an answer that is starting to make some sense! All this `if`, `switch` and `goto` non-sense is scaring me! – Dave White Oct 27 '10 at 20:03
  • 5
    Nitpick: this is really more functional than object-oriented. Which is *exactly* the right thing to do here, of course. – Jörg W Mittag Oct 28 '10 at 06:23
  • 2
    +1. I believe this is the best case I have ever seen for linq. Excellent. – Carl Manaster Oct 28 '10 at 17:51
  • 1
    checked in practice - works fine although is more condensed than previous (@JarePad) example but it's still easy to maintain like logical stuff can be. – Przemysław Michalski Nov 08 '10 at 18:46
  • you are relying on the numeric relationship between versions, both to select the ones to be applied, and to order the list. this is just a syntactic sugar version of the rest of the solutions – necromancer Oct 30 '11 at 02:11
40

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();
        }
    }
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • in my case update methods must be called incrementally - previous method do not cover functionality of current - so I would have to set if(version == number) for each cases separately anyway – Przemysław Michalski Oct 27 '10 at 19:43
  • 5
    @UGEEN, this solution does call them incrementally. If version started out as 1002 it would call `updateTo1003`, `updateTo1004` and `updateTo1005` in sequence. – JaredPar Oct 27 '10 at 19:45
  • @JaredPar - yes you've right, it will work, but as I see, I can use '==' operator insteed of '<=' – Przemysław Michalski Oct 27 '10 at 19:50
  • 2
    @UGEEN, that won't work unless you put in a big while loop or update version each time – user470379 Oct 27 '10 at 19:52
  • The value of version could be updated with each call to updateToX(). It might make it clearer about what the current version is. – Daniel Ballinger Oct 27 '10 at 19:53
  • 1
    I still don't like this for the same reason I commented on another answer (which has been deleted). As we get more and more version numbers that we need to support, this is going to turn into an really hard to maintain mess. The Chain of Responsibility pattern is, imho, a more appropriate design pattern to apply to this problem. – Dave White Oct 27 '10 at 19:59
  • @Dave, OP question suggested a smaller number of versions. Added an alternative for larger version numbers. – JaredPar Oct 27 '10 at 20:08
  • @Jared - Thanks Jared! That is much better than the if/switch/goto mess previous, but it is significantly different than your original suggested answer. Which one, in hind sight, would you want as "the" answer you would most support? :) – Dave White Oct 27 '10 at 20:13
  • 1
    @Dave, probably the most recent version. I have a personal style affinity for nice type loops, delegates and tuples so it appeals to me. It also has the least going forward overhead (upgrade function + 1 entry in a table). – JaredPar Oct 27 '10 at 20:14
  • @JaredPar - tuple example looks clear and easy to manage - interesting - thank you. – Przemysław Michalski Oct 27 '10 at 20:27
  • this one relies on numeric relationships and for the list elements to be in the right order – necromancer Oct 30 '11 at 02:00
6

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.

Dave White
  • 3,451
  • 1
  • 20
  • 25
  • 1
    +1 (though leave it to OO to take 2-line flow control, and turn it into 5-10 line classes, multiplied by the number of patches you need to do :). Even still, that "updateTo2002()" has to go somewhere...) – Merlyn Morgan-Graham Oct 27 '10 at 21:58
  • the chaining is done at run-time and needs run-time tests for whether null or not - not necessary if you can do the chaining at compile-time using a vanilla hierarchy – necromancer Oct 30 '11 at 02:14
5

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

Community
  • 1
  • 1
Andrew Bezzub
  • 15,744
  • 7
  • 51
  • 73
  • 6
    Have to agree. It's dogmatic and not true. I'll admit that I haven't use a goto since I first learned Pascal 27 years ago, but it's still not a good answer. – Adam Crossland Oct 27 '10 at 19:40
  • 12
    If your justification for never using goto is a Velociraptor attack, I think your argument needs work. – Jon B Oct 27 '10 at 19:41
  • 1
    Whether it is or not is besides the point, provide a better sample than OP without using goto or else don't universally state that goto is always bad. – user470379 Oct 27 '10 at 19:41
  • 1
    *Always* is an extremely strong word. It's also an extremely overused word. It makes me skeptical, by default, whenever I see someone use it. I can't think of a good reason to use GOTO, and I'm not sure this question's issue is one, but I wouldn't say it's 'always' a bad practice. – Andrew Barber Oct 27 '10 at 19:42
  • 20
    @Nathan It's not uninformed - can't you **see** the **Velociraptor**?? – Dan J Oct 27 '10 at 19:42
  • 1
    It is patently untrue that using goto is always less readable than an alternative. I have seen countless examples of code that went through hideous gyrations to avoid an obvious situation where `goto` would have provided a clear and obvious solution. – Adam Crossland Oct 27 '10 at 19:43
  • Sorry, but the linked list and processor method sounds 'complicated.' And unless it is unprecedentedly well-documented, it is not going to be easier to read and understand than a simple `goto`. Regardless, @Andrew, no matter how many really, reaaly good examples of not using `goto` you provide, I'm still going to think that it's not enough evidence to demonstrate that `goto` is ALWAYS bad. – Adam Crossland Oct 27 '10 at 19:47
  • 1
    @Adam It is always a bad practice in c# since c# is purely object oriented while goto is not. I think it was left in c# only for code-generating purposes. – Andrew Bezzub Oct 27 '10 at 19:52
  • 1
    Seems like the number of un/under-informed people is quite high. Current score on this answer is +6/-5. :\ – Nathan Taylor Oct 27 '10 at 19:52
  • 6
    How about we expand this problem to have 100 versions? How does this code look now? We need to stop looking at the very small sample data set for this example and look at the big picture and provide a design pattern to solve the problem more completely, not this very particular instance of the problem. – Dave White Oct 27 '10 at 20:02
  • I'm pretty sure that at some point someone proved that a series of alglorithms existed (that didn't calculate anything useful) that became exponentially more complicated to specify without GOTO's, but with GOTO's only became linearly more complicated. – DonaldRay Oct 27 '10 at 20:02
  • 1
    +1 for the altnerative to goto. @pst has nice sample code for this. – Dirk Vollmar Oct 27 '10 at 20:05
  • 3
    @Andrew: C# `goto` isn't nearly as dangerous as `goto` in other languages in that it does not allow you to jump from outside a block into a block. While it is rare for `goto` to be appropriate in C#, `goto` is not universally evil; there are some problems where using `goto` results in much cleaner code than not using it. Mind you, I haven't noticed any instances of this situation in my own code. – Brian Oct 27 '10 at 20:06
  • If goto is always considered bad practice, they wouldn't put it into the language. – Michael Stum Oct 27 '10 at 21:21
  • 1
    The `goto` statement in C# was *intended specifically* for this scenario. – Gabe Oct 27 '10 at 21:22
  • 1
    @Andrew, did you realize that _you_ are the dinosaur in the cartoon? – H H Oct 27 '10 at 22:20
  • I tried to use goto in C# on Mono for the Mac, inside of a lambda expression and it crashed the compiler. I just rewrote it with if statements (and an extra control flow bool, [shudder]). – Jared Updike Oct 28 '10 at 18:44
  • The raptor entered by an unlocked door. The door was unlocked as an unpredicted side effect of the `main_sub3` execution. – heltonbiker Jun 14 '13 at 18:43
2

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);
    }
}
dahlbyk
  • 75,175
  • 8
  • 100
  • 122
  • I think this solution is interesting, but in practical usage I think it's a little bit triumph of form over content – Przemysław Michalski Oct 28 '10 at 07:58
  • Depends on your needs and other implementation details. The individual upgrade implementations are easily testable, have a single responsibility, and you could configure an IoC container to automatically inject all implementers of IUpgradeCommand<> so you don't have to manage the list by hand. You did ask for an OO solution... ;) – dahlbyk Oct 28 '10 at 13:05
  • relies on numeric relationship between version numbers (essentially does the same thing but in a more complicated way: `common_sense > patterns` – necromancer Oct 30 '11 at 02:08
  • What about `bool UpgradeApplies(App)` depends on version numbers? I gave `App` a `Version` property to align with the original example, but you could just as easily detect features of the current environment instead. `composition > inheritance` – dahlbyk Oct 31 '11 at 01:37
2

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;
  }

}
Martijn
  • 11,964
  • 12
  • 50
  • 96
1

I would say that this is a very eligible reason to use the GOTO feature.

http://weblogs.asp.net/stevewellens/archive/2009/06/01/why-goto-still-exists-in-c.aspx

In fact, the switch() statement in C# is effectively a pretty face for a collection of labels and a hidden goto operation. case 'Foo': is just another way of defining a type of label inside the scope of a switch().

Nathan Taylor
  • 24,423
  • 19
  • 99
  • 156
  • 4
    One problem I have with that article is the appeal to authority he has at the end: "Before you disagree with the inclusion of the goto in the C# language, remember you are disagreeing with the people who created the language. You are also disagreeing with Steve McConnel the author of "Code Complete"." - not just a logical fallacy, but I've seen proof that the framework and language designers didn't always like their own decisions (e.g. their decision to add data to every single object, just to support lock, not always following naming conventions/not always supporting tryparse, etc) – Merlyn Morgan-Graham Oct 27 '10 at 22:32
  • 1
    @Merlyn - One more problem with that article `if (Step1() == true)` – Ishtar Oct 27 '10 at 23:39
  • @Merlyn and @Ishtar shoot the author, not the copy-paster! :p – Nathan Taylor Oct 28 '10 at 01:17
  • given that there are better solutions, i don't see a need to use the goto "feature" (feature!?) – necromancer Oct 30 '11 at 02:09
1

I think perhaps the logic is somewhat backwards here and causing the problem. What if your methods looked like this:

updateTo1002() 
{ 
   if (version != 1001) {
       updateTo1001();
   }
   // do the job     
} 
updateTo1003() 
{ 
   if (version != 1002) {
       updateTo1002();
   }
   // do the job     
} 

I don't know your exact use case, but it would seem to me like most often you would want to update to the most recent version, but install the incremental updates as needed along the way. I think doing it this way captures that logic better.

Edit: from @user470379's comment

In this case, mostly it's identifying the fact that you have a copy/paste pattern and editing it.

The coupling problem is, in this case, barely an issue but could be. I'll give you a few things that could come up in your scenario that would be hard to code if done this way:

  • Every update now needs an extra cleanup step, so after updateTo1001() call cleanup(), etc.
  • You need to be able to step back in order to test older versions
  • You need to insert an update between 1001 and 1002

Let's take a combination of these two done following your pattern. First let's add an "undoUpgradeXXXX()" to undo each upgrade and be able to step backwards. Now you need a second, parallel set of if statements to do the undos.

Now, let's add to that "insert 1002.5". All of a sudden you are re-writing two potentially long chains of if statements.

The key indication that you are going to have these kind of problems is that you are coding in a pattern. Look out for patterns like this--in fact, one of my first indications is usually when I'm looking over someone's shoulder at their code, if I can see a pattern without even being able to read anything written like this:

********
   ***
   *****

********
   ***
   *****
...

then I know I'm going to have problems with their code.

The easiest solution is generally to remove the differences from each "group" and put them into data (often an array, no necessarily an external file), collapse the groups into a loop and iterate over that array.

In your case, the easy solution is to make each of your upgrade objects with a single upgrade method. Create an array of these objects and when it's time to upgrade, iterate over them. You may also need some way to sequence them--You're currently using a number, that might work--or a date might be better--that way you can "Go to" a given date easily.

A few differences now:

  • Adding a new behavior to each iteration (cleanup()) would be a single line modification to your loop.
  • Reordering would be localized to modifying your objects--possibly even simpler.
  • Breaking your upgrade into multiple steps that must be called in order would be easy.

Let me give you an example of that last one. Suppose after all your upgrades have been run you need to go through an initialize step for each (different in each case). If you add an initialize method to each object then the modification to your initial loop is trivial (simply add a second iteration through the loop). In your original design you'd have to copy, paste & edit the entire if chain.

Combine JUST undo & initialize and you have 4 if chains. It's just better to identify problems before you start.

I can also say that eliminating code like this can be difficult (downright tough depending on your language). In Ruby it's actually pretty easy, in java it can take some practice and many can't seem to do it so they call Java inflexible and difficult.

Spending an hour here and there mulling over how to reduce code like this has done more for my programming abilities than any books I've read or training I've had.

Also it's a challenge, gives you something to do instead of editing huge if-chains looking for the copy/paste error where you forgot to change 8898 to 8899. Honestly it makes programming fun (which is why I spent so much time on this answer)

Bill K
  • 62,186
  • 18
  • 105
  • 157
user470379
  • 4,879
  • 16
  • 21
  • 1
    @DaveWhite Explain. If you're concerned about the verbosity it could be shortened to `if (version != 1001) updateTo1001();` – user470379 Oct 27 '10 at 20:06
  • Unmaintainable was harsh, but imagine your example with 20 version numbers. 50 version numbers. 100 version numbers. Do you want to maintain that code? Replace Unmaintainable with "Very hard to maintain". – Dave White Oct 27 '10 at 20:08
  • I don't think any of the current answers are overly verbose. Terse, not many of them, but nothing that would startle me. – Dave White Oct 27 '10 at 20:09
  • @Dave I would tend to agree, but compared to most of the other answers here I would say it's about even: A `goto` for each update vs. an if block around each update vs. this is about the same, while giving the flexibility to allow multiple update paths if needed. – user470379 Oct 27 '10 at 20:11
  • I've commented similarly on the other answers too, as appropriate. – Dave White Oct 27 '10 at 20:14
  • @user470379 Verbose and Terse have nothing to do with maintainable, in fact, verbose is generally more maintainable. Unmaintainable means that when you create an "Upgrade" method you have to come back here and modify some seemingly unrelated code--using copy, paste & edit nonetheless. This is one of the most important things a programmer can learn, to be completely intolerant of code like this. – Bill K Oct 27 '10 at 21:55
  • @Bill I appreciate your opinion, as I will freely admit that I'm still relatively inexperienced and appreciate learning from the many experienced people such as you here on SO. That being said, I'm still having problems seeing what you said about modifying unrelated code. If I wanted to add a 1004 version to the sample, I wouldn't have to touch any already written code -- the new updateTo1004 method would do a new check to make sure we're at 1003 and that would be that. I do agree that it is somewhat C&P code, but nearly every alternative I see here is C&P as well. Again, just trying to learn. – user470379 Oct 28 '10 at 00:57
  • @user470379 See the answer, I added a bunch to it. – Bill K Oct 28 '10 at 17:48
  • the code inside methods for one version has to know about other versions -- no high level abstraction here – necromancer Oct 30 '11 at 02:02
1

The right way to do this is using inheritance and polymorphism as follows:

First, note that there is a clear hierarchical relationship between the code executed in the various cases. I. e. The first case does everything for second case and then some more. The second case does everything for third case and then some more.

Therefore, create a class hierarchy:

// Java used as a preference; translatable to C#
class Version {
    void update () {
        // do nothing
    }
}

class Version1001 extends Version {
    @Override void update () {
        super.update();
        // code from case update 1001
    }
}

class Version1002 extends Version1001 {
    @Override void update () {
        super.update();
        // code from case update 1002
    }
}

class Version1003 extends Version1002 {
    @Override void update () {
        super.update();
        // code from case update 1003
    }
}

// and so forth

Second, use virtual dispatch, aka polymorphism, instead of the switch-case:

Version version = new Version1005();
version.update();

Discussion (for the unconvinced):

  1. Instead of gotos, use the destination-neutral super.update() and make the connection in the class hierarchy "Version1002 extends Version1001"
  2. This does not depend on the arithmetic relationship between the version numbers (unlike a popular answer above), so you can elegantly do things like "VersionHelios extends VersionGalileo"
  3. This class can centralize any other version-specific functionality such as @Override String getVersionName () { return "v1003"; }
necromancer
  • 23,916
  • 22
  • 68
  • 115
0

I think it's alright, although I doubt this is your real code. Are you sure you don't need to encapsulate an Update method inside an AppUpdate class or something? The fact that you have methods named XXX001, XXX002 etc. is not a good sign, IMO.

Anyway. here's an alternative with delegates (not really suggesting you use it, just an idea):

var updates = new Action[] 
                 {updateTo1002, updateTo1003, updateTo1004, updateTo1005};

if(version < 1001 || version > 1004)
   throw new InvalidOperationException("...");    

foreach(var update in updates.Skip(version - 1001))
   update();

It would be difficult to recommend the most appropriate pattern without more detail.

Ani
  • 111,048
  • 26
  • 262
  • 307
  • I find that code to be more difficult to read than the original goto code. – Holstebroe Oct 27 '10 at 19:58
  • 2
    @Holstebroe - Look at the original code with 100 version choices in it. This may "look" harder if you are unfamiliar, but this is a step towards testability and maintainability. – Dave White Oct 27 '10 at 20:06
  • The code doesn't have 100 version choices, it has 5. The point being that the cost of generalization only starts to pay off at a certain complexity level. Of course one should have the discipline to refactor when that level is reached. – Holstebroe Oct 28 '10 at 09:07
0

I've had to deal with some such a problem (get file into this format, so it can be gotten into this other format, etc.) and I don't like the switch statement. The version with 'if' tests might be good, or it might be good to recursively have something like:

/* Upgrade to at least version 106, if possible.  If the code
   can't do the upgrade, leave things alone and let outside code
   observe unchanged version number */

void upgrade_to_106(void)
{
  if (version < 105)
    upgrade_to_105();
  if (version == 105)
  {
    ...
    version = 106;
  }
}

Unless you have thousands of versions, stack depth shouldn't be a problem. I think the if-test version, testing specifically for versions that each upgrade routine can handle, reads better; if the version number at the end isn't one the main code can handle, signal an error. Alternatively, lose the "if" statements in the main code and include them in the routines. I don't like the 'case' statement because it doesn't consider the possibility that a version-update routine might not work, or might update more than one level at a time.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • relies on numeric relationship between version numbers and correct order of if statements -- brittle you can easily copy a segment and forget to change the version number and have the upgrade code run twice – necromancer Oct 30 '11 at 02:06
  • @agksmehx: I edited to fix the code above (should have set version to 106). As fixed, calling the upgrade routine twice should be a no-op. The basic idea is a simplistic linear implementation of what would, more generally, be the 'strategy' pattern. The code doesn't handle the question of what should happen if the file starts out in version 107 or higher; that could perhaps be handled by having two version numbers in a file: the actual version, and the number of the lowest code that can read it. Version 106 of the code won't otherwise have any idea whether it will be able to read 107. – supercat Oct 30 '11 at 20:22
0

I threw into a comment that using goto is never worth the crap you'll take for using it (even if it's an awesome, perfect use)--too many programmers learn something and can never get it unstuck from their brain.

I wasn't going to post an answer, but I don't think it's been made clear enough that the entire solution you are implying is just wrong. I had assumed it was just to make your point, but it should be made clear: Be very careful of patterns in code--this is just as bad as copy/paste code (in fact, it IS copy/paste code).

You should just have a collection of objects, each with the upgrade code and a version number.

You simply iterate over that collection while the version number is < your target version, and call the upgrade code on that object for each one. That way to create a new upgrade level you are just making a single "Upgrade" object and sticking it into the collection.

Your chain of upgrade objects can even be traversed backwards as well as forwards with the addition of an undo and a very trivial code addition to the controller--something that would become a nightmare to maintain using the example code.

Bill K
  • 62,186
  • 18
  • 105
  • 157
0

You may look at State Machine workflow pattern. The simple and usefull for you may be: Stateless project

BrainMaxx
  • 21
  • 3