1
public class Tile
{
   State state ;

   public void OnStep()
   {
       if(state == State.normal) { // Do something; }
       else if(state == State.empty) { // Do something; }
       else if(state == State.hazard) { // Do something; }

   }
}

Basically, OnStep will do different behaviours according to the 'state' variable.

I really want to remove those 'if statements' yet I do not want to change Tile class into an abstract class. It works now but I want to know if there is a better technique to manage this. Currently 'State' is enum. ( I am wondering if there is anything that can bind both state and action at the same time ).

hikariakio
  • 21
  • 2
  • _"I really want to remove those 'if statements' yet I do not want to change Tile class into an abstract class."_ How would that be related? – Fildor Feb 10 '23 at 15:09
  • 1
    _"Currently 'State' is enum. ( I am wondering if there is anything that can bind both state and action at the same time )"_ Sure. For one, you could use switch instead of if/else. But that's only a minor change. But then you could use for example strategy pattern ... basically that's a statemachine, right? – Fildor Feb 10 '23 at 15:11
  • It really depends on the nature of those `//Do something`s. If they can each be replaced by a parameterless method inside the same class than perhaps `state` could just be an `Action` instead that is update to reflect which method should be called next. Also depends on what else `state` is being used for. This is the problem when you so far abstract your example from your problem that so many answers *may* be possible. – Damien_The_Unbeliever Feb 10 '23 at 15:12
  • 1
    You're options are, use a `switch`, use a `Dicitonary` or as you seem to indicate is not what you want to do, create subclasses on `Tile` for each `State` that `Tile` can have (this only makes sense if `State` is immutable) – juharr Feb 10 '23 at 15:13
  • @Fildor, at first I want to make Tile into abstract class and create different child classes for each state ( instead of if statements ) – hikariakio Feb 10 '23 at 15:15
  • 1
    https://stackoverflow.com/questions/5923767/simple-state-machine-example-in-c – Hans Passant Feb 10 '23 at 15:44
  • What does `State` look like? – John Alexiou Feb 10 '23 at 16:27
  • @JohnAlexiou _"Currently 'State' is enum"_ - from Question. – Fildor Feb 10 '23 at 16:30
  • @Fildor - it threw me off because it is using lowercase names for the enum field, instead of Uppercase which is the standard, so it could have been a `struct` with readonly fields like you have in Java. – John Alexiou Feb 10 '23 at 19:05

3 Answers3

1

So I get you want to extract the logic away from Tile class ...

public class Tile
{
   private IState _state;

   public void OnStep()
   {
       _state = _state.Step();
   }
}

public interface IState
{
    IState Step();
}

public class NormalStep: IStep
{
    public IStep Step()
    { 
       /*Step logic*/ 
       // Just an example
       if( encounter.HazardousObject ) return new HazardStep();
       return this;
    }
}

public class HazardStep: IStep
{
    public IStep Step(){ /*Step logic*/ }
}

public class EmptyStep: IStep
{
    public IStep Step(){ /*Step logic*/ }
}

That would be the simplest scenario. From here you always can get more complex. For example with state data ...

To pick up on madreflection's comments: Everything has its price.

In this case ...

  • you get rid of the if statements.
  • you "bundle" behavior with type (== state)

But you pay with substantially more code, more classes, more interfaces. So you always need to take into consideration with what to go and live. What if you have to change/add something in two week's time? Will you be able to without problem?

One last thing: Please do mind, that the above example is just a brief and very simple "something like this". It is of course far away from being production-quality.

Fildor
  • 14,510
  • 4
  • 35
  • 67
  • 1
    In other words, learn the Gang of Four design patterns -- as one should do anyway -- and then select the State pattern to solve this problem. – madreflection Feb 10 '23 at 15:20
  • If you say it that way .... yes, basically that. @madreflection – Fildor Feb 10 '23 at 15:21
  • 2
    I mean, there's nothing inherently wrong with a stack of `if` statements for someone who's just setting out. But there seems to be this obsession with doing things with less code without any cost. It rarely is without cost. So here, you have less code int `OnStep`, but it had to cost something, and that's in what replaces the `if` statements, the State classes. New programmers need to stop obsessing over "less code" until they've learned enough, because they're not yet equipped (or willing) to pay for it with a maintainable design. – madreflection Feb 10 '23 at 15:27
  • 1
    Couldn't agree more. – Fildor Feb 10 '23 at 15:28
  • That said, source generation usually lets you have your cake (less code) and eat it too (automatically maintainable and as efficient as you can write it). – Blindy Feb 10 '23 at 15:44
  • 1
    Then the cost is the knowledge and effort it takes to write a source generator, which is a much more advanced topic than basic design patterns. – madreflection Feb 10 '23 at 15:46
  • 1
    @Blindy, agreed, yet nothing I'd give the newbie in the team. – Fildor Feb 10 '23 at 15:46
  • I don't know man, you have to give them *something*. What could be better than a completely new piece of code with no dependencies on your other code that is very easily and strongly testable by design? – Blindy Feb 10 '23 at 15:53
  • 1
    @Blindy Depends on the person, I guess. I know some newbies at ours that would totally excel and others that would just explode. I basically follow the rule "learn to crawl before you learn to run". – Fildor Feb 10 '23 at 15:55
0

I think using a switch statement produces the cleanest code

public enum State
{
    Empty,
    Normal,
    Hazard,
}

public class Tile
{
    public State State { get; set; }

    public void OnStep()
    {
        switch (State)
        {
            case State.Empty:
                // do things
                break;
            case State.Normal:
                // do things
                break;
            case State.Hazard:
                // do things
                break;
            default:
                throw new NotSupportedException($"Unknown state {State}");
        }
    }
}
John Alexiou
  • 28,472
  • 11
  • 77
  • 133
-1

This is not meant to be a "better" solution, just suggesting another way to do it.

You may name individual action methods in some convention e.g. RunNormalState(), so inside OnStep() you can use Reflection to invoke it.

  • Rule of thumb: If there are other options, reflection is very rarely the best (performance, maintainability, readability) of them. – Fildor Feb 10 '23 at 16:29
  • Well, bad performance yes, but readability is OK. You still keep all related logics inside a single class, and it's easy to extend or override methods when you have subclass of Tile. Reflection itself is not that uncommon. Numerous frameworks convert file paths / URL routes and invoke corresponding controller methods via reflection. – Siu Pang Tommy Choi Feb 11 '23 at 01:10