В компонентах представления знаний и онтологии , в том числе для объектно-ориентированного программирования и проектирования , 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 также можно противопоставить отношение экземпляра между объектами (экземплярами) и типами (классами): см. Различие типа и токена .
Подводя итоги отношений, можно выделить:
Подтипирование позволяет заменить данный тип другим типом или абстракцией. Говорят, что подтипирование устанавливает связь между подтипом и некоторой существующей абстракцией, явно или неявно, в зависимости от языковой поддержки. Отношения могут быть выражены явно через наследование в языках, которые поддерживают наследование как механизм подтипирования.
Следующий код C++ устанавливает явные отношения наследования между классами B и A , где B является одновременно подклассом и подтипом A и может использоваться как A везде, где указан B (через ссылку, указатель или сам объект). ).
класс A { общественный : недействительный DoSomethingALike () const {} }; класс B : public A { public : void DoSomethingBLike () const {} }; void UseAnA ( A const & some_A ) { some_A . СделатьЧто-нибудьAlike (); } недействительный SomeFunc () { B b ; УсеАнА ( б ); // b можно заменить на A. }
[3]
Следующий код Python устанавливает явные отношения наследования между классами B и A , где B является одновременно подклассом и подтипом A и может использоваться как A везде, где требуется B.
класс A : защита do_something_a_like ( self ): пройтикласс B ( A ): def do_something_b_like ( self ): пройтизащита use_an_a ( some_a ): some_a . сделать_что-то_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 is- отношение между параметрами типа одного класса или интерфейса и параметрами типа другого определяется предложениями расширения и реализации .
Использование Collections
классов ArrayList<E>
реализует List<E>
и List<E>
расширяет Collection<E>
. То же самое ArrayList<String>
относится и к подтипу List<String>
, который является подтипом Collection<String>
. Отношения подтипов сохраняются между типами автоматически. При определении интерфейса, PayloadList
который связывает необязательное значение универсального типа P с каждым элементом, его объявление может выглядеть так:
интерфейс PayloadList < E , P > расширяет список <E> { void setPayload ( int index , P val ) ; ... }
Следующие параметризации PayloadList являются подтипами List<String>
:
PayloadList < String , String > PayloadList < String , Integer > PayloadList < String , Exception >
Принцип замены Лискова объясняет следующее свойство: «Если для каждого объекта o1 типа S существует объект o2 типа T такой, что для всех программ P, определенных в терминах T, поведение P не меняется, когда o1 заменяется на o2, тогда S является подтипом T», . [5] Следующий пример демонстрирует нарушение LSP.
Вот, пожалуй, пример нарушения LSP:
класс Rectangle { public : void SetWidth ( double w ) { itsWidth = w ; } void SetHeight ( двойной час ) { itsHeight = час ; } Double GetHeight () const { return itsHeight ; } Double GetWidth () const { return itsWidth ; } Double GetArea () const { return GetHeight () * GetWidth (); } Частное : удвоить свою ширину ; удвоить его высоту ; };
С точки зрения программирования класс Square может быть реализован путем наследования класса Rectangle.
общественный класс Square : Rectangle { public : virtual void SetWidth ( double w ); виртуальная пустота SetHeight ( double h ); }; void Square::SetWidth ( double w ) { Rectangle :: SetWidth ( w ); Прямоугольник :: SetHeight ( w ); } void Square::SetHeight ( double h ) { Rectangle :: SetHeight ( h ); Прямоугольник :: SetWidth ( h ); }
Однако это нарушает LSP, хотя между Rectangle и Square сохраняется отношение is-a.
Рассмотрим следующий пример, где функция g не работает, если ей передан квадрат, и поэтому принцип открытости-закрытости можно считать нарушенным.
пустота г ( Прямоугольник & р ) { р . SetWidth ( 5 ); р . УстановитьВысоту ( 4 ); утверждать ( r . GetArea ()) == 20 ); // утверждение не удастся }
И наоборот, если учесть, что тип фигуры должен ограничивать только соотношение ее размеров, то предположение в g() о том, что SetHeight будет изменять высоту и площадь, но не ширину, является недопустимым, а не только для настоящих квадратов, но даже потенциально для других прямоугольников, которые могут быть закодированы так, чтобы сохранять площадь или соотношение сторон при изменении высоты.
[6]
{{cite book}}
: CS1 maint: unfit URL (link)