- 抽象類的使用原則:
- 抽象類不能被實例化,需要依靠子類採用向上轉型的方式處理;
- 抽象類必須有子類去繼承,一個子類只能繼承一個繼承抽象類;
- 抽象方法必須是public和protected(因爲如果是private,則不能被子類繼承,子類就不能實現此方法);
- 如果子類繼承了此抽象類,則子類必須要重寫抽象類中的全部抽象方法(如果子類沒有全部重寫父類中的抽象方法,則子類也需要定義爲abstract的)
- 抽象類不能用final聲明,因爲抽象類必須有子類;
- 抽象類和接口的區別:
- 抽象類裏面可以有方法的實現,但是接口完全都是抽象的,不存在方法的實現;
- 子類只能繼承一個抽象類,而接口可以被多個實現;
- 抽象方法可以是public,protected,但是接口只能是public,默認的;
- 抽象類可以有構造器,而接口不能有構造器;
- 抽象類當做父類,被繼承。且抽象類的派生類的構造函數中必須調用super();接口可以當做“子類”繼承其他類
抽象類派生:
abstract class Human {
constructor(readonly name: string) { }
}
class Student extends Human {
constructor(name: string) {
super(name)
}
}
接口繼承類:
class Man {
job: string;
constructor (readonly name: string) {}
earnMoney () {
console.log(`earning money`)
}
}
interface HumanRule extends Man{
nose: string;
mouth: string;
ear: string;
eye: string;
eyebrow: string
}
當類被接口繼承時,通常是需要爲這個類的子類添加約束。例如下面的例子中,Man類的子類就需要去實現特定的五官屬性,否則將會報錯。
class Player extends Man implements HumanRule{
nose: string;
mouth: string;
ear: string;
eye: string;
eyebrow: string
constructor (name) {
super(name);
}
}
- 抽象類與接口都無法實例化, 類類型接口實際上是一種 抽象類型
按個人理解,在使用類類型的接口時,類類型的接口其實就相當於抽象類的子集。抽象類中除了可以像接口那樣只定義不實現外,還可以部分實現,而且也可以使用類型修飾符。
類類型的接口更多的是當做一種抽象的數據類型使用,此處所說的類型通常是某個類的實例類型。
let James: Player = new Player('james'); // 類類型使用
class SoccerPlayer extends Player {
constructor (name) {
super(name)
}
playSoccer () {
console.log(`${this.name} is playing soccer.`)
}
}
function createPlayer (pl: SoccerPlayer, name: string) { // 類類型調用
return new SoccerPlayer(name);
}
- 接口中不能包含具體實現,抽象類中除抽象函數之外,其他函數可以包含具體實現
此處我們將Human類增加一些內容:
abstract class Human {
constructor (readonly name:string) {}
protected thinking () {
console.log(`I am a human, so i can think, ${this.name} is thinking.`)
}
}
作爲人類,可以在人類 這個類中添加具體實現,因爲人類都可以思考。所以思考這個類就不必非要放到子類中去具體實現,這也正是抽象類的靈活之處。
- 抽象類中的抽象方法在子類中必須實現, 接口中的非可選項在接口被調用時必須實現。
此處我們繼續增加Human類的內容,增加move的具體實現方法爲抽象方法,因爲不同類型的人,移動的實現不同。(此處實際上也是OO的特性中,多態的一種具體實現)
abstract class Human {
constructor (readonly name:string) {}
protected thinking () {
console.log(`I am a human, so i can think, ${this.name} is thinking.`)
}
abstract move (): void
}
class Student extends Human {
constructor (name:string) {
super(name)
}
move () {
console.log(`I am a student, so i move by bus`)
}
}
而接口一旦調用,就必須要嚴格實現。此處以函數類型的接口爲例:
interface createPlayer {
(pl: SoccerPlayer, name:string): SoccerPlayer
}
let createPlayer:createPlayer = function (pl: SoccerPlayer, name: string) { // 修改createPlayer 使用匿名函數方法創建
return new SoccerPlayer(name);
}
- 抽象方法可當做類的實例方法,添加訪問修飾符;但是接口不可以
抽象方法的添加訪問修飾符和接口的嚴格實現其實都是各自的特點,我們也往往是根據這些特點去選擇究竟是使用抽象類還是使用接口。
還拿Human類來說:
abstract class Human {
constructor (readonly name:string) {}
protected thinking () {
console.log(`I am a human, so i can think, ${this.name} is thinking.`)
}
protected abstract move (): void
}
我們爲move方法添加abstract標識符,是想讓開發者非常明白,Human的派生類中必須要實現此方法;而使用protected標識符,是想限制move方法調用或重載的權限。
綜合來說抽象類更多的是實現業務上的嚴謹性;接口更多的是制定各種規範,而此規範又分爲很多類規範,就像官方文檔在介紹接口這一節的時候所說的,例如函數型規範、類類型規範、混合規範、索引規範等。