28

Imagine two classes:

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

The idea is that a Person is automatically "morphed" to type MarriedPerson when the method marry is called on it. For this reason you would need to create an instance of MarriedPerson and transfer the data from the initial Person object. The Person object would still exist, which is bad. Another way would be the application of a design pattern ...

  • What design pattern would fit the problem?
  • Are there better ways to model this relationship?
  • (Off-topic) Are there Java features to define a custom downcast?

This is not homework. I just got curious after commenting here.

11
  • 2
    The Person object would still exist, which is bad -- No, a copy of an object representing a person in the state it existed at some moment in the past would still exist. Commented Apr 1, 2020 at 21:30
  • 4
    Ah where is the Rust when you need it?
    – Cthulhu
    Commented Apr 2, 2020 at 14:42
  • Strategy and/or state pattern - factor out the state that changes and just replace that. Commented Apr 2, 2020 at 15:02
  • 4
    an object does never change its type. so the design for questions like that is not good. whether an person is married or not should be a property of the type and not represented by the type. properties can change at runtime, types not.
    – anion
    Commented Apr 2, 2020 at 17:33
  • 2
    This is sometimes called the "become" (anti-)pattern, from the Smalltalk "become" primitive which changes the class of an object. Many consider this bad design.
    – Chris Dodd
    Commented Apr 15, 2020 at 20:06

7 Answers 7

90

I know that this is meant to be a fairly contrived example to demonstrate the idea at play. But I would say that as a rule of thumb, you want to exhaust every possible option before relying on Subtype polymorphism. Using composition is a better solution here just based on the domain.

Marriage does not define a "type" of person any more than race, wealth, interests, etc. So using subtype polymorphism to model this case isn't appropriate. On a more technical level, most languages only support single-inheritance. So if you did have classes for MarriedPerson, PacificIslanderPerson, and WealthyPerson, you would have no way to "compose" these together to describe a wealthy married Pacific-Islander.

Instead you would use simple composition within your Person class.

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

Here, MarriageStatus, Race, and Wealth can all be single-responsibility, and probably pretty simple. An example of MarriageStatus might be.

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

If you're using a programming language like Haskell or Rust with traits (typeclasses in Haskell parlance), then you can make Person automatically act like a MarriedPerson from the function's perspective. In a more traditional OOP languages, your business logic would simply work on MarriageStatus, Race, and Wealth objects. They would only accept a Person when interplay between those three composed properties are needed.

This way you've designed yourself out of the recursive relationship and all the pitfalls of it.


I apologize if I've entirely missed the point of your question. Specifically, you say

The Person object would still exist, which is bad.

I don't think that's necessarily true. If you return a MarriedPerson object and no references to the original "Person" are around, the garbage collector will just come along and remove the old "Person" object. I might be misunderstanding you though.

5
  • 2
    Thanks! I think your answer explains well what the misconception in my given solution is. It's important to differentiate between the type and the state of an entity. The related design patterns exist out of the motivation to model that. The idea behind "The Person object would still exist, which is bad." is that you need to manage this state of your program. There are two objects that are describing inherently the same entity but are in different states. This is redundant complexity IMO. ("wealthy married Pacific-Islander" made me lol btw. :-D)
    – akuzminykh
    Commented Apr 2, 2020 at 1:05
  • 1
    @user7761803 This way, where the MarriageStatus essentially models the Marriage itself, the husband and wife Person objects could share the same MarriageStatus object and there is a single source of through of their anniversary date for example.
    – RobCo
    Commented Apr 2, 2020 at 11:11
  • 2
    I'd avoid making a circular reference like this. A divorce for example requires the deletion or invalidation of the MarriageStatus as well as a change in both Persons. By keeping the MarriageStatus-es in a global MarriageRegister, you create a single source of truth.
    – Pelle
    Commented Apr 2, 2020 at 12:46
  • 1
    @Pelle This wouldn't require a change in both Person objects in almost every case I can think of. If they're all classes (i.e MarriageStatus isn't a record or struct), then as soon as you change the MarriageStatus, the two Persons are "automatically" updated, since they just hold references to the now-changed MarriageStatus. RobCo touched on that in his comment too. Commented Apr 2, 2020 at 14:58
  • @user7761803: Not if you consider the marriage status to be a navigational property, i.e. where two people both refer to the same marrage status (and it, in turn, refers to both of them). But yes, that does generally imply that marriage statuses are kept in a repository of their own and merely referenced/loaded for people. You wouldn't want to have to create two marriage statuses, one for each spouse, and have them refer to each other. That's just a breeding ground for inconsistencies (A says they're married to B, B says they're married to C, C is listed as single, ...)
    – Flater
    Commented Apr 2, 2020 at 15:21
22

The person, whether married or not is still the same person. Morphing it to another kind of person with a copy replace approach does not maintain this fundamental identity.

Several other alternatives could be considered:

  • the state pattern as already suggested by candied_orange allows to keep the same person, but use composition over inheritance to alter the behavior of that person depending on the state.
  • the decorator pattern is another relevant alternative. It uses composition over inheritance to add responsibilities to an object. It is very similar to your solution, but instead of morphing, you'd create a decorator that still refers to the initial person. But it shares the drawback of your solution: the single identity of the person is somehow lost.
  • another approach would be to keep a single person, and see the married person as a role of the normal person that is associated with a relationship. The role would act as kind of strategy pattern. This approach is not as flexible as the decorator, and it's not as easy to change the behavior as with the state. But it is worth exploring.
  • a final alternative could appear over-engineered if you just want to add some inquiry about the spouse, but could give the ultimate flexibility: the entity component system: the person would implement only the core human behavior. The person would be a container for components. Each component would be in charge of a cluster of properties and behaviors. I agree, it's more complex, but interestingly, it is the only of the four alternatives that could cope with a person that divorced, got remarried but still has obligation to the first spouse.
5
  • Sounds like a classic case for the Decorator pattern to me. Commented Apr 2, 2020 at 21:30
  • @MatthewFlynn Indeed: Yes, this was my first thought as well. And it works well for many requirements: friends and parents still know the person itself. The IRS and the spouse will know the married person (except for the year of the mariage where it knows both entities).
    – Christophe
    Commented Apr 2, 2020 at 21:50
  • However after a second thought, I came to the conclusion that it depends on the requirements. If the person is supposed to have different behavior when married, and consistently use this behavior whoever invokes it, the decorator would no longer fit the requirements, unless you would replace all the external references to the person with a reference to the decorator (could be achieved if using a handle/proxy). This is why I preferred to expose all the alternatives: each fulfils very well some kind of issues and each has some drawbacks. So up to OP to chose what best fits the needs ;-)
    – Christophe
    Commented Apr 2, 2020 at 21:51
  • 2
    Typing is more about how other objects see you than your actual internal behavior. If being a MarriedPerson means other objects expect extra things from you (e.g., spouse?) The decorator is the way to go. If a MarriedPerson is just a person who behaves differently, I'm with you that state makes more sense. Either way, this a good answer. Commented Apr 2, 2020 at 23:11
  • Really, decomposing to attributes -- of which Marriage or MarriageStatus is one -- is by far the best answer. I question whether the Marriage 'relationship role' approach constitutes a Strategy pattern, unless you model or decorate an extra MarriageRole object. I'm a big fan of Strategy and Decorator approaches for extensible behavior, but here the domain is relatively closed and seems questionable as to needing such flexibility. Any strategy, view or decorator should probably be specific to marriage-related topics ie. finer granularity of composition rather than global to Person.
    – Thomas W
    Commented Apr 17, 2020 at 6:55
9

enter image description here

The State Pattern could be applied here. But whether there is a better way to model the relationship depends entirely on the needs of the using code. You haven't offered any using code so all I can do is imagine functions like divorce() and fileTaxes(). But I've no idea if those match your needs.

I greatly prefer to do my design work from the point of view of use. Starting with these two classes and trying to imagine all of their uses just invites a lot of overdesign.

I can imagine other patterns applied to different needs that might also have these classes but don't see a reasonable end to that so I think i'll stop here.

1
  • Unless the model is solely to represent marriages, which almost no model would be, decomposing to attributes -- of which Marriage or MarriageStatus is one -- is by far the better answer. And having Person as the container for a MarriageStatus obviates the need for a separate 'State' pattern.
    – Thomas W
    Commented Apr 17, 2020 at 6:56
7

Does the MarriedPerson truly exhibit different behavior which the Person could not possibly implement? If no then a class is not necessary.

Besides, I'd avoid putting the marital status of a Person inside the Person. Just have a Marriage object with the two Person objects involved and put that Marriage in some appropriate ledger. That way it is easier to ensure coherent data.

1

One could argue that when a person gets married, a married person which is otherwise an exact copy of the original person is created, and the original is destroyed.

Luckily, object-oriented world oftentimes is much more logical than real-life world. In this case, I see absolutely no reason why the MarriedPerson class would exist. What is it exactly that this class does differently than Person class?

Subtyping just to differentiate between the states of the original class is generally a very, very bad idea. Just add the attribute that needs to be changed to the original class, and you are good to go.

1

I agree with the other answers that subtype polymorphism is the wrong solution to this specific problem, and even more generally it’s usually not the best solution.

That said, there is a canonical solution for this problem using subtype polymorphism, which may come in handy in other situations. The solution was first formally described by James Coplien and goes by a variety of names, but is most commonly known as envelope/letter idiom (because it resembles an envelope holding a letter that can be taken out and exchanged for a letter with different contents, addressed to the same person).

Both classes (Person and MarriedPerson in your example) are concrete implementations (“letters”) of the same abstract base class, and both are accessed via an envelope class that generally also implements the same interface, and which internally defers to an instance of a letter class.

To make the relationships clearer, I’ve renamed Person to UnmarriedPerson in the following, and used Person as the name of the envelope class.

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”
    }
}

But I want to reiterate that you should not use this solution in this particular case, for all the reasons mentioned in other answers. It’s fairly hard to think of concrete examples where the envelope/letter idiom is a good solution: I would probably never recommend trying to model “real-world situations” with it.

Instead, a genuine use is when an abstract data type has multiple implementations that need to be swapped out dynamically. Such as a Matrix class that can be implemented either as a SparseMatrix or a DenseMatrix, depending on the characteristics of the data, and where the optimal implementation strategy might change after performing operations on the matrix.

0

If you want the person to change behavior once married i would use the visitor pattern to implement that behavior. Then the act of marriage would switch which visitor implemented the appropriate behavior.

There are certainly times when an object wants to dynamically change behavior. But this can be done through either a strategy or visitor pattern.

Not the answer you're looking for? Browse other questions tagged or ask your own question.