Шрифт:
Вышеприведенный класс Person демонстрирует отношение типа «содержит». Объект Person имеет имя, адрес, номера телефона и факса. Нельзя сказать, что человек «есть разновидность» имени или что человек «есть разновидность» адреса. Можно сказать, что человек «имеет» («содержит») имя и адрес. Большинство людей не испытывают затруднений при проведении подобных различий, поэтому путаница между ролями «является» и «содержит» возникает сравнительно редко.
Чуть сложнее провести различие между отношениями «является» и «реализуется посредством». Например, предположим, что вам нужен шаблон для классов, представляющих множества произвольных объектов, то есть наборов без дубликатов. Поскольку повторное использование – прекрасная вещь, то сразу возникает желание обратиться к шаблону set из стандартной библиотеки. В конце концов, зачем писать новый шаблон, когда есть возможность использовать уже готовый?
К сожалению, реализации set обычно влекут за собой накладные расходы – по три указателя на элемент. Связано это с тем, что множества обычно реализованы в виде сбалансированных деревьев поиска, гарантирующих логарифмическое время поиска, вставки и удаления. Когда быстродействие важнее, чем объем занимаемой памяти, это вполне разумное решение, но конкретно для вашего приложения выясняется, что экономия памяти более существенна. Поэтому стандартный шаблон set для вас неприемлем. Похоже, нужно писать свой собственный.
Тем не менее повторное использование – прекрасная вещь. Будучи экспертом в области структур данных, вы знаете, что среди многих вариантов реализации множеств есть и такой, который базируется на применении связанных списков. Вы также знаете, что в стандартной библиотеке C++ есть шаблон list, поэтому решаете им воспользоваться (повторно).
В частности, вы решаете, что создаваемый вами шаблон Set должен наследовать от list. То есть Set<T> будет наследовать list<T>. В итоге в вашей реализации объект Set будет выступать как объект list. Соответственно, вы объявляете Set следующим образом:
До сих пор все вроде бы шло хорошо, но, если присмотреться, в код вкралась ошибка. Как объясняется в правиле 32, если D является разновидностью B, то все, что верно для B, должно быть верно также и для D. Однако объект list может содержать дубликаты, поэтому если значение 3051 вставляется в list<int> дважды, то список будет содержать две копии 3051. Напротив, Set не может содержать дубликатов, поэтому, если значение 3051 вставляется в Set<int> дважды, множество будет содержать лишь одну копию данного значения. Следовательно, утверждение, что Set является разновидностью list, ложно: ведь некоторые положения, верные для объектов list, неверны для объектов Set.
Из-за этого отношение между этими двумя классами не подходит под определение «является», открытое наследование – неправильный способ моделирования этой взаимосязи. Правильный подход основан на понимании того факта, что объект Set может быть реализован посредством объекта list:
Функции-члены класса Set могут опереться на функциональность, предоставляемую list и другими частями стандартной библиотеки, поэтому их реализацию нетрудно написать, коль скоро вам знакомы основы программирования с применением библиотеки STL: