9
votes

Modèle d'usine pour construire de nombreuses classes dérivées

J'ai un objet d'usine standallememeager code> pour générer des instances d'un objet code> code> objet pour un jeu que je bâtiment. Il y a beaucoup de défis. Les constructeurs pour chaque code> La dérivation de la classe code> sont différents, mais il existe une interface commune entre eux, définie dans la classe de base.

Quand j'appelle manager.Createchallenge () Code> , il renvoie une instance de Challenge Code>, qui est l'un des types dérivés. P>

Idéalement, je voudrais garder le code de la construction d'objets à l'intérieur de la classe dérivée elle-même. Tout le code relatif à cet objet est co-localisé. Exemple: P>

class Challenge {}

class ChallengeA : Challenge {
  public static Challenge MakeChallenge() {
    return new ChallengeA();
  }
}

class ChallengeB : Challenge {
  public static Challenge MakeChallenge() {
    return new ChallengeB();
  }
}


3 commentaires

Coller fortement à la conception que vous suggérez: Comment allez-vous identifier quelle classe à instancier dans sletherManager.createchaallenge () ? et; Si vous connaissez déjà la classe à instancier, pourquoi ne pouvez-vous pas simplement l'installer dans Createchalhange méthode?


Ce qui se passera-t-il est une méthode de création () de création () de création () dans ce cas et non aussi gérable que de rompre la construction aux classes concernées.


Votre conception est le meilleur ici design-sage. Je préfère ne pas mettre plusieurs méthodes d'usine statiques dans les classes de béton, cependant. Utilisez des constructeurs réels pour tout ce qui est la responsabilité des classes et la prise de décision et la construction de dépendances dans Createchallenge . Si CreatEchallenge devient trop longtemps à votre goût, extrayez les méthodes pour rendre l'intention plus claire. Si c'est vraiment long, extrayez-le dans sa propre classe. Ymmv


3 Réponses :


2
votes

Vous êtes "décentralisant" l'usine, de sorte que chaque sous-classe soit responsable de la création elle-même.

Plus couramment, vous auriez une usine centrale qui saurait sur les sous-types possibles et comment les construire (assez souvent, simplement en créant une nouvelle instance et en revenant cette instance saisie comme une interface commune ou une classe de base commune). Cette approche évite la question que vous avez actuellement. Je ne vois pas non plus d'avantages à votre approche actuelle. Vous ne gagnez actuellement aucune encapsulation ou de réutilisation de code sur la mise en œuvre la plus typique d'une usine.

Pour une référence supplémentaire, consultez

http://www.oodesign.com/factory-pattern.html < / p>


1 commentaires

YA, c'est une variation du scénario classique, principalement de contenir les détails de la construction à la classe dérivée pour la lisibilité.



23
votes

J'aime vraiment le modèle que vous décrivez et utilisez-le souvent. La façon dont j'aime le faire est: xxx

Ce modèle a de nombreuses propriétés agréables. Personne ne peut faire un nouveau défi car il est abstrait. Personne ne peut faire une classe dérivée car Challenge STR par défaut est privé. Personne ne peut obtenir à ChallestA ou HISTLINTB car ils sont privés. Vous définissez l'interface à Challenge et c'est la seule interface dont le client doit comprendre.

Lorsque le client veut un A , ils demandent défi pour un, et ils l'obtiennent. Ils n'ont pas besoin de s'inquiéter du fait que, derrière les scènes, A est implémenté par de défi . Ils viennent d'obtenir un défi qu'ils peuvent utiliser.


10 commentaires

J'aime l'idée de mettre en œuvre l'usine dans la classe de base abstraite elle-même.


Il y a un couple rédigé à cette approche, cependant. Étant donné que ChallestA et HISTERTB sont privés, vous ne pouvez pas les tester. Puisque vous n'utilisez pas abstrait usine , vous ne pouvez pas vous moquer de l'usine dans des tests d'unité. Multiples méthodes d'usine pour le même type signifie que vous mettez la décision de la classe à créer dans le code d'appel. La beauté d'une usine en ce qui me concerne est que vous mettez la décision de la mise en œuvre concrète de créer en une seule place. Ceci est fondamentalement le code hérité par conception.


Les défis et les défis @Bstenzel sont destinés à être utilisés par Interface Challenge. Donc, si vous obtenez un objet de contestear en appelant Challenge.Makea (), identique aux défis, vous pouvez tester les deux.


L'ajout de la déclaration partielle à la classe peut vous permettre de relever les défis et les défis dans des fichiers séparés dans la même assemblée. Une considération intéressante si A & B sont des classes non triviales.


Est-ce une mauvaise pratique de faire l'une de ces classes public afin que je puisse exposer des méthodes les autres n'auraient pas dû?


@Alexanderderck: Certaines personnes envisagent une classe nichée publique comme une mauvaise pratique; Les classes imbriquées se sentent un peu bizarre parce que les gens ne sont pas habitués à penser à une classe comme un membre de quelque chose d'autre (autre qu'un espace de noms). Je serais donc prudent d'adapter ce modèle de cette façon; Certains pourraient le trouver étrange.


@ERICLIPPERT Dans votre exemple, y a-t-il un moyen de déplacer chacune des classes privées contenues (par exemple, de ChamplillageA, Hatletb) à leurs propres fichiers .CS pourtant les avoir encore des implémentations partielles du défi de la classe abstraite contenant? En d'autres termes, je peux beaucoup de ces classes privées et pour aider à l'organisation du code, je souhaite peut-être les mettre chaque fichier dans leur propre dossier dans Visual Studio. Est-ce possible? Merci d'avance et merci pour tout ce que vous faites pour la communauté C #.


@ User1886710: Mais ce ne sont pas des implémentations partielles pour commencer. Ce sont des sous-classes. Si ce que vous demandez, c'est que vous puissiez dire Partial abstrait Class Challenge {Classe privée ...} Dans un fichier et Défi de classe abstrait partielle {Classe privée Hontb ... Dans un autre, assurez-vous de pouvoir le faire. En fait, je crois que nous faisons cela dans les sources de Roslyn.


@ Ericlippertt génial. Obtenu ça marche. Merci de clarifier. :)


Par design, le motif d'usine devrait permettre différentes implémentations de l'usine et, dans votre cas, vous avez fusionné les deux côtés en un, ce qui a fait le principe de découplage inexistant. En outre, le retourner de nouveaux défis (); Les déclarations sont toutes dans la classe abstraite elle-même, ce qui implique que la classe abstraite connaît tout sur ses descendants. Je ne pense pas que ce soit une bonne idée.



1
votes

Pas nécessairement la réponse que vous recherchez mais ... Vous pouvez utiliser la mise en œuvre suivante si vous pouvez vous éloigner de la méthode statique par classe.

using System;

public class Test
{
    public static void Main()
    {
        var c1 = ChallengeManager.CreateChallenge();
        var c2 = ChallengeManager.CreateChallenge();
        //var c = ChallengeManager.CreateChallenge<Challenage>(); // This statement won't compile
    }
}

public class ChallengeManager
{
    public static Challenage CreateChallenge()
    {
        // identify which challenge to instantiate. e.g. Challenage1
        var c = CreateChallenge<Challenage1>();
        return c;
    }

    private static Challenage CreateChallenge<T>() where T: Challenage, new()
    {
        return new T();
    }
}

public abstract class Challenage{}
public class Challenage1: Challenage{}
public class Challenage2: Challenage{}


3 commentaires

Belle approche pour utiliser une méthode paramétrée.


Avec cette approche, vous enfreignez le principe d'inversion de dépendance en apportant le code d'appel dépend des mises en œuvre concrètes. Vous ne gagnerez rien sur Nouveau Challenge1 () Lorsque vous appelez Createchallenge () . L'ensemble du point d'utilisation d'une usine est de créer des classes seulement dépend des abstractions.


Je suis d'accord, celui-ci n'a pas vraiment mis en œuvre le motif d'usine. Ma réponse (mise à jour) a été largement centrée autour de cette déclaration en question: Maintenant, mon appel à Saclallemanager.createchallenge n'a besoin que de décider de la classe à appeler Makecallenge () sur. La mise en œuvre de la construction est contenue par la classe elle-même. de cette façon, il élimine la nécessité de Makechallenge méthode.