1

I have two classes both implementing IFruit interface. The simplified problem is that I want to replace Apple with Mango in certain confitions. However the below example causes compiler error.

Cannot convert 'Mango' to 'Apple': Types 'Mango' and 'Apple' define property '_element' as private.

Why this is a problem? _element is not surfaced in the interface. It should not be forced to be public.

interface IFruit {
    getElement(): HTMLElement;
}

class Apple implements IFruit {

    private _element;

    constructor(element: HTMLElement) {
        this._element = element;
    }

    getElement(): HTMLElement {
        return this._element;
    }
}

class Mango implements IFruit {

    private _element;

    constructor(element: HTMLElement) {
        this._element = element;
    }

    getElement(): HTMLElement {
        return this._element;
    }

}

var e: HTMLElement;
var a = new Apple(e);
var m = new Mango(e);
a = m;

1 Answer 1

2

There are three solutions you can use, the choice is yours based on the context.

Interface

Because a private member effectively makes it impossible for any other type to be structurally a match, you can never make an Mango look like an Apple. They do both perfectly satisfy the IFruit interface, so if you type them as IFruit they are perfectly substituatble - example below with IFruit type annotation on both new Apple(e) and new Mango(e) - although this would work with it just on Apple, for example:

interface IFruit {
    getElement(): HTMLElement;
}

class Apple implements IFruit {

    private _element;

    constructor(element: HTMLElement) {
        this._element = element;
    }

    getElement(): HTMLElement {
        return this._element;
    }
}

class Mango implements IFruit {

    private _element;

    constructor(element: HTMLElement) {
        this._element = element;
    }

    getElement(): HTMLElement {
        return this._element;
    }

}

var e: HTMLElement;
var a: IFruit = new Apple(e);
var m: IFruit = new Mango(e);
a = m;

Inheritance

You could also solve this using inheritance. Apple and Mango could access a protected _element on the base Fruit class:

class Fruit {
    protected _element : HTMLElement;

    constructor(element: HTMLElement) {
        this._element = element;
    }

    getElement(): HTMLElement {
        return this._element;
    }
}

class Apple extends Fruit {
    constructor(element: HTMLElement) {
        super(element);
    }
}

class Mango extends Fruit {
    constructor(element: HTMLElement) {
        super(element);
    }
}

var e: HTMLElement;
var a = new Apple(e);
var m = new Mango(e);
a = m;

Public

If you want to make them compatible without using the interface or inheritance, you need to make the property public:

interface IFruit {
    getElement(): HTMLElement;
}

class Apple implements IFruit {

    public _element;

    constructor(element: HTMLElement) {
        this._element = element;
    }

    getElement(): HTMLElement {
        return this._element;
    }
}

class Mango implements IFruit {

    public _element;

    constructor(element: HTMLElement) {
        this._element = element;
    }

    getElement(): HTMLElement {
        return this._element;
    }

}

var e: HTMLElement;
var a = new Apple(e);
var m = new Mango(e);
a = m;
Sign up to request clarification or add additional context in comments.

3 Comments

I went with the first option. But I still think this is strange. If both has the same private member than it should work. Also it works if "element" is public and not private.
This is the nature of a structural typing system - it is important to be able to make a non matching type if you want to apply Domain Driven Design. For example class CustomerId { constructor(private id: number) {} getId() { return this.id; } } can not be mistaken for class ProductId { constructor(private id: number) {} getId() { return this.id; } } Example on the Playground: bit.ly/1FKUkxE
The code example in the above comment is hard to read, but it is written in more detail here: stevefenton.co.uk/Content/Blog/Date/201412/Blog/…

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.