Sous-classes réservées aux constructeurs: s'agit-il d'un anti-modèle?

37

Je discutais avec un collègue et nous avons fini par avoir des intuitions contradictoires quant à l'objectif du sous-classement. Mon intuition est que si l'une des fonctions principales d'une sous-classe est d'exprimer une plage limitée de valeurs possibles de son parent, il ne devrait probablement pas s'agir d'une sous-classe. Il a plaidé pour l'intuition opposée: cette sous-classe représente un objet plus "spécifique", et donc une relation de sous-classe est plus appropriée.

Pour mettre mon intuition plus concrètement, je pense que si j'ai une sous-classe qui étend une classe parente mais que le seul code que cette sous-classe écrase est un constructeur ce qui était vraiment nécessaire était une méthode d'assistance.

Par exemple, considérons cette classe un peu réelle:

public class DataHelperBuilder
{
    public string DatabaseEngine { get; set; }
    public string ConnectionString { get; set; }

    public DataHelperBuilder(string databaseEngine, string connectionString)
    {
        DatabaseEngine = databaseEngine;
        ConnectionString = connectionString;
    }

    // Other optional "DataHelper" configuration settings omitted

    public DataHelper CreateDataHelper()
    {
        Type dataHelperType = DatabaseEngineTypeHelper.GetType(DatabaseEngine);
        DataHelper dh = (DataHelper)Activator.CreateInstance(dataHelperType);
        dh.SetConnectionString(ConnectionString);

        // Omitted some code that applies decorators to the returned object
        // based on omitted configuration settings

        return dh;
    }
}

Il affirme qu'il serait tout à fait approprié d'avoir une sous-classe comme celle-ci:

public class SystemDataHelperBuilder
{
    public SystemDataHelperBuilder()
        : base(Configuration.GetSystemDatabaseEngine(),
               Configuration.GetSystemConnectionString())
    {
    }
 }

Alors, la question:

  1. Parmi les personnes qui parlent de modèles de conception, laquelle de ces intuitions est correcte? Le sous-classement décrit ci-dessus est-il un anti-motif?
  2. Si c'est un anti-motif, quel est son nom?

Je m'excuse si cela s'avère être une réponse facile à comprendre; mes recherches sur google ont pour la plupart retourné des informations sur l'anti-motif du constructeur télescopique et pas vraiment ce que je cherchais.

À peine savoir
la source
2
Je considère le mot "Helper" dans un nom comme un antipattern. C'est comme lire un essai avec des références constantes à un truc et à un truc. Dites ce qu'il est . Est-ce une usine? Constructeur? Pour moi, cela ressemble à un processus de réflexion paresseux, et cela encourage la violation du principe de responsabilité unique, puisqu'un nom sémantique approprié le dirigerait. Je suis d'accord avec les autres électeurs et vous devriez accepter la réponse de @ lxrec. La création d'une sous-classe pour une méthode d'usine est totalement inutile, sauf si vous ne pouvez pas faire confiance aux utilisateurs pour qu'ils transmettent les arguments corrects spécifiés dans la documentation. Dans ce cas, obtenez de nouveaux utilisateurs.
Aaron Hall
Je suis tout à fait d'accord avec la réponse de @ Ixrec. Après tout, sa réponse dit que j'avais toujours raison et que mon collègue avait tort. ;-) Mais sérieusement, c'est exactement pourquoi je me suis senti bizarre de l'accepter; Je pensais que je pourrais être accusé de partialité. Mais je suis nouveau pour les programmeurs StackExchange; Je serais prêt à l'accepter si j'étais assuré que ce ne serait pas une violation de l'étiquette.
HardlyKnowEm
1
Non, on s'attend à ce que vous acceptiez la réponse qui vous semble la plus précieuse. Celui que la communauté juge le plus précieux à ce jour est celui d'Ixrec, de loin plus de 3 contre 1. Ils s'attendent donc à ce que vous l'acceptiez. Dans cette communauté, un intervenant qui accepte la mauvaise réponse exprime très peu de frustration, mais de nombreuses frustrations s'expriment dans les interrogateurs qui n'acceptent jamais une réponse. Notez que personne ne se plaint de la réponse acceptée, mais se plaint du vote, qui semble se corriger: stackoverflow.com/q/2052390/541136
Aaron Hall
Très bien, je vais l'accepter. Merci d'avoir pris le temps de me renseigner sur les normes communautaires ici.
HardlyKnowEm

Réponses:

54

Si tout ce que vous voulez, c'est créer une classe X avec certains arguments, le sous-classement est un moyen étrange d'exprimer cette intention, car vous n'utilisez aucune des fonctionnalités offertes par les classes et l'héritage. Ce n'est pas vraiment un anti-modèle, c'est juste étrange et un peu inutile (sauf si vous avez d'autres raisons pour cela). Une méthode plus naturelle pour exprimer cette intention serait une méthode d'usine , qui dans ce cas est un nom sophistiqué pour votre "méthode d'assistance".

En ce qui concerne l'intuition générale, "plus spécifique" et "une plage limitée" sont des façons potentiellement néfastes de penser aux sous-classes, car elles impliquent toutes deux que faire de Square une sous-classe de Rectangle soit une bonne idée. Sans me fier à quelque chose de formel tel que LSP, je dirais qu'une meilleure classe est qu'une sous-classe fournit une implémentation de l'interface de base ou étend l'interface pour ajouter de nouvelles fonctionnalités.

Ixrec
la source
2
Cependant, les méthodes Factory ne vous permettent pas d'appliquer certains comportements (régis par des arguments) dans votre base de code. Et parfois, il est utile d’indiquer explicitement que cette classe / méthode / champ prend SystemDataHelperBuilderspécifiquement.
Telastyn
3
Ah, l'argument carré vs rectangle était exactement ce que je cherchais, je pense. Merci!
HardlyKnowEm
7
Bien sûr, avoir carré soit une sous-classe de rectangle peut être acceptable s’ils sont immuables. Vous devez vraiment regarder le LSP.
David Conrad
10
Le problème avec le carré / rectangle est que les formes géométriques sont immuables. Vous pouvez toujours utiliser un carré partout où un rectangle a du sens, mais le redimensionnement n’est pas quelque chose que font les carrés ou les rectangles.
Doval
3
@nha LSP = Principe de substitution de Liskov
Ixrec le
16

Laquelle de ces intuitions est correcte?

Votre collègue est correct (en supposant les systèmes de type standard).

Pensez-y, les classes représentent des valeurs légales possibles. Si la classe Aa un champ d'un octet F, vous pourriez être enclin à penser qu'il Aa 256 valeurs légales, mais cette intuition est incorrecte. Arestreint "toute permutation de valeurs" à "le champ doit Fêtre compris entre 0 et 255".

Si vous étendez Aavec Bqui a un autre champ d'octet FF, c'est ajouter des restrictions . Au lieu de "valeur où Fest un octet", vous avez "valeur où Fest un octet && FFest également un octet". Puisque vous conservez les anciennes règles, tout ce qui a fonctionné pour le type de base fonctionne toujours pour votre sous-type. Mais puisque vous ajoutez des règles, vous vous spécialisez davantage loin de "pourrait être n'importe quoi".

Ou de penser à une autre façon, on suppose que Aavait un tas de sous - types: B, Cet D. Une variable de type Apeut être n'importe lequel de ces sous-types, mais une variable de type Best plus spécifique. Il (dans la plupart des systèmes de types) ne peut pas être un Cou un D.

Est-ce un anti-modèle?

Enh? Avoir des objets spécialisés est utile et ne nuit pas clairement au code. Atteindre ce résultat en utilisant le sous-typage est peut-être un peu discutable, mais dépend des outils à votre disposition. Même avec de meilleurs outils, cette mise en œuvre est simple, directe et robuste.

Je ne considérerais pas cela comme un anti-modèle car ce n’est pas clairement faux et mauvais dans aucune situation.

Telastyn
la source
5
"Plus de règles signifie moins de valeurs possibles, plus spécifiques." Je ne suis vraiment pas cela. Le type a byte and bytedéfinitivement plus de valeurs que le type byte; 256 fois plus. Cela dit, je ne pense pas que vous puissiez associer des règles à un nombre d'éléments (ou à une cardinalité si vous voulez être précis). Il tombe en panne lorsque vous considérez des ensembles infinis. Il y a autant de nombres pairs que de nombres entiers, mais savoir qu'un nombre est pair est toujours une restriction / me donne plus d'informations que le simple fait de savoir que c'est un nombre entier! On pourrait en dire autant des nombres naturels.
Doval
6
@Telastyn Nous nous éloignons du sujet, mais il est prouvé que la cardinalité (généralement la "taille") de l'ensemble des entiers pairs est la même que celle de tous les entiers, voire de tous les nombres rationnels. C'est vraiment cool. Voir math.grinnell.edu/~miletijo/museum/infinite.html
HardlyKnowEm
2
La théorie des ensembles est assez peu intuitive. Vous pouvez mettre les entiers et les nuls dans une correspondance individuelle (par exemple, en utilisant la fonction n -> 2n). Ce n'est pas toujours possible. vous ne pouvez pas mapper les entiers sur les réels, de sorte que l'ensemble des réels est "plus grand" que les entiers. Mais vous pouvez mapper l'intervalle (réel) [0, 1] sur l'ensemble de tous les réels, ce qui leur donne la même "taille". Les sous-ensembles sont définis comme "chaque élément de Aest également un élément de B", précisément parce qu'une fois que vos ensembles deviennent infinis, il se peut qu'ils aient la même cardinalité mais qu'ils aient des éléments différents.
Doval
1
Plus en rapport avec le sujet à traiter, l'exemple que vous donnez est une contrainte qui, en termes de programmation orientée objet, étend réellement la "fonctionnalité" en termes de champ supplémentaire. Je parle de sous-classes qui n’étendent pas les fonctionnalités, comme le problème cercle-ellipse.
HardlyKnowEm
1
@Doval: Il y a plus d'une signification possible de "moins" - c'est-à-dire plus d'une relation d'ordre sur des ensembles. La relation "est un sous-ensemble de" donne un ordre bien défini (partiel) sur les ensembles. De plus, "plus de règles" peut signifier "tous les éléments de l'ensemble satisfont plus (c'est-à-dire un sur-ensemble de) propositions (à partir d'un certain ensemble)". Avec ces définitions, "plus de règles" signifie en effet "moins de valeurs". C'est en gros l'approche adoptée par un certain nombre de modèles sémantiques de programmes informatiques, par exemple les domaines Scott.
psmears
12

Non, ce n'est pas un anti-modèle. Je peux penser à un certain nombre de cas d'utilisation pratiques pour cela:

  1. Si vous souhaitez utiliser la vérification de la compilation pour vous assurer que les collections d'objets ne sont conformes qu'à une sous-classe particulière. Par exemple, si vous en avez MySQLDaoet SqliteDaodans votre système, mais pour une raison quelconque, vous voulez vous assurer qu'une collection ne contient que des données provenant d'une source, si vous utilisez des sous-classes telles que vous les décrivez, vous pouvez demander au compilateur de vérifier cette exactitude particulière. D'autre part, si vous stockez la source de données sous forme de champ, cela devient une vérification à l'exécution.
  2. Mon application actuelle a une relation de un à un entre AlgorithmConfiguration+ ProductClasset AlgorithmInstance. En d'autres termes, si vous configurez FooAlgorithmpour ProductClassdans le fichier de propriétés, vous ne pouvez en obtenir qu'un FooAlgorithmpour cette classe de produits. La seule façon d'obtenir deux FooAlgorithms pour une donnée ProductClassest de créer une sous-classe FooBarAlgorithm. Cela convient, car le fichier de propriétés est facile à utiliser (il n’est pas nécessaire de recourir à la syntaxe pour de nombreuses configurations différentes dans une section du fichier de propriétés) et il est très rare qu’il y ait plus d’une instance pour une classe de produit donnée.
  3. La réponse de @ Telastyn donne un autre bon exemple.

Bottom Line: Il n'y a pas vraiment de mal à faire cela. Un anti-motif est défini comme étant:

Un anti-motif (ou anti-motif) est une réponse courante à un problème récurrent, généralement inefficace et risquant d'être hautement contre-productif.

Il n'y a aucun risque d'être hautement contre-productif ici, donc ce n'est pas un anti-modèle.

durron597
la source
2
Oui, je pense que si je devais reformuler la question, je dirais "odeur de code". Ce n’est pas assez grave pour justifier d’avertir la prochaine génération de jeunes développeurs de l’éviter, mais si vous le voyez bien, cela peut indiquer qu’il ya un problème dans la conception globale, mais peut aussi être correct.
HardlyKnowEm
Le point 1 est juste sur la cible. Je n'ai pas vraiment compris le point 2, mais je suis sûr que c'est tout aussi bien ... :)
Phil
9

Si quelque chose est un motif ou un antipattern dépend de manière significative de la langue et de l'environnement dans lesquels vous écrivez. Par exemple, les forboucles sont un motif en assembleur, C et des langages similaires et un anti-motif en lisp.

Laissez-moi vous raconter une histoire d'un code que j'ai écrit il y a longtemps ...

C'était dans un langage appelé LPC et implémenté un cadre pour lancer des sorts dans un jeu. Vous disposiez d'une super-classe de sorts, qui comportait une sous-classe de sorts de combat permettant d'effectuer certaines vérifications, puis une sous-classe pour les sorts à dégâts directs à une cible, qui étaient ensuite classés dans les sorts individuels - missile magique, éclair, etc.

Le fonctionnement de LPC était dû au fait que vous aviez un objet principal qui était un Singleton. Avant de partir, vous avez eu Singleton, ce n’était pas du tout. On n'a pas fait de copies des sorts, mais plutôt invoqué les méthodes (effectivement) statiques dans les sorts. Le code pour missile magique ressemblait à:

inherit "spells/direct";

void init() {
  ::init();
  damage = 10;
  cost = 3;
  delay = 1.0;
  caster_message = "You cast a magic missile at ${target}";
  target_message = "You are hit with a magic missile cast by ${caster}";
}

Et vous voyez ça? C'est une classe de constructeur seulement. Il a défini certaines valeurs qui se trouvaient dans sa classe abstraite parente (non, il ne devrait pas utiliser de séparateur - c’est une immuable) et c’est tout. Cela a bien fonctionné . C'était facile à coder, à étendre et à comprendre.

Ce type de modèle se traduit dans d'autres situations où vous avez une sous-classe qui étend une classe abstraite et définit quelques valeurs qui lui sont propres, mais toutes les fonctionnalités sont dans la classe abstraite dont elle hérite. Jetez un coup d'oeil sur StringBuilder en Java pour un exemple - pas seulement un constructeur, mais je suis pressé de trouver beaucoup de logique dans les méthodes elles-mêmes (tout se trouve dans AbstractStringBuilder).

Ce n'est pas une mauvaise chose, et ce n'est certainement pas un anti-modèle (et dans certaines langues, ce peut être un modèle en soi).

Communauté
la source
2

L'utilisation la plus courante de telles sous-classes que je puisse imaginer est la hiérarchie des exceptions, bien qu'il s'agisse d'un cas dégénéré dans lequel nous ne définirions généralement rien dans la classe, tant que le langage nous permet d'hériter des constructeurs. Nous utilisons l'héritage pour exprimer qu'il ReadErrors'agit d'un cas particulier de IOError, etc., mais nous ReadErrorn'avons pas besoin de remplacer les méthodes de IOError.

Nous faisons cela parce que les exceptions sont détectées en vérifiant leur type. Ainsi, nous devons encoder la spécialisation dans le type afin que quelqu'un ne puisse attraper que ReadErrorsans tout capturer IOError, s'il le souhaite.

Normalement, vérifier les types de choses est terriblement mauvais et nous essayons de l'éviter. Si nous parvenons à l'éviter, il n'y a alors aucun besoin essentiel d'exprimer des spécialisations dans le système de types.

Cela peut néanmoins être utile, par exemple si le nom du type apparaît dans la forme de chaîne journalisée de l'objet: il s'agit d'un langage et éventuellement d'un framework. Vous pouvez en principe remplacer le nom du type dans un tel système avec un appel à une méthode de la classe, dans ce cas , nous aurions remplacer plus que le constructeur dans SystemDataHelperBuilder, nous avions également remplacer loggingname(). Ainsi, s'il existe une telle journalisation dans votre système, vous pouvez imaginer que, bien que votre classe ne remplace littéralement que le constructeur, "moralement", elle redéfinit également le nom de la classe. Ainsi, à des fins pratiques, il ne s'agit pas d'une sous-classe réservée aux constructeurs. Il a un comportement différent souhaitable, ça '

Donc, je dirais que son code peut être une bonne idée s'il y a du code ailleurs qui vérifie d'une manière ou d'une autre les instances de SystemDataHelperBuilder, soit explicitement avec une branche (comme les branches capturant des exceptions basées sur le type) ou peut-être parce qu'il y a un code générique qui finira par en utilisant le nom SystemDataHelperBuilderde manière utile (même s'il ne s'agit que de la journalisation).

Cependant, utiliser des types pour des cas particuliers crée une certaine confusion, car si ImmutableRectangle(1,1)et ImmutableSquare(1)se comporter différemment de quelque manière que ce soit, on finira par se demander pourquoi et peut-être souhaiter que ce ne soit pas le cas. Contrairement aux hiérarchies d'exceptions, vous vérifiez si un rectangle est un carré en vérifiant si height == width, pas avec instanceof. Dans votre exemple, il existe une différence entre a SystemDataHelperBuilderet DataHelperBuildercréé avec exactement les mêmes arguments, ce qui peut poser problème. Par conséquent, une fonction permettant de retourner un objet créé avec les arguments "corrects" me semble normalement être la bonne chose à faire: la sous-classe entraîne deux représentations différentes de la "même" chose, et cela n'aide que si vous faites quelque chose que vous faites. normalement essayer de ne pas faire.

Notez que je ne parle pas en termes de modèles de conception et d'anti-modèles, car je ne pense pas qu'il soit possible de fournir une taxonomie de toutes les bonnes et de mauvaises idées qu'un programmeur ait jamais envisagées. Ainsi, toutes les idées ne sont pas un motif ou un anti-motif, il devient seulement que lorsque quelqu'un identifie qu'il se produit de manière répétée dans différents contextes et le nomme ;-) Je ne connais pas de nom particulier pour ce type de sous-classement.

Steve Jessop
la source
Je pouvais en voir les utilisations ImmutableSquareMatrix:ImmutableMatrix, à condition qu’il existe un moyen efficace de demander ImmutableMatrixà rendre son contenu aussi réel que ImmutableSquareMatrixpossible. Même si ImmutableSquareMatrixc’était fondamentalement un ImmutableMatrixconstructeur dont la hauteur et la largeur devaient correspondre, et dont la AsSquareMatrixméthode se retournerait tout simplement (plutôt que de construire, par exemple, un ImmutableSquareMatrixmême tableau de sauvegarde), une telle conception autoriserait la validation des paramètres au moment de la compilation pour les méthodes nécessitant un traitement carré. matrices
Supercat
@supercat: bon point, et dans l'exemple du questionneur s'il y a des fonctions qui doivent être passées a SystemDataHelperBuilder, et pas n'importe lesquelles DataHelperBuilder, alors il est logique de définir un type pour cela (et une interface, en supposant que vous traitiez DI de cette façon) . Dans votre exemple, il existe certaines opérations qu'une matrice carrée pourrait avoir comme un déterminant, mais votre argument est valable, même si le système n'a pas besoin de celles que vous souhaitez vérifier par type. .
Steve Jessop
... donc je suppose que ce que j'ai dit n'est pas tout à fait vrai, que "vous vérifiez si un rectangle est un carré en vérifiant si height == width, pas avec instanceof". Vous pourriez préférer quelque chose que vous pouvez affirmer statiquement.
Steve Jessop
J'aimerais qu'il y ait un mécanisme qui permettrait à quelque chose comme la syntaxe de constructeur d'invoquer des méthodes d'usine statiques. Le type de l'expression foo=new ImmutableMatrix(someReadableMatrix)est plus clair que celui de someReadableMatrix.AsImmutable()ou même ImmutableMatrix.Create(someReadableMatrix), mais ces dernières formes offrent des possibilités sémantiques utiles; si le constructeur peut dire que la matrice est carrée, il est possible de refléter son type, ce qui pourrait être plus propre que d'exiger du code en aval qui nécessite une matrice carrée pour créer une nouvelle instance d'objet.
Supercat
@supercat: Une petite chose que j'aime bien à propos de Python est l'absence d' newopérateur. Une classe est juste un objet appelable qui, lorsqu'il est appelé, renvoie (par défaut) une instance d'elle-même. Donc, si vous voulez préserver la cohérence de l'interface, il est parfaitement possible d'écrire une fonction fabrique dont le nom commence par une majuscule. Elle ressemble donc à un appel du constructeur. Non pas que je le fasse régulièrement avec les fonctions d'usine, mais c'est là quand vous en avez besoin.
Steve Jessop
2

La définition la plus stricte, orientée objet, d'une relation de sous-classe est appelée «est-a». En utilisant l'exemple traditionnel d'un carré et d'un rectangle, un carré «est un rectangle».

Par là, vous devriez utiliser le sous-classement pour toute situation dans laquelle vous pensez qu'un «est-un» est une relation significative pour votre code.

Dans votre cas spécifique de sous-classe avec uniquement un constructeur, vous devriez l’analyser dans une perspective «is-a». Il est clair qu'un SystemDataHelperBuilder «est un» DataHelperBuilder, donc le premier passage suggère que son utilisation est valide.

La question à laquelle vous devez répondre est de savoir si l’un des autres codes exploite cette relation «est-un». Un autre code tente-t-il de distinguer SystemDataHelperBuilders de la population générale de DataHelperBuilders? Si tel est le cas, la relation est tout à fait raisonnable et vous devriez conserver la sous-classe.

Cependant, si aucun autre code ne le fait, alors vous avez une relationhp qui pourrait être une relation «est-une» ou qui pourrait être implémentée avec une fabrique. Dans ce cas, vous pouvez aller dans les deux sens, mais je recommanderais une relation Usine plutôt qu’une relation «est-ce». Lors de l'analyse de code orienté objet écrit par une autre personne, la hiérarchie des classes est une source d'informations importante. Il fournit les relations «is-a» dont ils auront besoin pour comprendre le code. En conséquence, le fait de placer ce comportement dans une sous-classe favorise le comportement de "quelque chose que quelqu'un trouvera s'il approfondit les méthodes de la superclasse" à "quelque chose que tout le monde examinera avant même de se plonger dans le code". Citant Guido van Rossum, "le code est lu plus souvent qu'il n'est écrit", la baisse de la lisibilité de votre code pour les développeurs à venir est donc un lourd prix à payer. Je choisirais de ne pas payer si je pouvais utiliser une usine.

Cort Ammon - Rétablir Monica
la source
1

Un sous-type n'est jamais "mauvais" tant que vous pouvez toujours remplacer une instance de son supertype par une instance du sous-type et que tout fonctionne toujours correctement. Ceci vérifie tant que la sous-classe n'essaye jamais d'affaiblir les garanties offertes par le supertype. Cela peut donner des garanties plus fortes (plus spécifiques), de sorte que l'intuition de votre collègue est correcte.

Cela dit, la sous-classe que vous avez créée n’est probablement pas fausse (étant donné que je ne sais pas exactement ce qu’ils font, je ne peux pas le dire avec certitude). Vous devez vous méfier du problème de la classe de base fragile interfacedemandez-vous si cela vous serait bénéfique, mais c'est le cas de toutes les utilisations de l'héritage.

Doval
la source
1

Je pense que la solution pour répondre à cette question consiste à examiner celui-ci, un scénario d'utilisation particulier.

L'héritage est utilisé pour appliquer les options de configuration de la base de données, comme la chaîne de connexion.

Il s'agit d'un modèle anti car la classe enfreint le principe de responsabilité unique: elle se configure avec une source spécifique de cette configuration et non pas "Faire une chose et bien le faire". La configuration d'une classe ne doit pas être intégrée à la classe elle-même. Pour définir les chaînes de connexion à la base de données, cela a souvent été le cas au niveau de l'application au cours d'un événement quelconque lors du démarrage de l'application.

Greg Burghardt
la source
1

J'utilise assez régulièrement des sous-classes constructeurs pour exprimer différents concepts.

Par exemple, considérons la classe suivante:

public class Outputter
{
    private readonly IOutputMethod outputMethod;
    private readonly IDataMassager dataMassager;

    public Outputter(IOutputMethod outputMethod, IDataMassager dataMassager)
    {
        this.outputMethod = outputMethod;
        this.dataMassager = dataMassager;
    }

    public MassageDataAndOutput(string data)
    {
        this.outputMethod.Output(this.dataMassager.Massage(data));
    }
}

Nous pouvons ensuite créer une sous-classe:

public class CapitalizeAndPrintOutputter : Outputter
{
    public CapitalizeAndPrintOutputter()
        : base(new PrintOutputMethod(), new Capitalizer())
    {
    }
}

Vous pouvez ensuite utiliser un CapitalizeAndPrintOutputter n’importe où dans votre code et vous savez exactement quel type de sortie il s’agit. De plus, ce modèle facilite l'utilisation d'un conteneur DI pour créer cette classe. Si le conteneur DI connaît PrintOutputMethod et Capitalizer, il peut même créer automatiquement le CapitalizeAndPrintOutputter.

L'utilisation de ce modèle est vraiment très flexible et utile. Oui, vous pouvez utiliser les méthodes d'usine pour faire la même chose, mais vous perdez (du moins en C #) une partie de la puissance du système de typographie et l'utilisation des conteneurs DI devient de plus en plus lourde.

Stephen
la source
1

Permettez-moi de noter d’abord que lors de l’écriture du code, la sous-classe n’est pas plus spécifique que le parent, car les deux champs initialisés sont tous deux réglables par le code client.

Une instance de la sous-classe ne peut être distinguée qu'à l'aide d'un downcast.

Maintenant, si vous avez une conception dans laquelle les propriétés DatabaseEngineet ConnectionStringont été réimplémentées par la sous-classe à laquelle Configurationvous avez accédé , vous avez une contrainte qui rend plus logique la présence de la sous-classe.

Cela dit, "anti-pattern" est trop fort à mon goût; il n'y a rien de vraiment faux ici.

Keith
la source
0

Dans l'exemple que vous avez donné, votre partenaire a raison. Les deux constructeurs d'assistants de données sont des stratégies. L'un est plus spécifique que l'autre, mais ils sont tous deux interchangeables une fois initialisés.

Fondamentalement, si un client du datahelperbuilder devait recevoir le systemdatahelperbuilder, rien ne se casserait. Une autre option aurait été de faire en sorte que systemdatahelperbuilder soit une instance statique de la classe datahelperbuilder.

Michael Brown
la source