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 ...
Ce ne sont pas des devoirs. Je viens de recevoir curieux après avoir commenté ici .
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.
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 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.
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.
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.
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.
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.