Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why protected members can be overridden by public members in TypeScript?

I am new to Typescript, and I tried to played around a little with TypeScript in this playground. I noticed that in TypeScript, a protected member in base class can be overridden by public members:

class Base {
    protected name: string = '!'
}

class Derived extends Base{
    public name: string = '?'
}

On the one hand, this makes sense to me since the Liskov Substitution Principle still holds: base class has stricter requirements than derived class. But on the other hand, I noticed that private member cannot be overridden by protected or public one, which seems inconsistent to me:

class Base {
    private name: string = '!'
}

class Derived extends Base{
    public name: string = '?'  // ERROR!
}

Thus I wonder:

  1. Is my observation an intended behavior or a bug in Typescript?

  2. If it's intended, why this inconsistency exists? Why not TypeScript requires all overriding members having the same accessibility as members in base class? Or allowing all derived members with higher accessibility overriding members in base class?

like image 317
Lifu Huang Avatar asked Dec 14 '22 23:12

Lifu Huang


2 Answers

This is the intended behavior.

You can make a protected field public because protected allows a derived class to read and write a field. The derived class can choose to use its ability to read and write the field to allow others to read and write the field. There's no point making you write something like this:

class Foo {
  protected someField;
}

class Bar extends Foo {
  public get someFieldButPublic() {
    return this.someField;
  }
  public set someFieldButPublic(value) {
    this.someField = value;
  }
}

if all you wanted to do was make someField public.

You can't make a private field protected or public because you don't have read or write access to that field. It's private; if the base class wanted you to have access to the field, they would have made it protected, after all.

like image 140
Ryan Cavanaugh Avatar answered May 20 '23 16:05

Ryan Cavanaugh


This is intended behavior.

TypeScript compiles to JavaScript. Because of this, using access modifiers in your code has absolutely no effect on the output. The only thing that access modifiers do is let the compiler yell at you if you use something that you shouldn't be accessing.

Example: Both of these classes compile to the exact same code. Playground (ignore the class name)

// prop is private
class Test {
    private prop: string;
    constructor() {
        this.prop = "str"
    }
}

// prop is public
class Test {
    public prop: string;
    constructor() {
        this.prop = "str"
    }
}

In languages like C#, private properties are truly private, and thus it is possible to inherit from a class with a private name property while exposing a name property. Inherited methods accessing this.name will access the name property in the base class, while methods in your class accessing this.name will use the property in the inheriting class.

Let's look at the emitted JavaScript for your example.

var __extends; // omitted for brevity
var Base = (function () {
    function Base() {
        this.name = '!';
    }
    return Base;
}());
var Derived = (function (_super) {
    __extends(Derived, _super);
    function Derived() {
        var _this = _super.apply(this, arguments) || this;
        _this.name = '?';
        return _this;
    }
    return Derived;
}(Base));

As you can see, what happens here is the Base class assigns ! to this.name, Derived then changes the supposedly private name property of Base to ?. Obviously, this could result in some incredibly confusing bugs when methods in the Base class refer to this.name and get the unexpected value assigned by the Derived class.

like image 39
Gerrit0 Avatar answered May 20 '23 18:05

Gerrit0