網站搜尋

面向物件程式設計 - Typescript 中作為類型的類別與介面


我最近遇到這個問題,不確定應該使用介面還是類別來定義某個特定類型。

注意:這個問題不是在詢問class和interface之間的區別

例如,給定這個類別和介面

interface IMyClass {
  foo: string;
  bar: () => string;
}

class MyClass implements IMyClass {
  foo = 'foo';
  bar() {
    return 'bar';
  }
}

我會在函數參數中使用類別或介面作為類型。

選項 A - 使用 class 作為類型

function identityByClass(value: MyClass): MyClass {
  return value;
}

選項 B - 使用 interface 作為類型

function identityByInterface(value: IMyClass): IMyClass {
  return value;
}

從我的觀點來看,我認為兩者都可以,但我更喜歡使用類別,以避免在介面上同步所有方法/屬性。我將介面視為類別必須遵守的模板/契約。

然而,在大多數情況下,就我而言,這個類別經常會新增一些介面定義中沒有的方法/屬性。

舉個例子,如下面這個課程。

class MyClassPlus implements IMyClass {
  foo = 'foo';
  bar() {
    return 'bar';
  }
  doMore() {
    console.log('more stuff here!');
  }
}

在那種情況下,我將無法再把兩者互換使用。

如果有關於最佳實踐的連結就更好了,謝謝。

答案#1

我不確定應該使用介面還是類別來定義特定類型。

類別和介面都會創建一種型別,所以原則上它們可以互相替換。如你所指出的,介面就像是一個公開的契約,而類別則實現這個契約。

但是考慮以下情況:

  1. 如果你需要更改 MyClass,該怎麼辦?
  2. MyClass 透過揭示它是一個類別型別而暴露了更多的實作細節。

重新構建後的 MyClass 新型別簽名可能不再能用作函數 identityByClass 的參數。因此你需要重構所有使用者——天啊……使用單獨的型別介面,這種情況就不太可能發生。

第2點的範例:客戶端可能會提出取得 MyClass 的靜態屬性的想法(介面本身並不明顯,而是隱藏實作細節):

class MyClass {
  static baz(){}
  foo = 'foo';
}

function identityByClass(value: MyClass) {
  console.log((value.constructor as any).baz) // function baz()
  // there we go. do something with static baz() method of MyClass
}

順便一提:這不僅僅關乎介面和類型。類型別名提供了強大的功能,例如映射型或條件型、聯合等,而且大多數時候可以用來替代介面。

然而,在大多數情況下,就我而言,這個類別經常會新增一些介面定義中沒有的方法/屬性。

你不需要同步類別和介面類型。為了保持乾淨(DRY 原則),我想到兩個選擇:

  1. 介面可以擴充類別型別定義,以消除冗餘的宣告。
  2. 只挑選那些對使用者來說必需的類別屬性。

我會偏好第二點勝過所有其他替代方案,因為它遵循像資訊隱藏或鬆耦合這樣的設計原則。例如:

class MyClass {
  foo = 'foo';
  tooMuchDetail = "hide me"
}

// just pick, what we need (foo here)
type OnlyNeededProps = Pick<MyClass, "foo">

function doSomethingWithFoo(a: OnlyNeededProps) {
  a.foo
  a.tooMuchDetail // error, unknown (OK)
}

doSomethingWithFoo(new MyClass())

答案#2

使用介面來定義類型所獲得的一個好處是,介面本身在編譯後會被移除。這減少了應用程式的整體膨脹,這是此情況下使用介面的有力理由。

你可能會覺得這個有用:https://jameshenry.blog/typescript-classes-vs-interfaces/