Выбор функции для вызова на основе перечисления без if-else (или переключателя) - PullRequest
0 голосов
/ 17 апреля 2019

У меня есть интерфейс и 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
    }

, и здесь может быть намного больше функций (и перечислений)

Iхочу иметь 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

Я смотрел здесь , здесь

Ответы [ 4 ]

1 голос
/ 17 апреля 2019

Этот код на самом деле не чище, чем 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
};

Обновление

Кстати, синтаксис показывает, что происходит что-то странное.Использование типов вместо перечисления предотвратило бы рисование кругов при запросе круга

Нам нужно было бы узнать больше о том, откуда берется Shapes, или почему интерфейсы используются для созданиялучшая реализация, и , избегайте использования неправильной реализации

Просто для удовольствия, между прочим, можно использовать выражения переключения C # 8 вместе с интерфейсами:

var drawer= shapes switch 
            {
                Shapes.Circle   =>new DrawingTriangle(),
                Shapes.Rectangle=>new DrawingRectangle(),
                Shapes.Triangle =>new DrawingCircle(),
                _ => ???
            };
drawer.Draw(c);
1 голос
/ 17 апреля 2019

Взгляните на ваши Draw методы: действительно ли они что-то меняют в любом из классов DrawTriangle, DrawRectangle и т. Д.?

Если нет, то вам не нужносоздавать новый экземпляр каждый раз, когда вы хотите что-то нарисовать.Вместо этого вы можете просто сохранить один экземпляр 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);
    }
}
1 голос
/ 17 апреля 2019

хотя я бы не советовал, вы можете использовать отражение, чтобы создать экземпляр класса по имени. Примерно так (не проверено):

var draw = (IDrawingObject)Activator.CreateInstance("AssemblyName", "Draw" + shape.ToString());
draw.Draw();
0 голосов
/ 17 апреля 2019

Это практически то, что у вас есть!

вы можете сделать свое мерзкое перечисление похожим на "правильный" объект, используя методы расширения.(хотя это мираж .... выставленный с помощью исключения в методе MkShape)

, поэтому большое возражение против этого заключается в том, что он не безопасен, если вы добавляете новые перечисления и забываете обновить MkShape, ваш код будетаварии ... функциональный язык, такой как F #, Scala и т. д., предупредит вас, что это плохо.

ваш словарь ничего не делает, кроме как поменяет немного сложностей на крошечное преимущество в производительности оператора switch ... т.е.не беспокойтесь, если у вас нет сотен значений enum, это может даже стоить вам производительности (для малых 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);
    }
}

PS

Я подозреваю, что это может быть полезно для храненияцвет в форме, но если вы привязаны к перечислению, то вы просто должны знать форму и ничего больше

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...