web-dev-qa-db-fra.com

Étude de cas de polymorphisme - Modèle de conception pour la morphing?

Imaginez deux classes:

class Person {
    MarriedPerson marry(Person other) {...}
}
class MarriedPerson extends Person {
}

L'idée est qu'un Person est automatiquement "morphed" à taper MarriedPerson lorsque la méthode marry est appelée. Pour cette raison, vous auriez besoin de créer une instance de MarriedPerson et de transférer les données de l'objet initial Person. L'objet Person serait toujours existé, ce qui est mauvais. Une autre façon serait l'application d'un modèle de conception ...

  • Quel modèle de conception correspondrait au problème?
  • Y a-t-il de meilleurs moyens de modéliser cette relation?
  • (Off-thème) Y a-t-il des fonctions =Java pour définir un personnalisé a-t-il été abattu?

Ce ne sont pas des devoirs. Je viens de recevoir curieux après avoir commenté ici .

28
akuzminykh

Je sais que cela est censé être un exemple assez artificiel pour démontrer l'idée en jeu. Mais je dirais qu'en règle générale, vous souhaitez épuiser toutes les options possibles avant de compter sur le polymorphisme sous-type. L'utilisation de la composition est une meilleure solution ici simplement basée sur le domaine.

Le mariage ne définit pas de "type" de personne plus que la race, la richesse, les intérêts, etc. Ainsi, l'utilisation de polymorphisme sous-type pour modéliser ce cas n'est pas approprié. Sur un niveau plus technique, la plupart des langues ne supportent que l'héritage unique. Donc, si vous avez eu des cours pour MarriedPerson, PacificIslanderPerson, et WealthyPerson, vous n'auriez aucun moyen de "composer" ceux-ci ensemble pour décrire un riche île pacifique marié.

Au lieu de cela, vous utiliseriez une composition simple dans votre classe Person.

public class Person {
  public MarriageStatus marriageStatus;
  public Race race;
  public Wealth wealth;
}

Ici, MarriageStatus, Race, et Wealth peut tous être une seule responsabilité et probablement assez simple. Un exemple de marriagestatus pourrait être.

public class MarriageStatus {
  public Datetime anniversary;
  public Person husband;
  public Person wife;

  // TODO: In the future the stakeholder would like to support polyamory
//  public List<Person> spouses;
}

Si vous utilisez un langage de programmation comme Haskell ou Rust avec des traits (typeclasses dans Haskell Parlance), vous pouvez faire une personne agir automatiquement comme un marringperson de la perspective de la fonction. Dans un OOP plus traditionnel, votre logique commerciale fonctionnerait simplement sur MarriageStatus, Race et Wealth objets. Ils n'accepteraient que un Person lors de l'interaction entre ces trois propriétés composées sont nécessaires.

De cette façon, vous vous êtes conçu de la relation récursive et de tous les pièges de celui-ci.


Je m'excuse si j'ai complètement oublié le point de votre question. Plus précisément, vous dites

L'objet personne existerait toujours, ce qui est mauvais.

Je ne pense pas que ce soit nécessairement vrai. Si vous retournez un objet marringperson et aucune référence à la "personne" originale n'est autour, le collecteur des ordures viendra simplement et retirera l'ancien objet "personne". Je pourrais vous mal comprendre vous-même.

90

Le person, marié ou non est toujours la même personne. Morphing-le à un autre type de person avec une copie remplacer une approche ne conserve pas cette identité fondamentale.

Plusieurs autres alternatives pourraient être envisagées:

  • le modèle d'état comme déjà suggéré par candied_orange permet de conserver la même personne, mais d'utiliser composition sur héritage pour modifier le comportement de cette personne en fonction de l'état.
  • le modèle de décorateur est une autre alternative pertinente. Il utilise la composition sur l'héritage pour ajouter des responsabilités à un objet. C'est très similaire à votre solution, mais au lieu de la morphing, vous créeriez un décorateur qui fait toujours référence à la personne initiale. Mais il partage l'inconvénient de votre solution: l'identité unique de la personne est perdue d'une manière ou d'une autre.
  • une autre approche serait de garder une personne célibataire et de voir la personne mariée comme un rôle de la personne normale associée à une relation. Le rôle agirait comme une sorte de modèle de stratégie . Cette approche n'est pas aussi flexible que le décorateur, et ce n'est pas aussi facile de changer le comportement que l'état. Mais cela vaut la peine d'explorer.
  • une alternative finale pourrait apparaître sur-ingénieur si vous souhaitez simplement ajouter une enquête sur le conjoint, mais cela pourrait donner la flexibilité ultime: le Système de composant d'entité : La personne n'irait que le comportement humain principal. La personne serait un conteneur pour les composants. Chaque composant serait chargé d'un groupe de propriétés et de comportements. Je suis d'accord, c'est plus complexe, mais de manière intéressante, c'est la seule des quatre alternatives susceptibles de faire face à une personne qui a divorcé, a été remariée, mais est toujours obligatoire pour le premier conjoint.
22
Christophe

enter image description here

Le modèle d'état pourrait être appliqué ici. Mais s'il existe un meilleur moyen de modéliser la relation dépend entièrement des besoins de l'utilisation du code. Vous n'avez pas offert d'utilisation de code pour que tout ce que je puisse faire est d'imaginer des fonctions telles que divorce() et fileTaxes(). Mais je ne sais pas si ceux-ci correspondent à vos besoins.

Je préfère grandement faire mon travail de conception du point de vue de l'utilisation. En commençant par ces deux classes et essayant d'imaginer que toutes leurs utilisations invitent tout simplement beaucoup de sutupes.

Je peux imaginer d'autres modèles appliqués à différents besoins qui pourraient également avoir ces classes mais ne voient pas une fin raisonnable à cela, donc je pense que je vais arrêter ici.

9
candied_orange

Est-ce que le marrierPerson présente vraiment un comportement différent que la personne ne pouvait pas éventuellement mettre en œuvre? Si non, une classe n'est pas nécessaire.

En outre, je éviterais de mettre l'état matrimonial d'une personne à l'intérieur de la personne. Il suffit d'avoir un objet de mariage avec les objets de deux personnes impliqués et mettez ce mariage dans un grand livre approprié. De cette façon, il est plus facile de garantir des données cohérentes.

7
Kafein

Je suis d'accord avec les autres réponses que le polymorphisme sous-type est la mauvaise solution à ce problème spécifique, et plus généralement, ce n'est généralement pas la meilleure solution.

Cela dit, là est une solution canonique pour ce problème à l'aide de polymorphisme sous-type, qui peut être utile dans d'autres situations. La solution a d'abord été décrite formellement par James Coplien et passe par une variété de noms, mais est le plus souvent connu sous le nom de Enveloppe/lettre Idiom (car il ressemble à une enveloppe détenant une lettre pouvant être retirée et échangée pour une lettre avec des contenus différents, adressée à la même personne).

Les deux classes (Person et MarriedPerson dans votre exemple) sont des implémentations concrètes ("lettres") de la même classe de base abstraite, et les deux sont accessibles via un enveloppe = La classe qui implémente généralement également la même interface et qui apparaît en interne à une instance d'une classe de lettres.

Pour rendre les relations plus claire, j'ai renommé Person à UnmarriedPerson dans les éléments suivants et utilisé Person comme nom de la classe d'enveloppe.

interface AbstractPerson {
    String name();
    boolean married();
}

class UnmarriedPerson implements AbstractPerson {
    private final String name;

    UnmarriedPerson(final String name) { this.name = name; }

    public String name() { return name; }
    public boolean married() { return false; }
}

class MarriedPerson implements AbstractPerson {
    private final String name;

    MarriedPerson(final String name) { this.name = name; }

    public String name() { return name; }
    public boolean married() { return true; }
}

class Person implements AbstractPerson {
    private AbstractPerson handle;

    public Person(final String name) { handle = new UnmarriedPerson(name); }

    public String name() { return handle.name(); }
    public boolean married() { return handle.married(); }

    public void marry(Person other) {
        if (married() || other.married()) {
            throw new IllegalStateException("already married");
        }

        setMarried();
        other.setMarried();
    }

    private void setMarried() { handle = new MarriedPerson(name()); }
}

class Main {
    static void print(final Person p) {
        System.out.printf("%s is %s\n", p.name(), p.married() ? "married" : "single");
    }

    public static void main(String[] args) {
        final Person sue = new Person("Sue");
        final Person andy = new Person("Andy");

        print(sue);        // “Sue is single”
        print(andy);       // “Andy is single”
        sue.marry(andy);
        print(sue);        // “Sue is married”
        print(andy);       // “Andy is married”
    }
}

Mais je veux réitérer que vous ne devrait pas Utilisez cette solution dans ce cas particulier, pour toutes les raisons mentionnées dans d'autres réponses. Il est assez difficile de penser aux exemples concrets où l'enveloppe/lettre idiome est une bonne solution: je ne recommanderais probablement jamais d'essayer de modéliser les "situations réelles" avec elle.

Au lieu de cela, une utilisation authentique est quand un type de données abstraite comporte plusieurs implémentations qui doivent être échangées de manière dynamique. Comme une classe Matrix pouvant être implémentée soit comme un SparseMatrix ou a DenseMatrix, en fonction des caractéristiques des données, et lorsque la stratégie de mise en œuvre optimale pourrait changer après l'exécution opérations sur la matrice.

1
Konrad Rudolph

On pourrait soutenir que lorsqu'une personne se marie, une personne mariée qui est une copie exacte de la personne d'origine est créée et l'original est détruit.

Heureusement, le monde de l'objet axé sur les objets est beaucoup plus logique que le monde de la vie réelle. Dans ce cas, je ne vois absolument aucune raison pour laquelle la classe d'AndroidPerson existerait. Qu'est-ce que c'est exactement que cette classe fait différemment que la classe de la personne?

Le sous-traitant juste pour différencier les états de la classe d'origine est généralement une idée très très mauvaise. Il suffit d'ajouter l'attribut qui doit être remplacé par la classe d'origine et vous êtes prêt à partir.

1
Vladimir Stokic

Si vous voulez que la personne change de comportement une fois mariée, j'utiliserais le modèle de visiteur pour mettre en œuvre ce comportement. Ensuite, l'acte de mariage changerait quel visiteur a mis en œuvre le comportement approprié.

Il y a certainement des moments où un objet veut changer de comportement de manière dynamique. Mais cela peut être fait à travers une stratégie ou un motif de visiteur.

0
Kirk