面向物件程式設計 - 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
我不確定應該使用介面還是類別來定義特定類型。
類別和介面都會創建一種型別,所以原則上它們可以互相替換。如你所指出的,介面就像是一個公開的契約,而類別則實現這個契約。
但是考慮以下情況:
- 如果你需要更改
MyClass,該怎麼辦? 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 原則),我想到兩個選擇:
- 介面可以擴充類別型別定義,以消除冗餘的宣告。
- 只挑選那些對使用者來說必需的類別屬性。
我會偏好第二點勝過所有其他替代方案,因為它遵循像資訊隱藏或鬆耦合這樣的設計原則。例如:
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/
