Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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.

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.

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.

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.

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.

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.

Licensed under: CC-BY-SA with attribution
scroll top