Is-a
Is-a (англ. is-a, также «является частным случаем», «является видом», «является типом») — это термин, обозначающий отношение субсумпции в области онтологий, репрезентации знаний, объектно-ориентированного программирования и объектно-ориентированного анализа и проектирования. В данном контексте отношение «is-a» выражает вложенность абстракций (например, типов или классов), при которой один класс A является подклассом другого класса B (и, соответственно, B — надклассом класса A).
Другими словами, тип A является подтипом типа B, если спецификация A влечёт спецификацию B. То есть любой объект (или класс), удовлетворяющий спецификации A, автоматически удовлетворяет и спецификации B, поскольку последняя является более общей[1].
Например, кошка — это частный случай животного, но не наоборот. Все кошки — животные, однако далеко не все животные являются кошками. Поведение, общее для всех животных, определяется в классе животных, а поведение, характерное только для кошек — в классе кошек. Таким образом, при объявлении класса «Кошка» как специализации класса «Животное», все экземпляры класса «Кошка» наследуют поведение, определённое для животных, без необходимости повторного его описания.
Связанные понятия
Отношение «is-a» («является частным случаем»), в отличие от отношения «has-a» («имеет») между типами (классами), необходимо различать при проектировании моделей (например, компьютерной программы), чтобы не допустить ошибочного смешения иерархии наследования и иерархии композиции. Кроме того, отношение «is-a» отличается от отношения «является экземпляром» между объектами (экземплярами) и их типами или классами (см. различие типа и сущности). В современном объектно-ориентированном программировании предпочтение часто отдаётся композиции (отношение «has-a») перед наследованием («is-a»). Композиция обеспечивает слабую связанность, большую гибкость и значительно упрощает модульное тестирование, в частности, мокирование зависимостей[2].[3]
Краткое описание различных отношений:
- гипероним-гипоним (надтип/надкласс — подтип/подкласс): отношения между типами (или классами), определяющие таксономическую иерархию. Для отношения субсумпции: гипоним (подтип, подкласс) находится в отношениях «is-a» со своим гиперонимом (надтипом, надклассом);
- холонимия-меронимия («целое»/«контейнер» — «часть»/«элемент»): отношения между типами (или классами), определяющие иерархию владения и включения.
Для отношения агрегации (без владения): холоним (целое) находится в отношении «has-a» («имеет») со своим меронимом (частью). Для отношения композиции (с владением): мероним (конституент) находится в отношении «является частью» по отношению к своему холониму (целому). Для отношения containment (контейнирования): мероним (элемент) — в отношении «является членом» со своим холонимом (контейнером)[4].
- концепт-объект (тип-экземпляр): отношение между типом (или классом) и его объектами (экземплярами), где экземпляр находится в отношении «является экземпляром» («instance of») по отношению к своему типу (классу).
В современных языках онтологий, таких как OWL 2, строгое различие между отношениями «is-a» (подкласс) и «instance-of» (экземпляр) может размываться благодаря механизму punning (каламбур). Этот механизм позволяет использовать один и тот же идентификатор одновременно в качестве класса и экземпляра для целей метамоделирования[5].
Примеры подтипизации
Подтипизация позволяет заменить один тип другим типом или абстракцией. Таким образом, подтипизация устанавливает отношение is-a между подтипом и определённой абстракцией — в зависимости от возможностей языка либо явно (эксплицитно), либо неявно. В языках программирования с поддержкой наследования как механизма подтипизации это отношение выражается непосредственно через наследование.
C++
Следующий пример на языке C++ демонстрирует явную связь наследования между классами A и B, где B — подкласс и одновременно подтип класса A. Класс B может быть использован везде, где ожидается класс A (через ссылку, указатель или сам объект):
class A
{ public:
void DoSomethingALike() const {}
};
class B : public A
{ public:
void DoSomethingBLike() const {}
};
void UseAnA(A const& some_A)
{
some_A.DoSomethingALike();
}
void SomeFunc()
{
B b;
UseAnA(b); // b может быть подставлен вместо A
}
Современные стандарты C++ предоставляют новые механизмы для проверки отношений «is-a» на этапе компиляции. В стандарте C++20 был введён концепт std::derived_from, позволяющий строго валидировать публичное наследование[6]. В стандарте C++26 ожидается внедрение статической рефлексии, которая предоставит возможности для глубокого метапрограммного анализа иерархий классов[7].
Python
Следующий пример на языке Python показывает явное наследование между классами A и B. Класс B является подклассом и подтипом класса A, а потому может быть использован везде, где требуется объект типа A.
class A:
def do_something_a_like(self):
pass
class B(A):
def do_something_b_like(self):
pass
def use_an_a(some_a):
some_a.do_something_a_like()
def some_func():
b = B()
use_an_a(b) # b может быть подставлен вместо A
В следующем примере type(a) — «обычный» тип, а type(type(a)) — метатип. Хотя все встроенные типы Python имеют один и тот же метатип PyType_Type (который также является собственным метатипом), это не является обязательным: например, классические классы с типом types.ClassType можно рассматривать как самостоятельный метатип[8].
>>> a = 0
>>> type(a)
<type 'int'>
>>> type(type(a))
<type 'type'>
>>> type(type(type(a)))
<type 'type'>
>>> type(type(type(type(a))))
<type 'type'>
Начиная с версии Python 3.8 (PEP 544), отношение «is-a» может быть реализовано через структурную подтипизацию с использованием протоколов (typing.Protocol). Класс считается подтипом протокола, если он реализует требуемый интерфейс, без необходимости явного наследования[9].
Java
В языке Java отношение is-a между типами одной иерархии классов или интерфейсов выражается с помощью ключевых слов extends и implements. Например, класс ArrayList<E> реализует интерфейс List<E>, а List<E> расширяет Collection<E>. Следовательно, ArrayList<String> является подтипом List<String>, который, в свою очередь, является подтипом Collection<String>. Отношение подтипизации транзитивно. Объявляя интерфейс PayloadList, связывающий с каждым элементом дополнительное значение произвольного типа, его объявление может выглядеть следующим образом:
interface PayloadList<E, P> extends List<E> {
void setPayload(int index, P val);
...
}
Следующие параметризации типа PayloadList являются подтипами типа List<String>:
PayloadList<String, String>
PayloadList<String, Integer>
PayloadList<String, Exception>
В современных версиях Java (начиная с Java 17) появился механизм строгого контроля над иерархией «is-a» с помощью запечатанных (sealed) классов и интерфейсов. В отличие от традиционной модели наследования, они позволяют автору API явно перечислять разрешённые подклассы, используя модификатор sealed и ключевое слово permits. Это создаёт закрытую и заранее известную иерархию, где только указанные типы могут быть прямыми наследниками[10].
Принцип подстановки Лисков
Принцип подстановки Лисков (LSP) формулирует корректность подтипизации следующим образом: «Если для любого объекта o1 типа S существует объект o2 типа T, такой, что во всех программах P, написанных в терминах T, поведение P остаётся неизменным при замене o2 на o1, то S является подтипом T»[11].
Следующий пример иллюстрирует нарушение принципа подстановки Лисков:
class Rectangle
{
public:
void SetWidth(double w) { itsWidth = w; }
void SetHeight(double h) { itsHeight = h; }
double GetHeight() const { return itsHeight; }
double GetWidth() const { return itsWidth; }
double GetArea() const { return GetHeight() * GetWidth(); }
private:
double itsWidth;
double itsHeight;
};
С точки зрения кода, класс Square может быть реализован с наследованием от Rectangle:
public class Square : Rectangle
{
public:
virtual void SetWidth(double w);
virtual void SetHeight(double h);
};
void Square::SetWidth(double w)
{
Rectangle::SetWidth(w);
Rectangle::SetHeight(w);
}
void Square::SetHeight(double h)
{
Rectangle::SetHeight(h);
Rectangle::SetWidth(h);
}
Однако такая реализация нарушает LSP: несмотря на отношение «is-a» между обобщённым прямоугольником и квадратом, подстановка подкласса «Квадрат» вместо «Прямоугольник» может привести к некорректному поведению.
Рассмотрим, например, следующую функцию, где предположенный результат не выполняется, если передаётся экземпляр класса Square:
void g(Rectangle& r)
{
r.SetWidth(5);
r.SetHeight(4);
assert(r.GetArea() == 20); // утверждение не выполнится!
}
Если же рассматривать ограничение на тип фигуры как на отношение между размерами, то предположения в g() (например, что SetHeight изменяет только высоту, не затрагивая ширину) могут быть неверными не только для настоящих квадратов, но и для других подклассов Rectangle, поведение которых может быть специальным[12].
Концептуальное (таксономическое) отношение «is-a» не всегда гарантирует поведенческую подтипизацию. Принцип подстановки Лисков требует строгого соблюдения контрактов и ожидаемого поведения базового типа[13].
Принцип подстановки Лисков фундаментально применим к семантическим моделям данных и онтологиям (например, OWL). Отношение rdfs:subClassOf требует полного соответствия подкласса семантике надкласса для обеспечения корректности логических выводов.
Применение в современных технологиях
Искусственный интеллект и онтологии
В 2025—2026 годах значительно возросла роль отношения «is-a» в системах искусственного интеллекта. Оно является основой для построения онтологий и машиночитаемых контекстных слоёв (англ. AI-readable context layers), обеспечивая семантическую интероперабельность ИИ-агентов и логический вывод через механизм наследования свойств[14]. В архитектуре гибридных нейросимволических систем иерархии «is-a» служат символическим каркасом, который направляет работу нейронных сетей, повышая их надёжность и объяснимость[15][16].
Базы данных и SQL
В современных реляционных базах данных для реализации семантических и иерархических связей применяются различные подходы. Стандарт SQL:2023 ввёл расширение SQL/PGQ (Property Graph Queries), которое позволяет выполнять графовые запросы поверх существующих реляционных таблиц. Это нововведение предоставляет стандартизированный синтаксис для работы со связями и типами узлов, позволяя эффективно моделировать отношения, близкие к «is-a», непосредственно на уровне запросов[17].[18]
Примечания
Литература
- Brachman, Ronald J. (октябрь 1983). “What IS-A is and isn't. An Analysis of Taxonomic Links in Semantic Networks”. IEEE Computer [англ.]. 16 (10). Дата обращения 2024-06-17. Проверьте дату в
|date=(справка на английском) - Hainaut, Jean-Luc. Understanding Implementations of IS-A Relations. ER 1996: 42-57 : [англ.] / Jean-Luc Hainaut, Jean-Marc Hick, Vincent Englebert … [et al.]. — 1996. — P. 42–57.