Подумайте хороший и жесткий о том, что вы действительно хотите MyClass
для. Я заметил, что некоторые программисты пишут обертки для своих коллекций просто по привычке, независимо от того, имеют ли они какие-то особые потребности, помимо тех, которые встречаются в стандартных коллекциях STL. Если это ваша ситуация, то typedef std::list<MyContainedClass> MyClass
и покончим с этим.
Если у вас действительно есть операции, которые вы собираетесь реализовать в MyClass
, то успех вашей инкапсуляции будет зависеть больше от интерфейса, который вы им предоставляете, чем от того, как вы предоставляете доступ к базовому списку.
Не в обиду, но ... Из-за предоставленной вами ограниченной информации она пахнет так, как будто вы пытаетесь: раскрыть внутренние данные, потому что вы не можете понять, как реализовать свои операции. Код клиента требует в MyClass
... или, возможно, потому что вы даже не знаете , какие операции потребует ваш код клиента. Это классическая проблема с попыткой написать низкоуровневый код перед высокоуровневым кодом, который в этом нуждается; вы знаете, с какими данными вы будете работать, но на самом деле еще точно не определили, что вы будете делать с ними, поэтому вы пишете структуру класса, которая предоставляет необработанные данные вплоть до самого верха. Вы бы хорошо переосмыслили свою стратегию здесь.
@ cos :
Конечно, я инкапсулирую
MyContainedClass не только ради
инкапсуляции. Давайте возьмем больше
конкретный пример:
Ваш пример мало помогает, чтобы развеять мой страх, что вы пишете свои контейнеры, прежде чем узнаете, для чего они будут использоваться. Ваш пример контейнера-контейнера - Document
- имеет в общей сложности три метода: NewParagraph()
, DeleteParagraph()
и GetParagraph()
, каждый из которых работает с содержащейся коллекцией (std::list
), и все из которых тесно отражает операции, которые std::list
предоставляет «из коробки». Document
инкапсулирует std :: list в том смысле, что клиентам не нужно знать о его использовании в реализации ... но реально это немного больше, чем фасад - так как вы предоставляете необработанные указатели клиентов на объекты, хранящиеся в список, клиент по-прежнему неявно привязан к реализации.
Если мы помещаем объекты (не указатели) в
контейнер они будут уничтожены
автоматически (что хорошо).
Хорошо это или плохо, зависит от потребностей вашей системы. Что означает эта реализация, просто: документ владеет Paragraph
s, и когда Paragraph
удаляется из документа, любые указатели на него немедленно становятся недействительными. Это означает, что вы должны быть очень осторожными при реализации чего-то вроде:
другие объекты, кроме использования коллекций
абзацы, но не владейте ими.
Теперь у вас есть проблема. Ваш объект, ParagraphSelectionDialog
, имеет список указателей на Paragraph
объектов, принадлежащих Document
. Если вы не будете осторожны в координации этих двух объектов, Document
- или другой клиент через Document
- может сделать недействительными некоторые или все указатели, содержащиеся в экземпляре ParagraphSelectionDialog
! Нет простого способа поймать это - указатель на действительный Paragraph
выглядит так же, как указатель на освобожденный Paragraph
, и может даже в конечном итоге указать на действительный - но другой - Paragraph
экземпляр! Поскольку клиентам разрешено и даже ожидается, что они могут сохранять и разыменовывать эти указатели, Document
теряет контроль над ними, как только они возвращаются из открытого метода, даже если он сохраняет владение объектами Paragraph
.
Это ... плохо. В результате вы получаете неполную, поверхностную инкапсуляцию, утечку абстракции, и в некотором смысле это хуже, чем отсутствие абстракции вообще. Поскольку вы скрываете реализацию, ваши клиенты не имеют представления о времени жизни объектов, на которые указывает ваш интерфейс. Вам, вероятно, повезет в большинстве случаев, так как большинство операций std::list
не делают недействительными ссылки на элементы, которые они не изменяют. И все будет хорошо ... до тех пор, пока неправильный Paragraph
не будет удален, и вы застрянете с задачей проследить через стек вызовов, ища клиента, который удерживал этот указатель немного слишком долго.
Исправление достаточно простое: возвращает значения или объекты, которые могут храниться столько, сколько нужно, и проверяется перед использованием. Это может быть что-то такое же простое, как порядковый номер или значение идентификатора, которое должно быть передано Document
в обмен на полезную ссылку, или такое сложное, как умный указатель со счетчиком ссылок или слабый указатель ... это действительно зависит от конкретного потребности ваших клиентов. Сначала укажите клиентский код, а затем введите Document
для обслуживания.