这个名字有点意思。它被称为一个具体的类,因为。
- 它不能被其他类扩展/子类化
- 它有完整的方法定义
而代码在本质上就像混凝土。
一旦它被写出来,就需要努力去改变。
具体类也可以实现接口和扩展抽象类。
当这个类被称为完整的具体类时,我们称它为完整的具体类。
- 在实现一个接口的情况下,完全实现了属性和方法。
- 在扩展一个抽象类的情况下,实现了抽象的方法。
另一件需要注意的事情是,具体类可以被实例化,对象可以从它那里创建。
我们不能直接实例化一个抽象类或一个接口。
面向对象设计的初学者常常用具体类来写他们的大部分代码,不知道如何利用抽象来创造更好的设计。
例子
让我们用动物来说明一下。
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是一个接口,需要的属性是 "姓名 "和 "年龄"。