views:

271

answers:

8

I have an application in C#/Winforms that lets users place objects on a grid to create levels for a game. It has several tools for placing tiles/lights/doors/entities etc. Currently I just use an enum for storing the currently selected tool and have a switch statement to run each tools code. As I've been adding more tools to the application it's starting to get spaghetti like, with lots of duplicated code.

Here is a cutdown version of the mouse down function in my editor class:

    public void OnEditorViewMouseDown(Point mousePos)
    {
        // Check if the click is out of bounds.
        if (IsLocationOOB(mousePos)) return;

        if (CurrentTool == ToolType.GroundTile)
        {
            // Allow drags along whole tiles only.
            m_DragManager.DragType = DragManager.DragTypeEnum.Tile;
            m_DragManager.StartDrag(mousePos);
        }
        else if (CurrentTool == ToolType.WallTile)
        {
            // Allow drags along grid edges only.
            m_DragManager.DragType = DragManager.DragTypeEnum.Edge;
            m_DragManager.StartDrag(mousePos);
        }
        else if (CurrentTool == ToolType.PostTile)
        {
            // Allow drags along grid points only.
            m_DragManager.DragType = DragManager.DragTypeEnum.Point;
            m_DragManager.StartDrag(mousePos);
        }
        else if (CurrentTool == ToolType.AreaLight)
        {
            // Allow drags anywhere. ie. not snapped to the grid in some way.
            m_DragManager.DragType = DragManager.DragTypeEnum.FreeForm;
            m_DragManager.StartDrag(mousePos);
        }
        else if (CurrentTool == ToolType.PointLight)
        {
            m_CurrentWorld.AddLight(TranslateToWorldCoords(mousePos));
        }
        else if (CurrentTool == ToolType.PlaceEntity)
        {
            m_CurrentWorld.PlaceEntity(TranslateToWorldCoords(mousePos));
        }
    }

The switch is used in several other functions (OnMouseMove, OnMouseUp) and it seems like bad design (big switch copied in several functions). Any advice for refactoring something like this in a cleaner and more extensible way? I'm currently thinking of having a base Tool class and having each tool it's own class that overrides the functions it uses (OnMouseDown() etc.). Does this sound sensible?

Thanks for reading.

A: 

Use a real switch statement, it will most likely get translated into a single jmp in assembler.

Blindy
Actually, a switch statement generally compiles to essentially the same as if/else if there are less than 3 or 4 expressions to match against (case statements). Of course, this depends on the compiler and how it is set up to optimize. If there are a large number of items, then the compiler will optimize by doing a few variations on jmp tables (think hash table). If there are a lot of expressions, but they are all between 0..255 for instance, a look up table whose index is the byte value of the expression may be used to find the jmp offset. i.e. it rarely generates a single jmp.
Adam Markowitz
+1  A: 

I'm not terribly familiar with the syntax of C# but in general you could make the CurrentTool an object that has methods StartDrag, EndDrag which accept a DragManager as an argument. Then when the mouse is pressed down on the view you just invoke CurrentTool.StartDrag(m_DragManager).

Allain Lalonde
+3  A: 

Yea, you should absolutley have a base class (or at the very least an interface) that defines all the common methods needed across all Tools. Try to make this code work, and it'll give you a good idea of how to design your classes:

m_DragManager.DragType = CurrentTool.DragType;
m_DragManager.StartDrag(mousePos);

where "CurrentTool" is an instance of your base class or your interface.

So basically, when a "Tool" is selected, at that point you determine which derived Tool you're dealing with, but from that point on, you deal with the base class only, and forget about any enums or anything like that to determine the currently selected tool. Make sense?

BFree
+6  A: 

You have the good intuition.

Usually, in OOP, when you have rows of if's or humongous switches, it's a strong code smell. The best way to make this smell go away is to go with polymorphism.

You should go ahead with your idea, having a base abstract class BaseTool, with the different OnXXX methods implemented as nops (just returns, so you only have to specify the behavior if your tool knows how to act on the method), and have each tool inherit from BaseTool and implement its own behavior by overriding the relevant methods.

So your method ends up being

public void OnEditorViewMouseDown(Point mousePos)
{
  currentTool.OnEditorViewMouseDown(mousePos);
}

Depending on your design, you should also consider passing the DragManager to the method, so as not to be tied to instance variables laying around. An EditorContext (containing the DragManager) fitted with everything the method needs without having to fetch "global" variables would make your method more self-contained and less brittle when refactoring. The design itself will depend on the responsability: who is in charge of what.

Yann Schwartz
Yeah this is definately what I was thinking. This is also known as the Strategy Pattern as stated by rjohnston's answer
zonkflut
A: 

Try flag enums. Each bit would be a different feature and would allow for better stacking of features per cell.

Then you could just check for each bit even in different methods.

Matthew Whited
Sweet... down votes with no comments. Good to see creative criticism at work. My point is that he has mixed modes for each of his enums. If the flag bits identity each mode he can mix them with less code. This could be done with if statements or method calls. But still allows for the same type of data with one int or byte per cell. But if you want the overhead of complex objects for collision detection and rendering, feel free.
Matthew Whited
+3  A: 

Yes, polymorphism is what you want here. You should define either an abstract base class Tool or an interface ITool depending on if you need to add implementation to the base or not (i.e. if there there common functionality among all tools, you might use an abstract base class).

Your manager should then take a Tool or ITool when something needs to be done. Your tool will implement a Drag function that takes the information it needs and either return what it needs to return or do what it needs to do. Or you could implement an inversion of control between your Manager and your Tool and inject the Manager into the Tool via property injection (Tool.Manager = dragManager) and let the tool do what it needs to do using the manager.

JP Alioto
+4  A: 

Sounds like a good place to use the Strategy Pattern: http://www.google.com/search?q=c%23+strategy+pattern

rjohnston
This is the specific problem that the Strategy Pattern addresses. I cannot imagine a better to the problem or a more appropriate use of the pattern. I strongly recommend people read the wikipedia article on strategy pattern. In particular note that in languages supporting first class functions you can implement strategy pattern with almost no code at all. While C# technically doesn't support 1st class functions, delegates allow you to treat functions as first class.
Peter Wone