📜  JS++ |接口

📅  最后修改于: 2022-05-13 01:55:49.793000             🧑  作者: Mango

JS++ |接口

正如我们在关于子类型多态性的章节中所了解的,为超类型定义的操作可以安全地替换为其子类型。因此,为“Animal”对象定义的操作可以安全地接受“Cat”或“Dog”对象。

此外,我们提到您不应该将子类型化与继承混淆。子类型描述类型之间的关系,而继承与实现有关。这种区别在接口上变得很明显。

接口类似于我们刚刚学习的抽象类和方法,只是接口的所有“方法”都是抽象的。接口不包含任何实现。

接口指定类型,但不指定实现。让我们以交通为例。你需要从旧金山到洛杉矶。您可能不在乎如何到达那里,您只需要一种交通工具。接口没有定义“如何”,因此在这种情况下,接口将是“传输模式”。 “如何”将在诸如“Train”、“Car”、“Airplane”或“Bus”之类的类中实现。只要“交通工具”能动,就应该能满足你的需求。但是,您不能乘坐抽象的“交通工具”;您需要“汽车”、“火车”、“飞机”或“公共汽车”。

可视化这些关系:

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' 的'move' 方法的实现与'Airplane' 非常不同,因为飞机可以飞行。

从这些关系中,您应该能够理解您可以实例化上述类之一,例如“Car”,如下所示:

Car rentalCar = new Car();

假设您正在开发一个旅游网站。您想为用户提供从旧金山到洛杉矶的多种选择。在这种情况下,您可能需要泛化类型:

IModeOfTransport transport = new Car();

现在,当用户选择不同的选项(“汽车”除外)时,您可以轻松地将“运输”变量中保存的数据类型更改为“火车”、“飞机”或“公共汽车”的实例。

但是,您也认识到您无法通过“交通工具”从旧金山到达洛杉矶。你必须指定什么样的运输方式。因此,您应该认识到这将导致编译器错误:

IModeOfTransport transport = new IModeOfTransport();

因此,我们只使用接口来指定类型和类型关系。接口不提供实现,因此它们不能被实例化。

有了这种理解,我们就可以改变我们的“动物”类型的关系。首先,让我们从一个非常基本的示例开始。我们的一些动物缺乏颜色。它现在只是一个黑白网页。让我们添加一点颜色。

导航到您的 OOP 项目的“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;

external $;

module Animals
{
    class Dog : Animal, IColorizable
    {
        string _name;

        Dog(string name) {
            super("icofont-animal-dog");
            _name = name;
        }

        final void render() {
            $element.attr("title", _name);
            super.render();
        }

        final void talk() {
            alert("Woof!");
        }

        final void colorize() {
            $element.css("color", "brown");
        }
    }
}

现在,我们必须在“Dog”实例上调用 colorize() 方法。编辑 main.jspp:

import Animals;

external $;

IColorizable fido = new Dog("Fido");
fido.colorize();

Animal[] animals = [
    new Cat("Kitty"),
    new Cat("Kat"),
    fido, 
    new Panda(),
    new Rhino()
];

foreach(Animal animal in animals) {
    animal.render();
}

$("#content").append("

Number of animals: " + Animal.getCount().toString() + "

");

尝试编译。你会得到一个错误:

[  ERROR  ] JSPPE5034: Could not determine type for array literal. All elements in array literal must be the same, a mixture of primitive types, or descendants of the same base class at line 8 char 19 at main.jspp

发生这种情况的原因是因为“fido”变量的类型为“IColorizable”。同时,该数组仅接受“Animal”类型的元素。如果您还记得,我们的“Dog”类直接继承自“IColorizable”,而我们的“Animal”基类则没有。因此,我们不能将“IColorizable”的对象直接插入“Animal”类型的数组中;否则,我们将能够像在 JavaScript 中那样执行不安全的操作。

请注意,在图表中,“Animal”和“IColorizable”之间没有类型关系。

有多种方法可以解决此问题。我们将通过使我们所有的动物可着色来解决它。编辑 Dog.jspp 并删除与“IColorizable”的类型关系。但是,保留“colorize”的方法实现。稍后你会明白为什么。这是您必须在 Dog.jspp 中删除的内容的可视化:

import Externals.DOM;

external $;

module Animals
{
    class Dog : Animal, IColorizable
    {
        string _name;

        Dog(string name) {
            super("icofont-animal-dog");
            _name = name;
        }

        final void render() {
            $element.attr("title", _name);
            super.render();
        }

        final void talk() {
            alert("Woof!");
        }

        final void colorize() {
            $element.css("color", "brown");
        }
    }
}

现在打开 Animal.jspp 并添加:

external $;

module Animals
{
    abstract class Animal : IColorizable
    {
        protected var $element;
        private static unsigned int count = 0;

        protected Animal(string iconClassName) {
            string elementHTML = makeElementHTML(iconClassName);
            $element = $(elementHTML);

            attachEvents();

            Animal.count++;
        }

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

        public virtual void render() {
            $("#content").append($element);
        }

        public abstract void colorize();

        public abstract void talk();
        private void attachEvents() {
            $element.click(talk);
        }

        private string makeElementHTML(string iconClassName) {
            string result = '
'; result += ''; result += "
"; return result; } } }

由于我们的“Animal”类是“抽象的”,我们不需要“实现”“colorize”方法。相反,我们只是通过将 'colorize' 方法声明为 'abstract' 将其推迟到 'Animal' 的派生类。还记得我们没有从 'Dog' 类中删除 'colorize' 方法吗?这就是为什么。我们已经将实现 'colorize' 的责任委托给了 'Animal' 的派生类,但我们仍然能够描述 'Animal' 和 'IColorizable' 之间的类型关系,其中 'IColorizable' 是 'Animal' 的超类型'。

现在,我们只需要为我们的其他动物添加颜色。我会保持简短。这是您需要添加到“Cat.jspp”、“Panda.jspp”和“Rhino.jspp”的代码类型的模板:

final void colorize() {
    $element.css("color", colorName);
}

将“colorName”替换为您想要制作动物的颜色。让你的猫“金”,熊猫“黑”,犀牛“银”。

编辑 main.jspp:

import Animals;

external $;

Animal[] animals = [
    new Cat("Kitty"),
    new Cat("Kat"),
    new Dog("Fido"), 
    new Panda(),
    new Rhino()
];

foreach(Animal animal in animals) {
    animal.colorize();
    animal.render();
}

$("#content").append("

Number of animals: " + Animal.getCount().toString() + "

");

编译:

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

打开 index.html。结果应如下所示: