implements
В уроке «Типы объектов» вы использовали интерфейсы, чтобы описать форму объекта. В интерфейсе могут быть и методы. Для метода записывают его имя, параметры и тип возвращаемого значения, без тела:
interface Character {
name: string;
attack(): number;
}У каждого Character есть имя и метод attack, который возвращает число — урон. Тогда игра сможет работать с любым персонажем, какого бы класса он ни был: function fight(party: Character[]).
Класс может пообещать, что у него такая форма: class Wizard implements Character. Тогда TypeScript проверяет класс прямо там, где он объявлен, и если обещание не выполнено, вы получаете ошибку:
interface Character {
name: string;
attack(): number;
}
class Wizard implements Character {
name: string;
mana: number = 30;
constructor(name: string) {
this.name = name;
}
}TypeScript сообщает:
main.ts(6,7): error TS2420: Class 'Wizard' incorrectly implements interface 'Character'.
Property 'attack' is missing in type 'Wizard' but required in type 'Character'.Первая строка говорит, какой класс не подходит к какому интерфейсу; строка с отступом объясняет почему: не хватает attack. Добавьте метод в класс, и ошибка исчезнет:
attack(): number {
return this.mana / 2;
}implements ничего не даёт классу: каждое поле и каждый метод вы по-прежнему пишете сами. Это проверка, а ещё пометка для тех, кто читает код: «Этот класс должен быть Character». Если у метода не тот тип, TypeScript тоже скажет об этом. Когда attack возвращает текст, вы получаете Property 'attack' in type 'Wizard' is not assignable to the same property in base type 'Character'. (TS2416).
Важны формы, а не имена. TypeScript сравнивает формы, поэтому любой объект с name и методом attack — это Character, даже без implements, даже простой объект:
interface Character {
name: string;
attack(): number;
}
class Wizard implements Character {
name: string;
mana: number = 30;
constructor(name: string) {
this.name = name;
}
attack(): number {
return this.mana / 2;
}
}
class Archer {
name: string = "Кира";
attack(): number {
return 7;
}
}
const party: Character[] = [new Wizard("Лена"), new Archer(), { name: "Бруно", attack: () => 9 }];
for (const member of party) {
console.log(`${member.name}: ${member.attack()}`);
}Программа выводит:
Лена: 15
Кира: 7
Бруно: 9Archer не пишет implements Character, но подходит, поэтому его можно положить в массив. Зато с implements вы узнаёте, что чего-то не хватает, прямо в самом классе, а не где-то потом, там, где впервые используете его как Character. А через тип Character можно пользоваться только тем, что есть в интерфейсе: party[0].mana — это Property 'mana' does not exist on type 'Character'. (TS2339), хотя у волшебника mana есть.
Задание
- 1
Напишите класс
Mage, который реализуетCharacter(через ключевое слово implements). У него есть поляnameиmana(число), конструктор, задающий оба, и методattack(), который возвращаетmana, умноженную на 3. - 2
Добавьте
new Mage("Лена", 5)в массивparty. Программа выводит:Бруно атакует: урон 12 Лена атакует: урон 15 - 3
Каждый персонаж должен уметь представиться. Добавьте
describe(): string;в интерфейсCharacter. Тогда TypeScript отметит оба класса, поэтому напишите этот метод в каждом из них: он возвращает имя, запятую и слововоинилимаг. Под циклом добавьте второй цикл поparty, который выводитmember.describe(). Программа выводит:Бруно атакует: урон 12 Лена атакует: урон 15 Бруно, воин Лена, маг