В представлении знаний и компонентах онтологии , включая объектно-ориентированное программирование и проектирование , is-a (также пишется как is_a или is a ) — это отношение включения [a] между абстракциями (например, типами , классами ), где один класс A является подклассом другого класса B (и поэтому B является суперклассом A ). Другими словами, тип A является подтипом типа B, когда спецификация A подразумевает спецификацию B. То есть любой объект (или класс), который удовлетворяет спецификации A, также удовлетворяет спецификации B, потому что спецификация B слабее. [1]
Например, кошка «является» животным, но не наоборот. Все кошки являются животными, но не все животные являются кошками. Поведение, относящееся ко всем животным, определяется в классе животных, тогда как поведение, относящееся только к кошкам, определяется в классе кошек. Определяя класс кошек как «расширение» класса животных, все кошки «наследуют» поведение, определенное для животных, без необходимости явно кодировать это поведение для кошек.
Отношение is-a следует противопоставлять отношению has-a ( has_a или has a ) между типами (классами); путаница между отношениями has-a и is-a является распространенной ошибкой при проектировании модели (например, компьютерной программы ) реальных отношений между объектом и его подчиненным. Отношение is-a также можно противопоставить отношению instance-of между объектами (экземплярами) и типами (классами): см. Различие типа и токена .
Подводя итоги отношений, можно выделить:
Подтипирование позволяет заменять заданный тип другим типом или абстракцией. Подтипирование, как говорят, устанавливает связь is-a между подтипом и некоторой существующей абстракцией, либо неявно, либо явно, в зависимости от поддержки языка. Связь может быть выражена явно через наследование в языках, которые поддерживают наследование как механизм подтипирования.
Следующий код C++ устанавливает явные отношения наследования между классами B и A , где B является как подклассом, так и подтипом A и может использоваться как A везде, где указан B (через ссылку, указатель или сам объект).
класс A { public : void DoSomethingALike () const {} }; класс B : public A { public : void DoSomethingBLike () const {} }; void UseAnA ( A const & some_A ) { some_A . DoSomethingALike (); } void SomeFunc () { B b ; UseAnA ( b ); // b можно заменить на A. }
[3]
Следующий код Python устанавливает явную связь наследования между классами B и A , где B является как подклассом, так и подтипом A и может использоваться как A везде, где требуется B.
класс A : def do_something_a_like ( self ): проходкласс B ( A ): def do_something_b_like ( self ): проход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)) — метатип. Хотя при распределении все типы имеют один и тот же метатип ( PyType_Type , который также является их собственным метатипом), это не является обязательным требованием. Тип классических классов, известный как types.ClassType , также можно считать отдельным метатипом. [4]
>>> a = 0 >>> тип ( a ) <тип 'int'> >>> тип ( тип ( a )) <тип 'тип'> >>> тип ( тип ( тип ( a ))) < тип ' тип'> >>> тип ( тип ( тип ( a )))) <тип 'тип'>
В Java отношение между параметрами типа одного класса или интерфейса и параметрами типа другого определяется предложениями extends и implements .
Используя Collections
классы, ArrayList<E>
реализует List<E>
и List<E>
расширяет Collection<E>
. Так же ArrayList<String>
является подтипом List<String>
, который является подтипом Collection<String>
. Отношения подтипирования сохраняются между типами автоматически. При определении интерфейса , PayloadList
который связывает необязательное значение универсального типа P с каждым элементом, его объявление может выглядеть так:
интерфейс PayloadList < E , P > расширяет List <E> { void setPayload ( int index , P val ) ; ... }
Следующие параметризации PayloadList являются подтипами List<String>
:
PayloadList < Строка , Строка > PayloadList < Строка , Целое число > PayloadList < Строка , Исключение >
Принцип подстановки Лисков объясняет свойство: «Если для каждого объекта o1 типа S существует объект o2 типа T такой, что для всех программ P, определенных в терминах T, поведение P не изменяется при замене o1 на o2, то S является подтипом T» . [5] Следующий пример демонстрирует нарушение LSP.
Вот, пожалуй, пример нарушения LSP:
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, даже несмотря на то, что между Rectangle и Square сохраняется связь is-a .
Рассмотрим следующий пример, в котором функция g не работает, если передан квадрат, и поэтому можно считать, что принцип открытости-закрытости нарушен.
void g ( Rectangle & r ) { r.SetWidth ( 5 ) ; r.SetHeight ( 4 ) ; assert ( r.GetArea ()) == 20 ) ; // утверждение не будет выполнено }
И наоборот, если учесть, что тип фигуры должен быть ограничением только на соотношение ее размеров, то предположение в g() о том, что SetHeight изменит высоту и площадь, но не ширину, является недействительным не только для настоящих квадратов, но и потенциально для других прямоугольников, которые могут быть закодированы таким образом, чтобы сохранять площадь или соотношение сторон при изменении высоты.
[6]
{{cite book}}
: CS1 maint: unfit URL (link)