【问题标题】:Why protected members can be overridden by public members in TypeScript?为什么受保护的成员可以被 TypeScript 中的公共成员覆盖?
【发布时间】:2017-08-22 09:47:51
【问题描述】:

我是 Typescript 的新手,我尝试在 playground 中使用 TypeScript。我注意到在 TypeScript 中,基类中的受保护成员可以被公共成员覆盖:

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

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

一方面,这对我来说是有意义的,因为 Liskov 替换原则仍然成立:基类比派生类有更严格的要求。但另一方面,我注意到 private 成员不能被 protected 或 public 覆盖,这对我来说似乎不一致:

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

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

所以我想知道:

  1. 我的观察是预期行为还是 Typescript 中的错误?

  2. 如果是有意的,为什么会存在这种不一致?为什么 TypeScript 不要求所有覆盖成员与基类中的成员具有相同的可访问性?还是允许所有具有更高可访问性的派生成员覆盖基类中的成员?

【问题讨论】:

标签: javascript class oop inheritance typescript


【解决方案1】:

javascript没有这个功能,因为在java中它是一个字段rewrite。让我们看一下java中的代码sn-p:

class Base {
    protected String name = "!";

    public String bar() {
        return name;
    }
}

class Derived extends Base {
    public String name = "?";

    public String foo() {
        return name;
    }
}

当你在java中持有一种派生实例it。然后 it.foo() 返回"?" 和 it.bar() 返回"!"。但是为什么这两种方法都在javascript中返回"?",由于javascript行为发生在运行时并将this类型绑定到Base中的Derived ,所以你不能在javascript中重写字段。你还记得Function.prototype.call(thisArg)吗?在做这样的事情的派生类中,如果你在派生实例上调用bar方法,它实际上将派生类型绑定到基类。您可以禁止这样做是在 Base 中将该字段设为私有,并让编译器仅在 typescript 中告诉您此错误。

【讨论】:

  • 对不起。我要问的是为什么公共成员可以覆盖受保护的成员,但不能覆盖私有成员。我想你并没有真正回答我的问题。
  • 既然私有成员在派生类中不可见,为什么公共成员不能直接覆盖它,就好像基类中的私有成员不存在一样?而是抛出错误?
  • @LifuHuang 字段不是覆盖,而是重写。
  • @LifuHuang 这个问题太简单了,因为受保护的成员可以在子类中访问,然后你可以提升成员的访问修饰符但你不能在任何OO语言中降低成员的访问修饰符。
  • @LifuHuang 在我的回答中我从未调用过字段被子类覆盖,我称之为重写。
【解决方案2】:

这是预期的行为。

您可以创建protected 字段public,因为protected 允许派生类读取和写入字段。派生类可以选择利用其读写字段的能力来允许其他人读写该字段。让你写这样的东西是没有意义的:

class Foo {
  protected someField;
}

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

如果您只想公开someField

您无法创建private 字段protectedpublic,因为您没有对该字段的读取或写入权限。这是private;毕竟,如果基类希望您能够访问该字段,那么他们会设置为 protected

【讨论】:

    【解决方案3】:

    这是预期的行为。

    TypeScript 编译为 JavaScript。因此,在代码中使用访问修饰符对输出绝对没有影响。访问修饰符唯一能做的就是让编译器在你使用不应该访问的东西时对你大喊大叫。

    示例:这两个类都编译为完全相同的代码。 Playground(忽略类名)

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

    在像 C# 这样的语言中,私有属性是真正私有的,因此可以从具有私有名称属性的类继承,同时公开名称属性。访问this.name 的继承方法将访问基类中的name 属性,而访问this.name 的类中的方法将使用继承类中的属性。

    让我们看看为您的示例发出的 JavaScript。

    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));
    

    如您所见,这里发生的情况是Base 类将! 分配给this.nameDerived 然后将Base 的所谓私有name 属性更改为?。显然,当Base 类中的方法引用this.name 并获得Derived 类分配的意外值时,这可能会导致一些难以置信令人困惑的错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-25
      • 1970-01-01
      • 1970-01-01
      • 2019-02-13
      • 2018-04-17
      • 1970-01-01
      相关资源
      最近更新 更多