1

Background

Suppose I have some set of fields which are related to each other I therefore make a class to gather them. Let us call this class Base. There are certain methods as well, which operate on these fields which will be common to all derived classes. Additionally, let us suppose we want Base and all its derived classes to be immutable.

In different contexts, these fields support additional operations, so I have different derived classes which inherit the fields and provide additional methods, depending on their context. Let us call these Derived1, Derived2, etc.

In certain scenarios, the program needs instances of a derived class, but the state of the fields must satisfy some condition. So I made a class RestrictedDerived1 which makes sure that the condition is satisfied (or changes the parameters to conform if it can) in the constructor before calling its base constructor, or throws an error otherwise.

Further, there are situations where I need even more conditions to be met, so I have SuperRestrictedDerived1. (Side note: given that some conditions are met, this class can more efficiently compute certain things, so it overrides some methods of Derived1.)

Problem

So far so good. The problem is that most of the methods of all these classes involve making another instance of some class in this hierarchy (not always the same as the one that the method was called on, but usually the same one) based on itself, but with some modifications which may involve somewhat complex computation (i.e. not just changing one field). For example one of the methods of Derived1 might look like:

public Derived1 foo(Base b) {
    TypeA fieldA = // calculations using this and b
    TypeB fieldB = // more calculations
    // ... calculate all fields in this way
    return new Derived1(fieldA, fieldB, /* ... */);
}

But then down the hierarchy RestrictedDerived1 needs this same function to return an instance of itself (obviously throwing an error if it can't be instantiated), so I'd need to override it like so:

@Override
public ResrictedDerived1 foo(Base b) {
    return new RestrictedDerived1(super.foo(b));
}

This requires a copy constructor, and unnecessarily allocating an intermediate object which will immediately destroyed.

Possible solution

An alternative solution I thought of was to pass a function to each of these methods which constructs some type of Base, and then the functions would look like this:

// In Derived1
public Derived1 foo(Base b, BaseCreator creator) {
    TypeA fieldA = // calculations using this and b
    TypeB fieldB = // more calculations
    // ... calculate all fields in this way
    return creator.create(fieldA, fieldB, /* ... */);
}

public Derived1 foo(Base b) {
    return foo(b, Derived1::create);
}

public static Derived1 create(TypeA fieldA, TypeB fieldB, /* ... */) {
    return new Derived1(fieldA, fieldB, /* ... */);
}

// In RestrictedDerived1
@Override
public ResrictedDerived1 foo(Base b) {
    return (RestrictedDerived1) foo(b, RestrictedDerived1::create);
}

public static RestrictedDerived1 create(TypeA fieldA, TypeB fieldB, /* ... */) {
    return new RestrictedDerived1(fieldA, fieldB, /* ... */);
}

My question

This works, however it feels "clunky" to me. My question is, is there some design pattern or concept or alternative design that would facilitate my situation?

I tried do use generics, but that got messy quick, and didn't work well for more than one level of inheritance.

By the way, the actual classes that these refer to is 3D points and vectors. I have a base called Triple with doubles x, y, and z (and some functions which take a lambda and apply them to each coordinate and construct a new Triple with the result). Then I have a derived class Point with some point related functions, and another derived class Vector with its functions. Then I have NonZeroVector (extends Vector) which is a vector that cannot be the zero vector (since other objects that need a vector sometimes need to be guaranteed that it's not the zero vector, and I don't want to have to check that everywhere). Further, I have NormalizedVector (extends NonZeroVector) which is guaranteed to have a length of 1, and will normalize itself upon construction.

1 Answer 1

1

MyType

This can be solved using a concept variously known as MyType, this type, or self type. The basic idea is that the MyType is the most-derived type at runtime. You can think of it as the dynamic type of this, but referred to statically (at "compile time").

Unfortunately, not many mainstream programming languages have MyTypes, but e.g. TypeScript does, and I was told Raku does as well.

In TypeScript, you could solve your problem by making the return type of foo the MyType (spelled this in TypeScript). It would look something like this:

class Base {
    constructor(public readonly fieldA: number, public readonly fieldB: string) {}

    foo(b: Base): this {
        return new this.constructor(this.fieldA + b.fieldA, this.fieldB + b.fieldB);
    }
}

class Derived1 extends Base {
    constructor(fieldA: number, fieldB: string, protected readonly repeat: number) {
        super(fieldA * repeat, fieldB.repeat(repeat));
    }

    override foo(b: Base): this {
        return new this.constructor(
            this.fieldA + b.fieldA, this.fieldB + b.fieldB, this.repeat
        );
    }
}

class RestrictedDerived1 extends Derived1 {
    constructor(fieldA: number, fieldB: string, repeat: number) {
        super(fieldA * repeat, fieldB.repeat(repeat), repeat);
        if (repeat >= 3) { 
            throw new RangeError(`repeat must be less than 3 but is ${repeat}`)
        }
    }
}

const a = new RestrictedDerived1(23, 'Hello', 2);
const b = new Base(42, ' World');

const restrictedDerived = a.foo(b); // Inferred type is RestrictedDerived1

Slightly b0rken Playground link

Implicit factories

In a language with type classes or implicits (like Scala), you could solve your problem with implicit Factory objects. This would be similar to your second example with the Creators, but without the need to explicitly pass the creators around everywhere. Instead, they would be implicitly summoned by the language.

In fact, your requirement is very similar to one of the core requirements of the Scala Collections Framework, namely that you want operations like map, filter, and reduce to only be implemented once, but still preserve the type of the collection.

Most other Collections Frameworks are only able to achieve one of those goals: Java, C#, and Ruby, for example, only have one implementation for each operation, but they always return the same, most-generic type (Stream in Java, IEnumerable in C#, Array in Ruby). Smalltalk's Collections Framework is type-preserving, but has duplicated implementations for every operation. A non-duplicated type-preserving Collections Framework is one of the holy grails of abstractions designers / language designers. (It's no coincidence that so many papers that present novel approaches to OO uses a refactoring of the Smalltalk Collection Framework as their working example.)

F-bounded Polymorphism

If you have neither MyType nor implicit builders available, you can use F-bounded Polymorphism.

The classic example is how Java's clone method should have been designed:

interface Cloneable<T extends Cloneable<T>> {
    public T clone();
}

class Foo implements Cloneable<Foo> {
    @Override
    public Foo clone() {
        return new Foo();
    }
}

JDoodle example

However, this gets tedious very quickly for deeply-nested inheritance hierarchies. I tried to model it in Scala, but I gave up.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.