0
votes

Sélection d'une fonction à appeler basée sur une énumération sans if-else (ou un commutateur)

J'ai une interface et 3 fonctions

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

et cette énumération

   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); } }
};

et ici peut être beaucoup plus de fonctions (et énumérations )

Je veux avoir static void Draw (Shapes s, Color c) qui sélectionne la bonne fonction à appeler en fonction de cette énumération, et il me semble que l'utilisation de if- else (ou switch va gonfler le code)

J'ai donc pris une autre approche qui consiste à utiliser un IDictionary

  public enum Shapes
    {
        Circle,
        Rectangle,
        Triangle
    }

et ma fonction est

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);
        }
    }

mais quand même, il me semble que je fais encore beaucoup de copier-coller peu effrayants

Y a-t-il un meilleur moyen pour le faire?

PS

J'ai regardé ici , ici


24 commentaires

Devez-vous créer un nouvel IDrawingObject chaque fois que vous dessinez une forme? Vous ne pouvez pas simplement avoir un Dictionary ?


Remplacez Action par Func et appelez Draw () dans Draw () ?


Vous pouvez le faire via la réflexion et le nom de l'énumération / méthode. c'est-à-dire que si l'énumération est Circle, recherchez une classe appelée DrawCircle qui implémente IDrawingObject. Vous devrez être strict et cohérent avec la façon dont vous nommez vos classes et énumérations.


@CodeCaster et canton7, mais j'ai également besoin de la couleur, dois-je utiliser Action > ?


Ce code n'est pas plus propre qu'un commutateur, sauf si le dictionnaire est également utilisé par d'autres méthodes. L'utilisation de l'énumération Shapes signifie qu'il n'est pas possible d'utiliser la double répartition, la surcharge ou la correspondance de modèle - il n'y a aucun type impliqué ici du tout. Vous pouvez essayer d'utiliser if (Mapper.TryGetValue (s, out var act) {act (c);}


Pourquoi l'énumération Shapes ? D'où est ce que ça vient?


@PanagiotisKanavos c'est ma propre énumération


@styx Je veux dire pourquoi, au lieu de passer des objets de types différents à une méthode surchargée? Pourquoi ne pas utiliser des instances IDrawingObject au lieu de cette énumération? La valeur enum est-elle chargée à partir d'une base de données peut-être?


@PanagiotisKanavos c'est comme ça que l'héritage est implémenté, je ne veux pas y toucher ATM


@styx La couleur est transmise à la méthode Draw . Vous feriez donc Mapper [s] .Draw (c)


@styx la façon dont le code est écrit, vous n'obtenez aucun avantage des interfaces. Les différentes méthodes Draw peuvent tout aussi bien être des méthodes statiques sur une ou plusieurs classes statiques


@styx ce que vous avez posté ressemble un peu à une tentative d'utiliser les syndicats discriminés de F #


@ canton7, pouvez-vous me montrer comment l'écrire? (je veux dire le mappeur)


@styx Dictionnaire statique privé Mapper = new Dictionary () {{Shapes.Circle, new DrawCircle ()}, {Shapes.Rectangle, new DrawRectangle ()}, {Shapes. Triangle, nouveau DrawTriangle ()}};


@styx Ajout d'une réponse montrant un exemple complet


L'approche OO canonique consiste à modéliser les formes en tant que classes avec une méthode de dessin. Ensuite, vous avez juste besoin d'une méthode statique qui prend une énumération et retourne un objet de forme ... qui sait comment se dessiner ... qui rend l'ajout de nouvelles formes assez facile ... vous ajoutez une forme et modifiez la méthode mkShape ... si vous Un ensemble de formes est fermé et vous souhaitez ajouter de nombreuses méthodes ... puis regardez le modèle de visiteur. Les syndicats discriminés et les visiteurs sont étroitement liés.


@MrD si vous pouvez l'afficher comme une réponse complète, ce serait génial


En fait, vous faites DÉJÀ vraiment l’approche OO canonique, sauf que vous avez décidé de renvoyer une action et non un IDrawingObject ..


Si vous voulez le faire dans un style plus fonctionnel, vous utiliseriez le modèle de visiteur ... mais vous devrez toujours mapper votre énumération (désagréable) à un objet "Shape".


@MrD, pouvez-vous montrer cette partie? sauf que vous avez décidé de renvoyer une action et non un IDrawingObject


ou faites-le dans f # ... où vous utiliseriez des types de somme plutôt que des énumérations.


ok ... l'approche standard OF est pratiquement ce que vous avez, le problème que vous avez est que l'énumération n'est pas une chose amicale OO


@MrD malheureusement je ne peux pas abandonner l'énumération


J'ai publié une réponse ... si vous ne pouvez pas vous en débarrasser, isolez simplement son caractère désagréable à une seule méthode qui en mappe des objets de forme ... vous pouvez lui donner l'impression qu'il n'existe pas (avec l'extension méthodes) et vous n'avez qu'une méthode non sécurisée qui mappe des énumérations aux objets dans un commutateur


4 Réponses :


1
votes

Bien que je ne le recommande pas, vous pouvez utiliser la réflexion pour créer une instance de la classe par son nom. Quelque chose comme ça (non testé):

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


6 commentaires

Terme d'expression non valide 'enum'


mot probablement réservé, expurgé


maintenant il donne Impossible d'utiliser la variable locale 'shape' avant qu'elle ne soit déclarée, vouliez-vous dire s.ToString ()?


oui "s.ToString ()", remplacez également "AssembyName" par votre nom d'assembly


vous avez besoin d'un nom de type complet


@valisy oui, peut-être ... c'était un exemple, pas de copier-coller



1
votes

Ce code n'est pas vraiment plus propre qu'un commutateur , sauf si le dictionnaire est utilisé par plusieurs méthodes. La façon dont les objets de dessin et les interfaces sont utilisés, toutes les méthodes Draw pourraient facilement être des méthodes statiques sur une classe.

Pour répondre à la question exacte, on pourrait utiliser Dictionary.TryGetValue :

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

Toutes les méthodes Draw peuvent devenir des méthodes statiques puisqu'elles n'utilisent aucun membre d'instance:

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

Sinon:

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)
};

Mise à jour

BTW que la syntaxe montre, il se passe quelque chose de bizarre . L'utilisation de types au lieu d'une énumération aurait empêché de dessiner des cercles lorsqu'un cercle est demandé

Nous aurions besoin d'en savoir plus sur l'origine de Shapes , ou pourquoi les interfaces sont utilisées pour créer une meilleure implémentation, et évitez d'utiliser la mauvaise implémentation

Juste pour le plaisir d'ailleurs, on pourrait utiliser les expressions de commutation de C # 8 avec les interfaces:

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


0 commentaires

1
votes

Jetez un œil à vos méthodes Draw : changent-elles réellement quelque chose sur l'une des classes DrawTriangle , DrawRectangle , etc.? Sinon, vous n'avez pas besoin de créer une nouvelle instance à chaque fois que vous voulez dessiner quelque chose. Au lieu de cela, vous pouvez simplement stocker une seule instance de DrawTriangle dans votre dictionnaire Mapper :

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

Ensuite, vous récupérez le dictionnaire approprié IDrawingObject pour un Shapes donné, et appelez sa méthode Draw :

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

Si, pour une raison quelconque, vous avez besoin de créer un nouveau DrawTriangle au moment même où vous voulez dessiner un triangle, vous pouvez à la place mettre Func code > délégués dans votre dictionnaire, mais appelez toujours IDrawingObject.Draw dans votre méthode Draw statique:

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

Ensuite:

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


0 commentaires

0
votes

C'est pratiquement ce que vous avez!

vous pouvez faire en sorte que votre mauvaise énumération ressemble à un objet "correct" en utilisant des méthodes d'extension. (bien que ce soit un mirage .... exposé en ayant l'exception dans la méthode MkShape)

donc la grande objection à cela n'est pas de type sécurisé, si vous ajoutez de nouvelles énumérations et oubliez de mettre à jour MkShape, votre code tombera en panne ... un langage fonctionnel comme F #, Scala, etc. vous avertirait que c'était mauvais.

votre dictionnaire ne fait rien sauf permuter un peu de complexité pour un petit avantage de performance de l'instruction switch .. .c'est à dire ne vous dérangez pas à moins que vous n'ayez des centaines de valeurs d'énumération, cela peut même vous coûter des performances (pour les petits 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

Je soupçonne qu'il peut être utile de stocker la couleur dans la forme, mais si vous êtes lié à l'énumération, alors vous êtes lié à la simple connaissance de la forme et rien d'autre


0 commentaires