如何在TypeScript中从接口创建对象

172 阅读4分钟

这个名字有点意思。它被称为一个具体的类,因为。

  1. 它不能被其他类扩展/子类化
  2. 它有完整的方法定义

而代码在本质上就像混凝土。

一旦它被写出来,就需要努力去改变。

具体类也可以实现接口扩展抽象类

当这个类被称为完整的具体类时,我们称它为完整的具体类。

  • 在实现一个接口的情况下,完全实现了属性和方法。
  • 在扩展一个抽象类的情况下,实现了抽象的方法。

另一件需要注意的事情是,具体类可以被实例化,对象可以从它那里创建。

我们不能直接实例化一个抽象类或一个接口。

面向对象设计的初学者常常用具体类来写他们的大部分代码,不知道如何利用抽象来创造更好的设计。

例子

让我们用动物来说明一下。

TypeScript中的具体类(原始)。

// Raw concrete class

class Animal {
  public color: Color;
  public isHungry: boolean;
  constructor (color: Color) {
    this.color = color;
  }

  // We're making a lot of assumptions about the type of
  // animal that this is going to be, with a lack of abstraction.
  // Do hamster's hunt? 🤔 (youtube that and let me know)
  hunt (): void {
    if (this.isHungry) {
      // Hunt stuff so you can eat it
    }
  }

  makeNoise (): string {
    // Weird noise for an animal to be making... 🤔
    return "Yeedle yeedle yeedle!"
  }
}

这个例子展示了一个非常基本的具体类。我们也许应该在这里加入一些抽象的东西,这样我们就可以真正具体到我们可以创建的动物的类型。这也有助于对一些动物可以或不可以有的行为类型进行约束。

TypeScript中的具体类(实现一个接口和抽象类)

type Color = 'blue' | 'red' | 'green'

enum HunterSkillLevel {
  Novice,
  Skilled,
  Master
}

// Any class implementing this, be it an Animal, Person,
// Robot, etc.. needs to have these methods and properties.

interface IHunter {
  skillLevel: HunterSkillLevel;
  hunt (): void;
}

// Animal is an abstract class now. It can't be instantiated directly. 
// But, it does allow for us to subclass it and create lots of different
// types of animals from it.

abstract class Animal {
  
  protected color: Color;
  
  constructor (color: Color) {
    this.color = color;
  }

  // makeNoise should be implemented by any Animal subclass.
  abstract makeNoise () : string;
}

// Wolf concrete class.
// 
// The concrete class fully implements the requirements
// of the Animal abstract class by implementing the makeNoise method.
// 
// It also fully implements the requirements of the IHunter
// interface by including the HunterSkillLevel and implementing the
// hunt method.
//
// We can instantiate this directly.

class Wolf extends Animal implements IHunter {
  public skillLevel: HunterSkillLevel;

  constructor (color: Color, skillLevel: HunterSkillLevel) {
    super(color);

    this.skillLevel = skillLevel;
  }

  hunt (): void {
    // Get mean
  }

  makeNoise (): string {
    return "Arooooooooo"
  }
}

// Finally, we can create objects from our concrete Wolf class.

const meanWolf = new Wolf('blue', HunterSkillLevel.Master);
const babyWolf = new Wolf('red', HunterSkillLevel.Novice);

依赖于具体类

这通常是不可取的。我们通常希望依赖接口或抽象类;某种形式的抽象。当我们直接依赖具体的类时,我们的代码会受到一些不幸的设计限制。

依赖于具体类的负面效应。实现锁定

/**
 * A concrete Stratocaster guitar class. 
 */

class Stratocaster {
  private color: string;
  constructor (color: string) {
    this.color = color;
  }

  // Actual sound a guitar makes
  play () {
    console.log('do-dee-do-do-drnrnr')
  }
}

/**
 * The musician plays a guitar. 
 */

class Musician {
  // We've specified that this musician HAS to play
  // a Stratocaster... so they can't even play a Jazzmaster
  // if they wanted to 😢
  private guitar: Stratocaster;

  // Inject a Stratocaster into the constructor.
  // Clearly we've missed an abstraction here.
  constructor (guitar: Stratocaster) {
    this.guitar = guitar;
  }
} 

在这个TypeScript的例子中,我们有点玩弄自己。音乐家唯一能弹的吉他是具体的Stratocaster。如果我们想让音乐家能够弹奏其他吉他,比如Jazzmaster,我们就必须重新实现Stratocaster类的所有方法。

目前,这不是什么大问题,因为Statocaster只有一个方法,play() 。但随着时间的推移,当我们增加新的功能时,会发生什么呢?想到最后可能会变成这样也不是不现实的。

class Stratocaster {
  private color: string;
  public pedals: IPedal[];
  private currentVolume: Volume;
  private currentTone: ITone;

  constructor (color: string, pedals: IPedal[]) {
    this.color = color;
  }

  play () : void {}
  getTuning () : Tuning {}
  setTuning (newTuning: Tuning) : void {}
  getVolumn () : Volume {}
  setVolume (newVolume: Volume) : void {}
  getTone () : ITone {}
  setTone (newTone: ITone) : void {}
  plugIn () : void {}
  isPluggedIn () : boolean;
  isAtMaxVolume () : boolean;
  getConnectedPedals () : IPedal[]
  connectPedal (pedal: IPedal) : void {}
  getGuitarInfo () : IGuitarMetaData {}
  changeStrings (strings: IStrings) : void {}
  ...
}

这并不是最好的情况。如果你想添加那个Jazzmaster作为一个选项,你就必须在Jazzmaster的具体类中也重新实现所有这些方法。

一个更好的设计是使用一个抽象类

abstract class Guitar {
  private color: string;
  public pedals: IPedal[];
  private currentVolume: Volume;
  private currentTone: ITone;

  constructor (color: string, pedals: IPedal[] = []) {
    this.color = color;
    this.pedals = pedals 
  }

  play () : void {}
  getTuning () : Tuning {}
  setTuning (newTuning: Tuning) : void {}
  getVolumn () : Volume {}
  setVolume (newVolume: Volume) : void {}
  getTone () : ITone {}
  setTone (newTone: ITone) : void {}
  plugIn () : void {}
  isPluggedIn () : boolean;
  isAtMaxVolume () : boolean;
  getConnectedPedals () : IPedal[]
  connectPedal (pedal: IPedal) : void {}
  getGuitarInfo () : IGuitarMetaData {}
  changeStrings (strings: IStrings) : void {}
  ...
}

// Stratocaster has access to all of the properties and methods
// of guitar, defined in one place.
class Stratocaster extends Guitar {
  constructor (color: string) {
    super(color, [])
  }
}

// Jazzmaster does too!
class Jazzmaster extends Guitar {
  constructor (color: string) {
    super(color, [])
  }
}

我们也可以通过使用一个抽象工厂来控制吉他的创建。


在TypeScript中,我们实际上可以直接从接口创建对象。 我们可以做这样的事情。

const khalil: Person = { name: 'Khalil', age: 23 }

其中Person是一个接口,需要的属性是 "姓名 "和 "年龄"。