views:

102

answers:

1

If we want to refactor an enum (contained in the domain layer) to a polymorphic class, using "simple" abstract methods could be a bad idea, if all the switch and if statements we want to refactor are inside the other layers (like the business or the presentation layer), because we could end up to reference these layers inside the domain layer:

public abstract class MyRefactoredEnum
{
    public abstract void DoSomething(IBusinnessObject aBizObject); //dependency to the biz. layer

    public abstract MvcHtmlString GetImgTag(); //dependency to presentation layer
}

(in the example above, we can have a "cross reference" problem too)

I've found that the visitor pattern ( http://en.wikipedia.org/wiki/Visitor_pattern ) it's a valid solution to this problem: in the domain layer we define only the MyRefactoredEnum.IVisitor interface, and all the other layers can implement their own visitors.

The only problem: when we modify the MyRefactoredEnum.IVisitor interface (for example, because we've added another MyRefactoredEnum's subclass) we have to modify and recompile all projects and solutions that reference the domain model. We can solve the problem using reflection ( http://surguy.net/articles/visitor-with-reflection.xml ), but it can be slow...

Is there a better pattern to refactor an enum?

PS: sorry for my awful English :)

+3  A: 

You could provide a default implementation for visitors that has a fall-back method:

abstract class Cheese
{
    public abstract void Visit(ICheeseVisitor visitor);
}

class Wensleydale : Cheese { ... }

class Gouda : Cheese { ... }

interface ICheeseVisitor
{
    void Visit(Wensleydale cheese);
    void Visit(Gouda cheese);
}

abstract class CheeseVisitor : ICheeseVisitor
{
    public virtual void Visit(Wensleydale cheese) { Default(cheese); }
    public virtual void Visit(Gouda cheese) { Default(cheese); }
    public virtual void Default(Cheese cheese) { }
}

When you add new types, libraries built against an older version will use the fall-back method, while newer libraries can override the new overloads:

class Brie
{
    public override void Visit(ICheeseVisitor visitor)
    {
        visitor.Visit(this);
    }
}

interface ICheeseVisitor
{
    ...
    void Visit(Brie cheese);
}

abstract class CheeseVisitor : ICheeseVisitor
{
    ...
    public virtual void Visit(Brie cheese) { Default(cheese); }
    ...
}

Example:

class CheeseImgVisitor : CheeseVisitor 
{
    private string src;

    public string Src
    {
        get { return this.src; }
    }

    public override void Visit(Wensleydale cheese)
    {
        this.src = "wensleydale.png";
    }

    public override void Visit(Gouda cheese)
    {
        this.src = "gouda.png";
    }

    public override void Default(Cheese cheese)
    {
        this.src = "generic_cheese.png";
    }
}
dtb
The "abstract visitor with a default case" is a great idea, thanks! :) And clients can always choose to inherit from the IVisitor interface, if they want to "be notified" (by compile errors) when a new subclass is introduced...
Notoriousxl
Upvote for making me think of the Cheese Shop sketch, and smiling. I think this "cheese" example of inheritance is my new favorite (though admitedly, it's kinda limited to a hierarchy only 2 levels deep). :)
mikemanne