Внутренний и внешний интерфейс
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Один из важнейших принципов ООП – отделение внутреннего интерфейса от внешнего.
Это – обязательная практика в разработке чего угодно сложнее hello world.
Чтобы это понять, отвлечёмся от разработки и переведём взгляд на объекты реального мира.
Как правило, устройства, с которыми мы имеем дело, весьма сложны. Но разделение интерфейса на внешний и внутренний позволяет использовать их без малейших проблем.
Пример из жизни
Например, кофеварка. Простая снаружи: кнопка, индикатор, отверстия,… И, конечно, результат – кофе 🙂

Но внутри… (картинка из пособия по ремонту)

Масса деталей. Но мы можем пользоваться ей, совершенно не зная об этом.
Кофеварки – довольно-таки надёжны, не правда ли? Можно пользоваться годами, и только когда что-то пойдёт не так – придётся нести к мастеру.
Секрет надёжности и простоты кофеварки – в том, что все детали отлажены и спрятаны внутри.
Если снять с кофеварки защитный кожух, то использование её будет более сложным (куда нажимать?) и опасным (током ударить может).
Как мы увидим, объекты очень схожи с кофеварками.
Только для того, чтобы прятать внутренние детали, используется не кожух, а специальные средства языка и соглашения.
Внутренний и внешний интерфейс
В программировании мы будем разделять методы и свойства объекта на две группы:
- Внутренний интерфейс – это свойства и методы, доступ к которым может быть осуществлён только из других методов объекта, их также называют «приватными» (есть и другие термины, встретим их далее).
- Внешний интерфейс – это свойства и методы, доступные снаружи объекта, их называют «публичными».
Если продолжить аналогию с кофеваркой – то, что спрятано внутри кофеварки: трубка кипятильника, нагревательный элемент, тепловой предохранитель и так далее – это её внутренний интерфейс.
Внутренний интерфейс используется для обеспечения работоспособности объекта, его детали используют друг друга. Например, трубка кипятильника подключена к нагревательному элементу.
Но снаружи кофеварка закрыта специальным кожухом, чтобы никто к ним не подобрался. Детали скрыты и недоступны. Виден лишь внешний интерфейс.
Получив объект, всё, что нужно для пользования им – это знать внешний интерфейс. О внутреннем же знать вообще не обязательно.
Это были общие слова по теории программирования.
Далее мы реализуем кофеварку на JavaScript с приватными и публичными свойствами. В кофеварке много деталей, мы конечно, не будем моделировать каждый винтик, а сосредоточимся на основных приёмах разработки.
Шаг 1: публичное и приватное свойство
Конструктор кофеварок будет называться CoffeeMachine .
function CoffeeMachine(power) < this.waterAmount = 0; // количество воды в кофеварке alert( 'Создана кофеварка мощностью: ' + power + ' ватт' ); >// создать кофеварку var coffeeMachine = new CoffeeMachine(100); // залить воды coffeeMachine.waterAmount = 200;
Локальные переменные, включая параметры конструктора, можно считать приватными свойствами.
В примере выше это power – мощность кофеварки, которая указывается при создании и далее будет использована для расчёта времени кипячения.
К локальным переменным конструктора нельзя обратиться снаружи, но они доступны внутри самого конструктора.
Свойства, записанные в this , можно считать публичными.
Здесь свойство waterAmount записано в объект, а значит – доступно для модификации снаружи. Можно доливать и выливать воду в любом количестве.
Вопрос терминологии
Далее мы будем называть power как «локальной переменной», так и «приватным свойством» объекта.
Это, смотря, с какой стороны посмотреть.
Термины «приватное свойство/метод», «публичное свойство/метод» относятся к общей теории ООП. А их конкретная реализация в языке программирования может быть различной.
Здесь ООП-принцип «приватного свойства» реализован через локальные переменные, поэтому и «локальная переменная» и «приватное свойство» – правильные термины, в зависимости от того, с какой точки зрения взглянуть – кода или архитектуры ООП.
Шаг 2: публичный и приватный методы
Добавим публичный метод run , запускающий кофеварку, а также вспомогательные внутренние методы getBoilTime и onReady :
function CoffeeMachine(power) < this.waterAmount = 0; // расчёт времени для кипячения function getBoilTime() < return 1000; // точная формула расчёта будет позже >// что делать по окончании процесса function onReady() < alert( 'Кофе готов!' ); >this.run = function() < // setTimeout - встроенная функция, // она запустит onReady через getBoilTime() миллисекунд setTimeout(onReady, getBoilTime()); >; > var coffeeMachine = new CoffeeMachine(100); coffeeMachine.waterAmount = 200; coffeeMachine.run();
Приватные методы, такие как onReady , getBoilTime могут быть объявлены как вложенные функции.
В результате естественным образом получается, что доступ к ним (через замыкание) имеют только другие функции, объявленные в том же конструкторе.
Шаг 3: константа
Для расчёта времени на кипячение воды используется формула c*m*ΔT / power , где:
- c – коэффициент теплоёмкости воды, физическая константа равная 4200 .
- m – масса воды, которую нужно нагреть.
- ΔT – температура, на которую нужно подогреть, будем считать, что изначально вода – комнатной температуры 20°С, то есть до 100° нужно греть на ΔT=80 .
- power – мощность.
Используем её в более реалистичном варианте getBoilTime() , включающем использование приватных свойств и константу:
"use strict" function CoffeeMachine(power) < this.waterAmount = 0; // физическая константа - удельная теплоёмкость воды для getBoilTime var WATER_HEAT_CAPACITY = 4200; // расчёт времени для кипячения function getBoilTime() < return this.waterAmount * WATER_HEAT_CAPACITY * 80 / power; // ошибка! >// что делать по окончании процесса function onReady() < alert( 'Кофе готов!' ); >this.run = function() < setTimeout(onReady, getBoilTime()); >; > var coffeeMachine = new CoffeeMachine(1000); coffeeMachine.waterAmount = 200; coffeeMachine.run();
Удельная теплоёмкость WATER_HEAT_CAPACITY выделена большими буквами, так как это константа.
Внимание, при запуске кода выше в методе getBoilTime будет ошибка. Как вы думаете, почему?
Шаг 4: доступ к объекту из внутреннего метода
Внутренний метод вызывается так: getBoilTime() . А чему при этом равен this ?… Как вы наверняка помните, в современном стандарте он будет undefined (в старом – window ), из-за этого при чтении this.waterAmount возникнет ошибка!
Её можно решить, если вызвать getBoilTime с явным указанием контекста: getBoilTime.call(this) :
function CoffeeMachine(power) < this.waterAmount = 0; var WATER_HEAT_CAPACITY = 4200; function getBoilTime() < return this.waterAmount * WATER_HEAT_CAPACITY * 80 / power; >function onReady() < alert( 'Кофе готов!' ); >this.run = function() < setTimeout(onReady, getBoilTime.call(this)); >; > // создаю кофеварку, мощностью 100000W чтобы кипятила быстро var coffeeMachine = new CoffeeMachine(100000); coffeeMachine.waterAmount = 200; coffeeMachine.run();
Такой подход будет работать, но он не очень-то удобен. Ведь получается, что теперь везде, где мы хотим вызвать getBoilTime , нужно явно указывать контекст, т.е. писать getBoilTime.call(this) .
К счастью существуют более элегантные решения.
Привязка через bind
Можно при объявлении привязать getBoilTime к объекту через bind , тогда вопрос контекста отпадёт сам собой:
function CoffeeMachine(power) < this.waterAmount = 0; var WATER_HEAT_CAPACITY = 4200; var getBoilTime = function() < return this.waterAmount * WATER_HEAT_CAPACITY * 80 / power; >.bind(this); function onReady() < alert( 'Кофе готов!' ); >this.run = function() < setTimeout(onReady, getBoilTime()); >; > var coffeeMachine = new CoffeeMachine(100000); coffeeMachine.waterAmount = 200; coffeeMachine.run();
Это решение будет работать, теперь функцию можно просто вызывать без call . Но объявление функции стало менее красивым.
Сохранение this в замыкании
Пожалуй, самый удобный и часто применяемый путь решения состоит в том, чтобы предварительно скопировать this во вспомогательную переменную и обращаться из внутренних функций уже к ней.
function CoffeeMachine(power) < this.waterAmount = 0; var WATER_HEAT_CAPACITY = 4200; var self = this; function getBoilTime() < return self.waterAmount * WATER_HEAT_CAPACITY * 80 / power; >function onReady() < alert( 'Кофе готов!' ); >this.run = function() < setTimeout(onReady, getBoilTime()); >; > var coffeeMachine = new CoffeeMachine(100000); coffeeMachine.waterAmount = 200; coffeeMachine.run();
Теперь getBoilTime получает self из замыкания.
Конечно, чтобы это работало, мы не должны изменять self , а все приватные методы, которые хотят иметь доступ к текущему объекту, должны использовать внутри себя self вместо this .
Вместо self можно использовать любое другое имя переменной, например var me = this .
Итого
Итак, мы сделали кофеварку с публичными и приватными методами и заставили их корректно работать.
В терминологии ООП отделение и защита внутреннего интерфейса называется инкапсуляция.
Кратко перечислим бонусы, которые она даёт:
Защита пользователей от выстрела себе в ногу
Представьте, команда разработчиков пользуется кофеваркой. Кофеварка создана фирмой «Лучшие Кофеварки» и, в общем, работает хорошо, но с неё сняли защитный кожух и, таким образом, внутренний интерфейс стал доступен.
Все разработчики цивилизованны – и пользуются кофеваркой как обычно. Но хитрый Вася решил, что он самый умный, и подкрутил кое-что внутри кофеварки, чтобы кофе заваривался покрепче. Вася не знал, что те изменения, которые он произвёл, приведут к тому, что кофеварка испортится через два дня.
Виноват, разумеется, не только Вася, но и тот, кто снял защитный кожух с кофеварки, и тем самым позволил Васе проводить манипуляции.
В программировании – то же самое. Если пользователь объекта будет менять то, что не рассчитано на изменение снаружи – последствия могут быть непредсказуемыми.
Удобство в поддержке
Ситуация в программировании сложнее, чем с кофеваркой, т.к. кофеварку один раз купили и всё, а программа может улучшаться и дорабатываться.
При наличии чётко выделенного внешнего интерфейса, разработчик может свободно менять внутренние свойства и методы, без оглядки на коллег.
Гораздо легче разрабатывать, если знаешь, что ряд методов (все внутренние) можно переименовывать, менять их параметры, и вообще, переписать как угодно, так как внешний код к ним абсолютно точно не обращается.
Ближайшая аналогия в реальной жизни – это когда выходит «новая версия» кофеварки, которая работает гораздо лучше. Разработчик мог переделать всё внутри, но пользоваться ей по-прежнему просто, так как внешний интерфейс сохранён.
Люди обожают пользоваться вещами, которые просты с виду. А что внутри – дело десятое.
Программисты здесь не исключение.
Всегда удобно, когда детали реализации скрыты, и доступен простой, понятно документированный внешний интерфейс.
Задачи
Добавить метод и свойство кофеварке
важность: 5
Улучшите готовый код кофеварки, который дан ниже: добавьте в кофеварку публичный метод stop() , который будет останавливать кипячение (через clearTimeout ).
function CoffeeMachine(power) < this.waterAmount = 0; var WATER_HEAT_CAPACITY = 4200; var self = this; function getBoilTime() < return self.waterAmount * WATER_HEAT_CAPACITY * 80 / power; >function onReady() < alert( 'Кофе готово!' ); >this.run = function() < setTimeout(onReady, getBoilTime()); >; >
Вот такой код должен ничего не выводить:
var coffeeMachine = new CoffeeMachine(50000); coffeeMachine.waterAmount = 200; coffeeMachine.run(); coffeeMachine.stop(); // кофе приготовлен не будет
P.S. Текущую температуру воды вычислять и хранить не требуется.
P.P.S. При решении вам, скорее всего, понадобится добавить приватное свойство timerId , которое будет хранить текущий таймер.
Кофеварка с новым методом:
function CoffeeMachine(power) < this.waterAmount = 0; var WATER_HEAT_CAPACITY = 4200; var timerId; var self = this; function getBoilTime() < return self.waterAmount * WATER_HEAT_CAPACITY * 80 / power; >function onReady() < alert( 'Кофе готово!' ); >this.run = function() < timerId = setTimeout(onReady, getBoilTime()); >; this.stop = function() < clearTimeout(timerId) >; > var coffeeMachine = new CoffeeMachine(50000); coffeeMachine.waterAmount = 200; coffeeMachine.run(); coffeeMachine.stop(); // кофе приготовлен не будет
Поделиться
Комментарии
перед тем как писать…
- Если вам кажется, что в статье что-то не так — вместо комментария напишите на GitHub.
- Для одной строки кода используйте тег , для нескольких строк кода — тег , если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
- Если что-то непонятно в статье — пишите, что именно и с какого места.
- © 2007—2024 Илья Кантор
- о проекте
- связаться с нами
- пользовательское соглашение
- политика конфиденциальности
Интерфейсы в TypeScript
Что такое интерфейсы и зачем они вообще нужны? TypeScript ввел новый тип данных, определяемый с помощью конструкции называемой «interface» . Интерфейсы в TypeScript служат для именования типов данных, и являются способом определения соглашений внутри кода. Другими словами, создавая интерфейс мы создаем некоторый тип данных, который в основном служит для объектов или классов, где мы указываем какие поля, какие функции и какие вообще элементы должны присутствовать у этих объектов. Давайте рассмотрим вот такой простой пример:
interface IUser < readonly id: number, name: string, color?: string, size:< width: number, height: number >>
Некоторые свойства должны изменяться только один раз, при создании объекта. Этого можно добиться, ограничив свойство только для чтения, применив модификатор “readonly” , как мы с делали это полем “id” .
Не всегда все свойства интерфейса должны являться обязательными. Сделать свойство необязательным для заполнения путем добавления знака “?” после его именования, как это сделано у поля “color” в примере.
Также в интерфейсе можно указывать, какие-то другие объекты, как это сделано у поля “size” .
После создания интерфейса, для нас открывается возможность создавать различные объекты которые будут являться типом нашего интерфейса, а именно “IUser” . Давайте
посмотрим на примере как это работает:
const user: IUser = < id: 123, name: 'Чак Норрис', color: '#ccc', size:< width: 20, height: 30 >>
В примере мы создали переменную “user” , указав ее тип “IUser” . В следующем примере мы проделаем подобные действия, но уже не добавляя свойство “color” , так как оно указано как не обязательное, но добавляя его ниже созданной переменной.
const user: IUser = < id: 123, name: 'Чак Норрис', size:< width: 20, height: 30 >> user.color = “red”
Единственное, что здесь может смутить, это то что переменная “user” , является константой, а в последствии мы ее изменяем. Но в конечном итоге, так как, мы работаем в JavaScript, то мы можем изменять внутреннее состояние констант, если они является объектом или массивом.
Так же мы можем указывать к какому типу будет относиться объект. Вот два примера:
const user = <> as IUser const user1 = <> // более старая запись
Наследование (или расширение) интерфейсов
TypeScript позволяет, создавать интерфейсы включающие в себя комбинации других интерфейсов, что позволяет настроить очень гибкое взаимодействие между интерфейсами. Давайте рассмотрим это на примере:
interface IUserWidthArea extends IUser < getArea:()=>number > const user2: IUserWidthArea = < id: 123, name: 'Чак Норрис', size:< width: 20, height: 30 >, getArea(): number < return this.size.width * this.size. height >>
Интерфейс, который мы создали будет наследоваться от интерфейса “IUser” . В него мы можем добавлять какие-то новые поля. В нашем случае мы обязываем его реализовывать функцию “getArea()” , которая будет рассчитывать площадь.
Так же интерфейсы могут взаимодействовать с классами. Давайте
это рассмотрим на примере интерфейса “IClock” :
interface IClock < time: Date, setTime(date: Date): void >сlass Clock implements IClock < time: Date = new Date() setTime(date: Date): void< this.time = date >>
Еще что бы я хотел рассказать об интерфейса в рамках это статья, это то что бывают ситуации, когда необходимо создать интерфейс для объекта у которого будет большое количество динамических ключей, например:
const css = < padding: '10px 40px 10px 40px', position: 'relative', margin: '10px 60px 10px 60px', height: '90px', // и так далее >
Создавая интерфейс для данный переменной мы не можем перечислить всевозможные свойства, это будет крайне неэффективно, поэтому для таких ситуаций существует специальный синтаксис:
interface IStyles
Где в квадратных скобках мы указываем тип ключа и тип его значения.
Интерфейсы в TypeScript
Интерфейсы представляют собой способ описать объект без указания его реализации. Для их объявления в Typescript используется ключевое слово interface. То есть интерфейс служит для записи формы объекта и может использоваться для указания типа переменной. Этой переменной можно присвоить любой объект, подходящий под описание интерфейса. Также интерфейсы можно расширять с помощью ключевого слова extends аналогично классам, но делать это стоит с осторожностью и не создавать больших иерархий.
interface IButton < text: string, click: () =>void; >
В данном примере мы объявили интерфейс IButton, который требует, чтобы у объекта было строковое поле text и функция click, которая ничего не принимает и не возвращает. В разных языках программирования существуют разные соглашения по именованию интерфейсов, но для удобства понимания, что это интерфейс я буду именовать их с большой буквы I. Теперь мы можем создать объект, подходящий под это описание и присваивать его переменной типа интерфейса. Причём, если мы пропустим хоть одно описание из интерфейса или добавим лишнего, то линтер TS предупредит нас об этом даже до компиляции:
/* Property 'click' is missing in type '< text: string; >' but required in type 'IButton' */ const primaryButton: IButton = /* Object literal may only specify known properties, and 'id' does not exist in type 'IButton' */ const primaryButton: IButton = < text: 'Подтвердить', id: 'main', click: () => < console.log('Отправка формы'); >>
Таким образом, интерфейсы — это помощник компилятора и разработчика, которые служат средством проверки формы объекта до этапа выполнения. После компиляции в JS они просто удаляются, так как больше не нужны.
У интерфейсов есть ещё одно полезное свойство: они могут выступать ещё и контрактами, которые должен будет выполнять класс. Причём класс может взять на себя выполнение сразу нескольких контрактов с помощью ключевого слова implements:
class PrimaryButton implements IButton, IHidable < constructor(text: string) < this.text = text; >// Члены интерфейса IButton text: string; click(): void < console.log('Отправка формы'); >// Члены интерфейса IHidable hide(): void < console.log('Скрываем кнопку'); >show(): void < console.log('Показываем кнопку'); >>
Реализовав интерфейс, класс говорит о том, что он соблюдает контракт интерфейса и объекты этого класса можно присвоить переменной типа этого интерфейса:
const btn: IButton = new PrimaryButton('Ок');
И тут мы подходим к тому, что интерфейсы являются отличным инструментом для полиморфизма (несколько классов, реализующих один интерфейс и имеющих различающийся функционал, могут быть взаимозаменяемы в моменте использования). Например, у нас есть несколько разных кнопок, которые надо скрыть в определённый момент и мы их можем присвоить однотипному массиву и выполнить общий метод hide на всех кнопках:
class ImageButton implements IButton, IHidable < constructor(text: string, imgUrl: string) < this.text = text; >// Дополнительные члены private setImage(imgUrl: string) < // TODO: Установка фона картинки >// Члены интерфейса IButton text: string; click(): void < console.log('Отправка формы'); >// Члены интерфейса IHidable hide(): void < console.log('Скрываем кнопку'); >show(): void < console.log('Показываем кнопку'); >> const btn1 = new PrimaryButton('Ок'); const btn2 = new ImageButton('Продолжить', 'https://some-url.ru/next.png'); const buttons: IHidable[] = [btn1, btn2]; buttons.forEach(b => b.hide());
Но формы объектов описывают не только с помощью ключевого слова interface, а также и ключевым словом type. Так в чём же отличие interface от type? На самом деле это очень похожие вещи и отличий почти нет, но всё же несколько интерфейсов с одним именем в одной области автоматически сольются в один общий интерфейс, а type не могут. Интерфейсы не могут использовать выражения & или | при объявлении, а type могут. А также при расширении (extends) интерфейсов TS проверит совместимость членов интерфейсов, а при расширении type такой проверки не будет:
interface IA < go(n: number): string; >/* Interface 'IB' incorrectly extends interface 'IA'. The types returned by 'go(. )' are incompatible between these types. Type 'number' is not assignable to type 'string'. */ interface IB extends IA < go(n: number): number; stop(n: number): number; >// ================================================= type A = < go(n: number): string; >// Без ошибок type B = A &
Интерфейсы в TS применяются повсюду: их используют для описания типов возвращаемых объектов из сетевых сервисов, сложных параметров функции, а также возвращаемых значений. Но этим их применение не исчерпывается, так как они играют роль абстракции и применяются для возможности расширяемости кода без его изменения, а также для инверсии зависимостей из принципов SOLID.
Также интерфейсы упрощают реализации паттернов программирования “Банды четырёх” (GoF) для описания таких подходов, как “Адаптер”, “Фабрика” или “Декоратор” и др.
Interface js что это
Интерфейс определяет свойства и методы, которые объект должен реализовать. Другими словами, интерфейс — это определение кастомного типа данных, но без реализации. В данном случае интерфейсы в TS похожи на интерфейсы в языках Java и C#. Интерфейсы определяются с помощью ключевого слова interface . Для начала определим простенький интерфейс:
interface IUser
Интерфейс в фигурных скобках определяет два свойства: id, которое имеет тип number, и name, которая представляет строку. Теперь используем его в программе:
let employee: IUser = < id: 1, name: "Tom" >console.log("id: ", employee.id); console.log("name: ", employee.name);
По сути employee — обычный объект за тем исключением, что он имеет тип IUser . Если правильнее говорить, то employee реализует интерфейс IUser. Причем эта реализация накладывает на employee некоторые ограничения. Так, employee должен реализовать все свойства и методы интерфейса IUser, поэтому при определении employee данный объект обязательно должен включать в себя свойства id и name.
Параметры методов и функций также могут представлять интерфейсы:
interface IUser < id: number; name: string; >let employee: IUser = < id: 1, name: "Alice" >function printUser(user: IUser): void < console.log("id: ", user.id); console.log("name: ", user.name) >printUser(employee);
В этом случае аргумент, который передается в функцию, должен представлять объект или класс, который реализует соответствующий интерфейс.
И также можно возвращать объекты интерфейса:
interface IUser < id: number; name: string; >function buildUser(userId: number, userName: string): IUser < return < id: userId, name: userName >; > let newUser = buildUser(2, "Bill"); console.log("id: ", newUser.id); console.log("name: ", newUser.name);
Необязательные свойства и свойства только для чтения
При определении интерфейса мы можем задать некоторые свойства как необязательные с помощью знака вопроса. Подобные свойства реализовать необязательно:
interface IUser < id: number; name: string; age?: number; >let employee: IUser = < id: 1, name: "Alice", age: 23 >let manager: IUser =
Свойство age помечено как необязательное, поэтому его можно не определять в объектах.
Также интерфейс может содержать свойства только для чтения, значение которых нельзя изменять. Такие свойства определяются с помощью ключевого слова readonly :
interface Point < readonly x: number; readonly y: number; >let p: Point = < x: 10, y: 20 >; console.log(p); // p.x = 5; // Ошибка - свойство доступно только для чтения
Определение методов
Кроме свойств интерфейсы могут определять функции:
interface IUser < id: number; name: string; sayWords(words: string): void; >let employee: IUser = < id: 1, name: "Alice", sayWords: function(words: string): void< console.log(`$говорит "$"`); > > employee.sayWords("Привет, как дела?");
Опять же объект, который реализует интерфейс, также обязан реализовать определенную в интерфейсе функцию с тем же набором параметров и тем типом выходного результата. В данном случае функция sayWords() в качестве параметра принимает строку и ничего возвращает, выводя на консоль некоторое сообщение.
Интерфейсы классов
Интерфейсы могут быть реализованы не только объектами, но и классами. Для этого используется ключевое слово implements :
interface IUser < id: number; name: string; getFullName(surname: string): string; >class User implements IUser < id: number; name: string; age: number; constructor(userId: number, userName: string, userAge: number) < this.id = userId; this.name = userName; this.age = userAge; >getFullName(surname: string): string < return this.name + " " + surname; >> let tom = new User(1, "Tom", 23); console.log(tom.getFullName("Simpson"));
Класс User реализует интерфейс IUser. В этом случае класс User обязан определить все те же свойства и функции, которые есть в IUser.
При этом объект tom является как объектом User, так и объектом IUser:
let tom :IUser = new User(1, "Tom", 23); //или let tom :User = new User(1, "Tom", 23);
Расширение интерфейса
TypeScript позволяет добавлять в интерфейс новые поля и методы, просто объявив интерфейс с тем же именем и определив в нем необходимые поля и методы. Например:
interface IUser < id: number; name: string; >interface IUser < age: number; >let employee: IUser = < id: 1, name: "Alice", age: 31 >function printUser(user: IUser): void < console.log(`id: $name: $ age: $`); > printUser(employee);
В данном случае первое определение интерфейса IUser содержит поля id и name . Второе определение интерфейса содержит объявление поля age . В итоге объект или класс, который реализует этот интерфейс, должен определить все три поля — id, name и age.
Наследование интерфейсов
Интерфейсы, как и классы, могут наследоваться:
interface IMovable < speed: number; move(): void; >interface ICar extends IMovable < fill(): void; >class Car implements ICar < speed: number; move(): void < console.log("Машина едет со скоростью " + this.speed + " км/ч"); >fill(): void < console.log("Заправляем машину топливом"); >> let auto = new Car(); auto.speed = 60; auto.fill(); auto.move();
После наследования интерфейс ICar будет также иметь все те свойства и функции, которые определены в IMovable. И тогда, класс Car, реализующий интерфейс ICar, должен будет реализовать также и свойства и методы интерфейса IMovable.
Интерфейсы функций
Интерфейсы функций содержат определение типа функции. Затем они должны быть реализованы объектом, который представляет функцию данного типа:
interface FullNameBuilder < (name: string, surname: string): string; >let simpleBuilder: FullNameBuilder = function (name:string, surname: string): string < return "Mr. " + name + " " + surname; >let fullName = simpleBuilder("Bob", "Simpson"); console.log(fullName); // Mr. Bob Simpson
Здесь определен интерфейс FullNameBuilder, который лишь содержит сигнатуру функции. Далее определяется переменная simpleBuilder, которая имеет тип FullNameBuilder и поэтому должна представлять функцию с данной сигнатурой.
Интерфейсы массивов
Интерфейсы массивов описывают объекты, к которым можно обращаться по индексу, как, например, к массивам
interface StringArray < [index: number]: string; >let phones: StringArray; phones = ["iPhone 7", "HTC 10", "HP Elite x3"]; let myPhone: string = phones[0]; console.log(myPhone);
Здесь определен интерфейс StringArray, который содержит сигнатуру массива. Эта сигнатура указывает, что объект, который реализует StringArray, может индексироваться с помощью чисел (объекта типа number). И, кроме того, данный объект должен хранить объекты типа string , то есть строки.
Выше индекс представлял тип number. Но мы можем использовать для индексации и тип string:
interface Dictionary < [index: string]: string; >var colors: Dictionary = <>; colors["red"] = "#ff0000"; colors["green"] = "#00ff00"; colors["blue"] = "#0000ff"; console.log(colors["red"]);
Гибридные интерфейсы
Интерфейсы могут сочетать различные стили, могут применяться сразу как к определению объекта, так и к определению функции:
interface PersonInfo < (name: string, surname: string):void; fullName: string; password: string; authenticate(): void; >function personBuilder(): PersonInfo < let person = function (name: string, surname: string): void< person.fullName = name + " " + surname; >; person.authenticate = function () < console.log(person.fullName + " входит в систему с паролем " + person.password); >; return person; > let tom = personBuilder(); tom("Tom", "Simpson"); tom.password = "qwerty"; tom.authenticate();
Тип функции, определяемый в таком гибридном интерфейсе, как правило, выступает в роли конструктора объекта. В данном случае такой конструктор имеет тип (name: string, surname: string):void; .
А функция, которая представляет данный интерфейс (в данном случае — функция personBuilder ), реализует эту функцию конструктора, и также может использовать другие свойства и методы, которые были определены в интерфейсе.