Rex
Rex

Reputation: 157

How to join 2 unrelated classes into a single type without using inheretence

I have 2 classes: X and Y. None of them is related or have same properties. But there's another class Z that have setters and getters where the setter could accept of type X or Y and getter could return of type X or Y. I thought of having a parent class Parent for x,y and use it as a type for setters and getters .. but I don't think it's a good design and will break the whole meaning of inheretence. What can I do in such case? Other than using generic type 'T' for z class

Upvotes: 1

Views: 247

Answers (2)

Zabuzard
Zabuzard

Reputation: 25903

Disclaimer

As others have already stated, this is a sign of poor design in the first place and smells.

Consider redesigning your code such that you do not have the need to return two unrelated things at the same time.


Code

Anyways, here is some code that would implement a solution for this situation. We are using a Container class that provides getters. But only one of them works, hence there are also methods to check which of them it is. Such a class is typically called Union or Variant, because only one of the types is allowed to be contained.

public class Variant<X, Y> {
    public static <X, Y> Variant<X, Y> ofX(X x) {
        Objects.requireNonNull(x);
        return new Variant(x, null);
    }

    public static <X, Y> Variant<X, Y> ofY(Y y) {
        Objects.requireNonNull(y);
        return new Variant(null, y);
    }

    private final X x;
    private final Y y;

    private Variant(X x, Y y) {
        this.x = x;
        this.y = y;
    }

    public X getX() {
        if (x == null) {
            throw new IllegalStateException("Contains the other type");
        }
        return x;
    }

    public Y getY() {
        if (y == null) {
            throw new IllegalStateException("Contains the other type");
        }
        return y;
    }

    public boolean isX() {
        return x != null;
    }

    public boolean isY() {
        return y != null;
    }
}

Usage:

Variant<String, Integer> variant1 = Variant.ofX("hello");
System.out.println(variant1.getX()); // hello

Variant<String, Integer> variant2 = Variant.ofY(20);
System.out.println(variant2.getY()); // 20

Or in your specific example:

public Variant<String, Integer> foo() {
    ...
    if (...) {
        return Variant.ofX("hello");
    } else {
        return Variant.ofY(20);
    }
}

Notes

The usage of the static factory methods is on purpose as having two different constructors with X x and Y y only would yield to an identical signature after type erasure. So the two different method names are necessary.

In an ode to "Optional - The Mother of All Bikesheds", you might also want to consider renaming getX() and getY() to something that sounds more serious, like getXOrThrow()/getYOrThrow().

Upvotes: 1

Deadron
Deadron

Reputation: 5289

Its a good sign you probably need to rewrite code if a method sometimes returns different types. You should always return the same type.

Usually rearranging the code is enough, but not always, and that is when its common to implement some sort of container object that contains the nested objects purely for the purpose of returning them.

Upvotes: 2

Related Questions