JS ++ | Интерфейсы

Опубликовано: 10 Апреля, 2022

Как мы узнали из главы, посвященной полиморфизму подтипа, операцию, определенную для супертипа, можно безопасно заменить ее подтипами. В результате операции, определенные для объектов «Животное», могут безопасно принимать объекты «Кошка» или «Собака».

Кроме того, мы упоминали, что не следует путать подтипирование с наследованием. Подтипирование описывает отношения между типами, тогда как наследование касается реализаций. Это различие становится понятным с интерфейсами.

Интерфейсы похожи на абстрактные классы и методы, о которых мы только что узнали, за исключением того, что все «методы» интерфейса абстрактны. Интерфейс не содержит реализаций.

Интерфейсы определяют типы, но не определяют реализации. Возьмем аналогию с транспортом. Вам нужно попасть из Сан-Франциско в Лос-Анджелес. Возможно, вам все равно, как вы туда доберетесь, вам просто нужен вид транспорта. Интерфейсы не определяют «как», поэтому интерфейс в этом случае будет «видом транспорта». «Как» будет реализовано в таких классах, как «Поезд», «Автомобиль», «Самолет» или «Автобус». Пока «вид транспорта» может перемещаться, он должен соответствовать вашим потребностям. Однако вы не можете ездить на абстрактном «транспортном средстве»; вам нужен «Автомобиль», «Поезд», «Самолет» или «Автобус».

Чтобы визуализировать эти отношения:

interface IModeOfTransport
{
    void move();
}

class Car : IModeOfTransport
{
    override void move() {
        // ...
    }
}

class Train : IModeOfTransport
{
    override void move() {
        // ...
    }
}

class Airplane : IModeOfTransport
{
    override void move() {
        // ...
    }
}

class Bus : IModeOfTransport
{
    override void move() {
        // ...
    }
}

Каждый конкретный класс действует по-своему. Например, «Автомобиль» будет иметь совсем другую реализацию для метода «перемещения», чем «Самолет», потому что самолеты могут летать.

Из этих отношений вы должны понять, что можете создать экземпляр одного из вышеперечисленных классов, например «Автомобиль», следующим образом:

 Car rentalCar = новая машина ();

Допустим, вы разрабатываете туристический сайт. Вы хотите предоставить пользователю несколько вариантов проезда из Сан-Франциско в Лос-Анджелес. В этом случае вы можете обобщить тип:

 IModeOfTransport transport = new Car ();

Теперь, когда пользователь выбирает другой вариант (отличный от «Автомобиль»), вы можете легко изменить тип данных, содержащихся в переменной «транспорт», на экземпляр «Поезд», «Самолет» или «Автобус».

Однако вы также понимаете, что вы не можете добраться из Сан-Франциско в Лос-Анджелес «на транспорте». Вы должны указать, какой вид транспорта. Таким образом, вы должны понимать, что это приведет к ошибке компилятора:

 IModeOfTransport транспорт = новый IModeOfTransport ();

Поэтому мы используем интерфейсы только для определения типов и отношений типов. Интерфейсы не предоставляют реализации, поэтому их нельзя создать.

Обладая этим пониманием, мы можем изменить наши отношения типа «Животные». Во-первых, давайте начнем с очень простого примера. Некоторым из наших животных не хватает цвета. Сейчас это просто черно-белая веб-страница. Добавим немного цвета.

Перейдите в папку src / Animals / вашего проекта ООП, содержащую Animal.jspp, Cat.jspp, Dog.jspp и т. Д. Внутри этой папки создайте новый файл с именем IColorizable.jspp. Внутри IColorizable.jspp введите этот код:

module Animals
{
    interface IColorizable
    {
        void colorize();
    }
}

Первое, что вы заметите, это то, что мы добавили к имени нашего интерфейса префикс «I» (заглавная «i»). Это известно как соглашение об именах . Соглашения об именах помогают нам называть классы, интерфейсы, модули, функции и переменные, которые будут согласованы с именами, используемыми другими программистами, которые присоединяются к нашей команде, другим сторонним библиотекам и так далее. В JS ++ к именам интерфейсов обычно добавляется буква «I» (заглавная «i»).

Обратите внимание, что нам не нужен модификатор «абстрактный». Все сигнатуры методов внутри интерфейса считаются абстрактными, поскольку реализации внутри интерфейса не разрешены.

Затем мы должны указать, какие классы имеют отношение к IColorizable. А пока дадим цвет только классу Dog:

import Externals.DOM;

внешний $;

модуль Животные
{
    класс Собака: Животное , IColorizable
    {
        строка _name;

        Dog (название строки) {
            супер («икофонт-животное-собака»);
            _name = имя;
        }

        final void render () {
            $ element.attr ("название", _name);
            super.render ();
        }

        final void talk () {
            alert («Гав!»);
        }

        final void colorize () {
            $ element.css ("цвет", "коричневый");
        }
    }
}

Теперь нам нужно вызвать метод colorize () в нашем экземпляре Dog. Отредактируйте main.jspp:

импортные животные;

внешний $;

IColorizable fido = новая собака ("Фидо");
fido.colorize ();

Животное [] животные = [
    новый Кот ("Китти"),
    новый Кот ("Кэт"),
    фидо
    новая панда (),
    новый носорог ()
];

foreach (животное животное в животных) {
    animal.render ();
}

$ ("# content"). append ("<p> Количество животных:" + Animal.getCount (). toString () + "</p>");

Попробуй скомпилировать. Вы получите сообщение об ошибке:

 [ОШИБКА] JSPPE5034: не удалось определить тип литерала массива. Все элементы в литерале массива должны быть одинаковыми, смесь примитивных типов или потомков одного и того же базового класса в строке 8 char 19 в main.jspp

Причина этого в том, что переменная fido имеет тип IColorizable. Между тем, массив принимает только элементы типа Animal. Если вы помните, наш класс «Dog» наследуется напрямую от «IColorizable», а наш базовый класс «Animal» - нет. Следовательно, мы не можем вставить объект «IColorizable» непосредственно в массив типа «Animal»; в противном случае мы могли бы выполнять небезопасные операции, как в JavaScript.

Обратите внимание на то, что на диаграмме нет отношения типов между Animal и IColorizable.

Есть несколько способов решить эту проблему. Мы исправим это, сделав всех наших животных раскрашиваемыми. Отредактируйте Dog.jspp и удалите отношение типа к «IColorizable». Однако оставьте реализацию метода для colorize. Позже вы поймете, почему. Вот визуализация того, что вам нужно удалить в Dog.jspp:

import Externals.DOM;

внешний $;

модуль Животные
{
    класс Собака: Животное , IColorizable
    {
        строка _name;

        Dog (название строки) {
            супер («икофонт-животное-собака»);
            _name = имя;
        }

        final void render () {
            $ element.attr ("название", _name);
            super.render ();
        }

        final void talk () {
            alert («Гав!»);
        }

        final void colorize () {
            $ element.css ("цвет", "коричневый");
        }
    }
}

Теперь откройте Animal.jspp и добавьте:

внешний $;

модуль Животные
{
    абстрактный класс Animal : IColorizable
    {
        защищенный элемент var $;
        частный статический беззнаковый int count = 0;

        protected Animal (string iconClassName) {
            строковый элементHTML = makeElementHTML (iconClassName);
            $ element = $ (elementHTML);

            attachEvents ();

            Animal.count ++;
        }

        public static unsigned int getCount () {
            return Animal.count;
        }

        public virtual void render () {
            $ ("# содержание"). append ($ element);
        }

        public abstract void colorize ();

        public abstract void talk ();
        private void attachEvents () {
            $ element.click (разговор);
        }

        частная строка makeElementHTML (строка iconClassName) {
            строка result = '<div class = "animal">';
            результат + = '<i class = "icofont' + iconClassName + '"> </i>';
            результат + = "</div>";
            вернуть результат;
        }
    }
}

Поскольку наш класс Animal является абстрактным, нам не нужно «реализовывать» метод colorize. Вместо этого мы просто откладываем его на шаг дальше к производным классам от Animal, объявив метод colorize как абстрактный. Помните, как мы не убрали метод colorize из класса Dog? Вот почему. Мы делегировали ответственность за реализацию 'colorize' производным классам 'Animal', но мы все же смогли описать отношения типов между 'Animal' и 'IColorizable', где 'IColorizable' - это супертип 'Animal'. '.

Теперь нам просто нужно добавить цвета остальным нашим животным. Я буду краток. Вот шаблон кода, который вам нужно добавить в Cat.jspp, Panda.jspp и Rhino.jspp:

final void colorize () {
    $ element.css ("цвет", colorName);
}

Замените colorName цветом, которого хотите сделать животным. Сделайте своих кошек «золотыми», панд - «черными», а носорогов - «серебряными».

Отредактируйте main.jspp:

импортные животные;

внешний $;

Животное [] животные = [
    новый Кот ("Китти"),
    новый Кот ("Кэт"),
    новая собака ("Фидо"),
    новая панда (),
    новый носорог ()
];

foreach (животное животное в животных) {
    animal.colorize ();
    animal.render ();
}

$ ("# content"). append ("<p> Количество животных:" + Animal.getCount (). toString () + "</p>");

Скомпилировать:

 $ js ++ src / -o build / app.jspp.js

Откройте index.html. Результат должен выглядеть так: