基于没有if-else的枚举选择一个要调用的函数(或开关)

时间:2019-04-17 11:24:33

标签: c# dictionary inheritance enums

我有一个界面和3个功能

public interface IDrawingObject
    {
        void Draw(Color c);
    }

    public class DrawTriangle : IDrawingObject
    {
        public void Draw(Color c)
        {
            //for demo purpose
            Console.WriteLine("Drawing Triangle with color " + c.Name );

        }
    }

    public class DrawCircle : IDrawingObject
    {
        public void Draw(Color c)
        {
            //for demo purpose
            Console.WriteLine("Drawing Circle with color " + c.Name);
        }
    }

    public class DrawRectangle : IDrawingObject
    {
        public void Draw(Color c)
        {
            //for demo purpose
            Console.WriteLine("Drawing Rectangle with color " + c.Name);
        }
    }

和这个枚举

  public enum Shapes
    {
        Circle,
        Rectangle,
        Triangle
    }

这里可以有更多的功能(和枚举)

我想让static void Draw(Shapes s, Color c)根据此枚举选择要调用的正确函数,并且在我看来,使用if-else(或switch将使代码膨胀)

所以我采用了另一种方法,即使用IDictionary

   private static IDictionary<Shapes, Action<Color>> Mapper = new Dictionary<Shapes, Action<Color>>
{
    { Shapes.Circle, (Color c) => { IDrawingObject draw = new DrawTriangle(); draw.Draw(c);} },
    { Shapes.Rectangle, (Color c) => { IDrawingObject draw = new DrawRectangle(); draw.Draw(c); } },
    { Shapes.Triangle, (Color c) => { IDrawingObject draw = new DrawCircle(); draw.Draw(c); } }
};

我的功能是

public static void Draw(Shapes s, Color c)
        {
            if (Mapper.ContainsKey(s))
            {
                Mapper[s](c);
            }
        }

但是,在我看来,我仍在进行大量不复杂的复制和粘贴

还有更好的方法吗?

PS

我看过herehere

4 个答案:

答案 0 :(得分:1)

虽然我不建议这样做,但是您可以使用反射通过名称创建类的实例。像这样(未经测试):

var draw = (IDrawingObject)Activator.CreateInstance("AssemblyName", "Draw" + shape.ToString());
draw.Draw();

答案 1 :(得分:1)

此代码并不是真的比switch干净,除非字典被多种方法使用。使用绘图对象和界面的方式,所有Draw方法都可以很容易地在一个类上成为静态方法。

回答了确切的问题,可以使用Dictionary.TryGetValue

public static void Draw(Shapes s, Color c)
{
    if (Mapper.TryGetValue(s,out var act))
    {
        act(c);
    }
 }

所有Draw方法都可以更改为静态方法,因为它们不使用任何实例成员:

private static IDictionary<Shapes, Action<Color>> Mapper = new Dictionary<Shapes, Action<Color>>
{
    [Shapes.Circle]= (Color c) => DrawTriangle.Draw(c),
    [Shapes.Rectangle]= (Color c) => DrawRectangle.Draw(c),
    [Shapes.Triangle]=(Color c) => DrawCircle.Draw(c)
};

如果不是:

private static IDictionary<Shapes, Action<Color>> Mapper = new Dictionary<Shapes, Action<Color>>
{
    [Shapes.Circle]    = DrawTriangle.Draw,
    [Shapes.Rectangle] = DrawRectangle.Draw,
    [Shapes.Triangle]  = DrawCircle.Draw
};

更新

语法显示的

顺便说一句,这有点奇怪。使用 types 代替枚举会阻止在请求圆时绘制圆

我们需要更多地了解Shapes的来源,或者为什么要使用接口来创建更好的实现,并且要避免使用错误的实现

顺便说一句,可以将C#8的switch表达式与接口一起使用:

var drawer= shapes switch 
            {
                Shapes.Circle   =>new DrawingTriangle(),
                Shapes.Rectangle=>new DrawingRectangle(),
                Shapes.Triangle =>new DrawingCircle(),
                _ => ???
            };
drawer.Draw(c);

答案 2 :(得分:1)

看看您的Draw方法:它们实际上是否对DrawTriangleDrawRectangle等类中的任何类进行了更改?

如果没有,那么您不需要每次想绘制东西时都创建一个新实例。相反,您可以只将DrawTriangle的一个实例存储在Mapper字典中:

private static Dictionary<Shapes, IDrawingObject> Mapper = new Dictionary<Shapes, IDrawingObject>()
{
    { Shapes.Circle, new DrawCircle() },
    { Shapes.Rectangle, new DrawRectangle() },
    { Shapes.Triangle, new DrawTriangle() },
};

然后,您为给定的IDrawingObject获取相应的Shapes,并调用其Draw方法:

public static void Draw(Shapes s, Color c)
{
    if (Mapper.TryGetValue(s, out IDrawingObject drawingObject))
    {
        drawingObject.Draw(c);
    }
}

如果出于某些原因要做需要在您要绘制三角形时立即创建一个新的DrawTriangle,则可以将Func<IDrawingObject>个委托放入字典中,但是仍以您的静态IDrawingObject.Draw方法调用Draw

private static Dictionary<Shapes, Func<IDrawingObject>> Mapper = new Dictionary<Shapes, IDrawingObject>()
{
    { Shapes.Circle, () => new DrawCircle() },
    { Shapes.Rectangle, () => new DrawRectangle() },
    { Shapes.Triangle, () => new DrawTriangle() },
};

然后:

public static void Draw(Shapes s, Color c)
{
    if (Mapper.TryGetValue(s, out Func<IDrawingObject> drawingObjectFactory))
    {
        IDrawingObject drawingObject = drawingObjectFactory();
        drawingObject.Draw(c);
    }
}

答案 3 :(得分:0)

这实际上就是您所拥有的!

您可以使用扩展方法使讨厌的枚举看起来像“适当”的对象。 (虽然是一个海市rage楼,但在MkShape方法中具有异常)。

所以对此的主要反对意见是它不是类型安全的,如果您添加新的枚举并忘记更新MkShape,您的代码将崩溃...像F#,Scala等这样的功能性语言会警告您这很糟糕。 / p>

您的字典什么也没做,只是交换一点复杂性以换取switch语句的微小性能优势...即除非您有数百个枚举值,否则不要打扰,它甚至可能会降低性能(对于较小的n)

public enum Shapes
{
    Circle,
    Rectangle,
    Triangle
}

public interface IShape
{
    void Draw(Color c);
}

public static class Shape
{
    public static void ExampleClientCode()
    {
        var s = Shapes.Circle;
        // your enum looks like a "proper" object
        s.Draw(Color.AliceBlue);
    }

    public static IShape MkShape(this Shapes s)
    {
        switch (s)
        {
            case Shapes.Circle:
                return new Circle();
            case Shapes.Rectangle:
                return new Rectangle();
            case Shapes.Triangle:
                return new Triangle();
            default:
                throw new Exception("nasty enum means I can't have typesafe switch statement");
        }
    }

    public static void Draw(this Shapes s,Color c)
    {
        s.MkShape().Draw(c);
    }
}

public class Triangle : IShape
{
    public void Draw(Color c)
    {
        //for demo purpose
        Console.WriteLine("Drawing Triangle with color " + c.Name);

    }
}

public class Circle : IShape
{
    public void Draw(Color c)
    {
        //for demo purpose
        Console.WriteLine("Drawing Circle with color " + c.Name);
    }
}

public class Rectangle : IShape
{
    public void Draw(Color c)
    {
        //for demo purpose
        Console.WriteLine("Drawing Rectangle with color " + c.Name);
    }
}

P.S。

我怀疑将颜色存储在形状中可能很有用,但是如果您绑定到枚举,那么您将只知道形状而不是其他任何东西