【问题标题】:In TypeScript an interface can extend a class, what for?在 TypeScript 中,一个接口可以扩展一个类,这是为了什么?
【发布时间】:2016-12-31 16:24:34
【问题描述】:

TypeScript Handbook 在“将类用作接口”部分中,有一个扩展类的接口示例。

class Point { ... } interface Point3d extends Point {...}

这在什么时候有用?你有这方面的实际例子吗?

【问题讨论】:

    标签: inheritance typescript interface


    【解决方案1】:

    以这个类为例:

    class MyClass {
        public num: number;
        public str: string;
    
        public constructor(num: number, str: string) {
            this.num = num;
            this.str = str;
        }
    
        public fn(arr: any[]): boolean {
            // do something
        }
    }
    

    你可以像这样创建一个实例:

    let a1 = new MyClass(4, "hey");
    

    但你也可以像这样创建一个满足相同接口的对象:

    let a2 = {
        num: 3,
        str: "hey",
        fn: function(arr: any[]): boolean {
            // do something
        }
    }
    

    a1instanceof MyClass,而 a2 只是一个对象,但它们都实现了相同的接口。
    接口扩展类的意义在于,你可以拿类定义的接口进行扩展。

    由于语言的性质,这可能只是一种可能性,但这里有一个可能有用的示例:

    class Map<T> {
        private _items: { [key: string]: T };
    
        set(key: string, value: T) { ... }
    
        has(key: string): boolean { ... }
    
        get(key: string): T { ... }
    
        remove(key: string): T { ... }
    }
    
    interface NumberMap extends Map<number> {}
    interface StringMap extends Map<string> {}
    interface BooleanMap extends Map<boolean> {}
    
    function stringsHandler(map: StringMap) { ... }
    

    【讨论】:

      【解决方案2】:

      Interfaces section of TypeScript Handbook中所述:

      接口甚至继承基类的私有成员和受保护成员。这意味着,当您创建的接口扩展具有私有或受保护成员的类时,该接口类型只能由该类或其子类实现。 p>

      这种限制似乎是私有成员和受保护成员继承的副作用。

      class Parent
      {
          private m_privateParent;
      }
      
      interface ISomething extends Parent
      {
          doSomething(): void; 
      }
      
      class NoonesChild implements ISomething
      {
      /**
       * You will get error here
       * Class 'NoonesChild' incorrectly implements interface 'ISomething'.
       * Property 'm_privateParent' is missing in type 'NoonesChild'
       */
          doSomething()
          {
              //do something
          }
      }
      
      class NoonesSecondChild implements ISomething
      {
      /**
       * Nope, this won't help
       * Class 'NoonesSecondChild' incorrectly implements interface 'ISomething'.
       * Types have separate declarations of a private property 'm_privateParent'.
       */
          private m_privateParent;
      
          doSomething()
          {
              //do something
          }
      }
      
      class ParentsChild extends Parent implements ISomething
      {
      /**
       * This works fine
       */
          doSomething()
          {
              //Do something
          }
      }
      

      【讨论】:

        【解决方案3】:

        我也很难理解“你为什么要这样做?”这是我学到的。

        如前所述,接口甚至继承基类的私有成员和受保护成员。这意味着,当您创建一个扩展具有私有或受保护成员的类的接口时,该接口类型只能由该类或其子类实现。

        假设您正在为用户界面实现控件。您需要标准控件,例如按钮、文本框和标签。

        层次结构是:

        class Control{
            private state: any;
        }
        class Button extends Control{
        }
        class TextBox extends Control{
        }
        class Label extends Control{
        }
        

        注意 Control 中的私有状态值。这很重要。

        现在假设我们想要一种方法来引用可以由某些激活触发的控件。例如,可以单击一个按钮。当您输入文本并按 Enter 时,可以激活文本框。但是,标签是装饰性的,因此用户无法对其进行任何操作。

        我们可能想要一种方法来引用此类控件,以便我们可以仅使用这些类型的控件进行操作。例如,我们可能想要一个接受控件作为参数的函数,但我们只想要可以激活的控件。

        假设我们尝试用一个扩展类的普通接口来描述这些控件(这是不正确的,但我会稍微解释一下原因)。

        // WARNING: This isn't correct - see rest of post for details.
        interface ActivatableControl{
            activate(): void;
        }
        

        任何实现该接口的东西都可以被视为一个 ActivatableControl,所以让我们更新我们的层次结构:

        class Control{
            private state: any;
        }
        interface ActivatableControl{
            activate(): void;
        }
        class Button extends Control implements ActivatableControl{
            activate(){}
        }
        class TextBox extends Control implements ActivatableControl{
            activate(){}
        }
        class Label extends Control{}
        

        如上所述,Label 没有实现 ActivatableControl。所以一切都很好,对吧?

        这就是问题所在 - 我可以添加另一个实现 ActivatableControl 的类:

        class Dishwasher implements ActivatableControl{
            activate(){}
        }
        

        界面的目的是用于可以激活的控件,而不是用于无关的对象。

        所以我真正想要的是指定一个界面,该界面需要某些控件是可激活的,仅此而已。

        为了做到这一点,我让我的界面扩展了 Control,如下所示:

        class Control{
            private state: any;
        }
        interface ActivatableControl extends Control {
            activate(): void;
        }
        

        由于 Control 具有私有值,只有 Control 的子类可以实现 ActivatableControl

        现在,如果我尝试这样做:

        // Error!
        class Dishwasher implements ActivatableControl{
            activate(){}
        }
        

        我会收到 Typescript 错误,因为 Dishwasher 不是控件。

        附加说明:如果一个类扩展了Control,并实现了activate,那么它可以被视为一个ActivatableControl。本质上,即使没有显式声明接口,该类也实现了接口。

        所以下面的 TextBox 实现仍然让我们把它当作一个 ActivatableControl:

        class TextBox extends Control {
            activate(){}
        }
        

        这是层次结构的最终版本,其中一些代码显示了我可以用它做什么:

        class Control {
            private state: any;
        }
        interface ActivatableControl extends Control {
            activate(): void;
        }
        class Button extends Control implements ActivatableControl {
            activate() { }
        }
        // Implicitly implements ActivatableControl since it matches the interface and extends Control.
        class TextBox extends Control {
            activate() { }
        }
        class Label extends Control {
        }
        
        // Error - cannot implement ActivatableControl because it isn't a Control
        /*
        class Dishwasher implements ActivatableControl {
            activate() { }
        }
        */
        // Error - this won't work either. 
        // ActivatableControl extends Control, and therefore contains state as a private member.
        // Only descendants of Control can implement ActivatableControl.
        /*
        class Microwave implements ActivatableControl {
            private state: any;
            activate() { }
        }
        */
        
        let button: Button = new Button();
        let textBox: TextBox = new TextBox();
        let label: Label = new Label();
        let activatableControl: ActivatableControl = null;
        
        // I can assign button to activatableControl.
        activatableControl = button;
        
        // Same with textBox since textBox fulfills the contract of an ActivatableControl.
        activatableControl = textBox;
        
        // Error - label does not implement ActivatableControl
        // nor does it fulfill the contract.
        //activatableControl = label;
        
        function activator(activatableControl: ActivatableControl){
            // I can assume activate can be called 
            // since ActivatableControl requires that activate is implemented.
            activatableControl.activate();
        }
        

        【讨论】:

          【解决方案4】:
          • 限制界面的使用。
          • 如果我们不希望任何类可以实现接口,可以使用此解决方案。
          • 假设接口“I”扩展类“C”。那么 'I' 只能由 'C' 或其子级来实现。
          • 或者如果一个类需要实现“I”,那么它应该首先扩展“C”。

          请参阅下面的示例。

          // This class is a set of premium features for cars.
          class PremiumFeatureSet {
              private cruiseControl: boolean;
          }
          
          // Only through this interface, cars can use premium features.
          // This can be 'licensed' by car manufacturers to use premium features !!
          interface IAccessPremiumFeatures extends PremiumFeatureSet {
              enablePremiumFeatures(): void
          }
          
          // MyFirstCar cannot implement interface to access premium features.
          // Because I had no money to extend MyFirstCar to have PremiumFeatureSet.
          // Without feature, what's the use of a way to access them?
          // So This won't work.
          class MyFirstCar implements IAccessPremiumFeatures {
          
              enablePremiumFeatures() {
          
              }
          }
          
          // Later I bought a LuxuryCar with (extending) PremiumFeatureSet.
          // So I can access features with implementing interface.
          // Now MyLuxuryCar has premium features first. So it makes sense to have an interface to access them.
          // i.e. To implement IAccessPremiumFeatures, we need have PremiumFeatureSet first.
          
          class MyLuxuryCar extends PremiumFeatureSet implements IAccessPremiumFeatures {
              enablePremiumFeatures() {
                  // code to enable features
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2018-12-01
            • 2012-10-03
            • 2010-10-13
            • 2013-11-02
            • 1970-01-01
            • 1970-01-01
            • 2016-04-28
            • 2013-05-24
            相关资源
            最近更新 更多