Является ли создание подклассов в Objective-C плохой практикой?

Прочитав множество блогов, форумов и несколько документов Apple, я до сих пор не знаю, разумно ли делать обширные подклассы в Objective-C.

Возьмем, например, следующий случай:

Скажем, я разрабатываю игру-головоломку, в которой много элементов. Все эти элементы имеют определенную долю одинакового поведения. Затем в моей коллекции элементов разные группы элементов имеют одинаковое поведение, отличая группы от групп и т. д.

Итак, после определения того, что от чего наследуется, я решил создать подкласс из забвения. А почему бы и нет? Учитывая простоту настройки общего поведения в этой модели, я думаю, что добился того, для чего предназначен ООП.

Но — и это источник моего вопроса — Apple упоминает использование делегатов, методов источника данных и неформальных протоколов в пользу создания подклассов. Это действительно уму непостижимо, почему?

Кажется, есть два лагеря. Кто за подклассы, кто за нет. Видимо зависит от личного вкуса. Мне интересно, каковы плюсы и минусы массового создания подклассов, а не массового создания подклассов?

Подводя итог, у меня простой вопрос: Прав ли я? А почему или почему бы и нет?


person Community    schedule 09.05.2009    source источник
comment
Крием, Apple говорит об использовании Cocoa в целом, а не о Objective-C. Для каждого отдельного проекта вам придется решить, как лучше настроить приложение и кодовую базу. В случае Apple они настроили Cocoa (особенно AppKit/UIKit) с использованием парадигм MVC и IoC, поэтому они предлагают вам не создавать подклассы таких вещей, как NSControl и т. д., когда вместо этого вы можете использовать делегатов. Подводя итог: это предупреждение относится конкретно к фреймворку Cocoa, а не к Objecive-C в целом.   -  person Jason Coco    schedule 10.05.2009
comment
@Jason: Это довольно хороший ответ, зачем оставлять его простым комментарием?   -  person mouviciel    schedule 10.05.2009
comment
@mouviciel - Наверное, я просто не подумал. Теперь есть ряд ответов, которые в основном говорят одно и то же, поэтому, похоже, не стоит добавлять больше шума :)   -  person Jason Coco    schedule 10.05.2009
comment
Objective-C без какао? Я уверен, что есть почти пять человек в мире, которые используют это.   -  person Chuck    schedule 10.05.2009
comment
@Kriem - Чак, вероятно, просто имеет в виду, что большинство людей используют Objective-C с Cocoa, но все еще не в этом суть. Не стесняйтесь разрабатывать свою игру/головоломку/что угодно с большим количеством подклассов, если это лучше всего подходит для вашего решения. Сама структура Cocoa была разработана таким образом, чтобы не было необходимости в подклассах, вот и все.   -  person Jason Coco    schedule 10.05.2009
comment
@Jason Coco - Хорошо, спасибо за понимание. Я все еще позволяю этой информационной перегрузке успокоиться. Да, делай это и нет, не делай этого повсюду, поэтому я должен попытаться понять, почему. :)   -  person Kriem    schedule 10.05.2009
comment
Этот вопрос существует уже много лет, но кажется, что самая очевидная проблема с глубокой иерархией не упоминается. Мир редко бывает так четко разделен, как следует из вопроса. Что вы делаете, когда у вас есть элемент, который работает немного как подкласс A и немного как подкласс B? Если у вас уже есть глубокая иерархия, когда вы обнаруживаете этот компонент, это настоящая PITA, с которой нужно иметь дело.   -  person Daniel T.    schedule 17.10.2012


Ответы (9)


Лично я следую этому правилу: я могу создать подкласс, если он соблюдает принцип подстановки Лисков.

person Martin Cote    schedule 09.05.2009
comment
Хорошая информация. Я думаю, что это применимо к моему случаю. Но — и это источник моего вопроса — Apple упоминает об использовании делегатов, методов источника данных и неформальных протоколов в пользу подклассов. Это действительно уму непостижимо, почему? - Я отредактирую вопрос, чтобы подчеркнуть это. - person Kriem; 10.05.2009
comment
Я подозреваю, что в большинстве случаев гораздо проще использовать делегатов, отсюда и рекомендация Apple. Подклассы могут быть уместны, но это будет сложнее сделать правильно. Просто мое мнение. - person Martin Cote; 10.05.2009
comment
Я думаю, что это определение ужасно общее. Если вы прочитаете математику, то в основном говорится, что все, что использует суперкласс, уже должно иметь возможность использовать подкласс без изменений. Таким образом, вместо того, чтобы помочь понять, когда иметь подкласс, он просто указывает, что вы должны не допускать, чтобы подклассы нарушали класс. Это определение также полностью исключает целые общие категории подклассов - например, абстрактные суперклассы, которые никогда не предназначены для прямого использования, а только подклассы... - person Kendall Helmstetter Gelner; 10.05.2009
comment
@ Кендалл - это не совсем так. Эти категории, безусловно, не исключаются при таком подходе. NSArray разработан таким образом - как абстрактный класс с неизвестными, замещенными подклассами в качестве реальных реализаций. - person Jason Coco; 10.05.2009

Делегирование — это способ использования техники композиции для замены некоторых аспектов кодирования, для которых в противном случае вы бы создали подклассы. Таким образом, это сводится к извечному вопросу о том, что для поставленной задачи требуется одна большая вещь, которая умеет делать много, или если у вас есть свободная сеть специализированных объектов (очень похожая на UNIX модель ответственности).

Использование комбинации делегатов и протоколов (чтобы определить, что должны делать делегаты) обеспечивает большую гибкость поведения и простоту кодирования — возвращаясь к этому принципу подстановки Лискова, когда вы создаете подкласс, вы должны быть осторожны. вы не делаете ничего неожиданного для пользователя всего класса. Но если вы просто создаете объект-делегат, то у вас гораздо меньше ответственности, только за то, что методы делегата, которые вы реализуете, делают то, что требует этот один протокол, кроме того, что вам все равно.

Есть еще много веских причин для использования подклассов, если у вас действительно есть общее поведение и переменные между несколькими классами, может иметь смысл создавать подклассы. Но если вы сможете воспользоваться концепцией делегата, вы часто сделаете свои классы более простыми для расширения или использования способами, которые вы, возможно, не ожидали от разработчика.

Я склонен быть большим поклонником формальных протоколов, чем неформальных, потому что формальные протоколы не только гарантируют, что у вас есть методы, которые ожидает класс, рассматривающий вас как делегата, но и потому, что определение протокола является естественным местом для документирования того, что вы делаете. ожидать от делегата, который реализует эти методы.

person Kendall Helmstetter Gelner    schedule 10.05.2009

У подклассов есть свои преимущества, но есть и некоторые недостатки. Как правило, я стараюсь избегать наследования реализации и вместо этого использую наследование и делегирование интерфейса.

Одна из причин, по которой я это делаю, заключается в том, что когда вы наследуете реализацию, у вас могут возникнуть проблемы, если вы переопределяете методы, но не придерживаетесь их (иногда недокументированного контракта). Кроме того, я нахожу сложные иерархии классов с наследованием реализации, потому что методы могут быть переопределены или реализованы на любом уровне. Наконец, при создании подклассов вы можете только расширить интерфейс, но не сузить его. Это приводит к дырявым абстракциям. Хорошим примером этого является java.util.Stack, который расширяет java.util.Vector. Я не должен обращаться со стеком как с вектором. Это только позволяет потребителю бегать по интерфейсу.

Другие упомянули принцип замещения Лисков. Я думаю, что использование этого, безусловно, устранило бы проблему java.util.Stack, но также может привести к очень глубокой иерархии классов, чтобы гарантировать, что классы получают только те методы, которые они должны иметь.

Вместо этого при наследовании интерфейсов практически отсутствует иерархия классов, поскольку интерфейсы редко должны расширять друг друга. Классы просто реализуют интерфейсы, которые им нужны, и поэтому могут быть правильно обработаны потребителем. Кроме того, из-за отсутствия наследования реализации потребители этих классов не будут делать выводы об их поведении из-за предыдущего опыта работы с родительским классом.

В конце концов, не так уж важно, каким путем вы пойдете. Оба вполне приемлемы. На самом деле это больше вопрос того, что вам удобнее и что поощряют фреймворки, с которыми вы работаете. Как говорится в старой поговорке: «В Риме поступай, как римляне».

person Bryan Kyle    schedule 10.05.2009

Нет ничего плохого в использовании наследования в Objective-C. Apple использует его совсем немного. Например, в Cocoa-touch дерево наследования UIButton — это UIControl : UIView : UIResponder : NSObject.

Я думаю, что Мартин уловил важный момент, упомянув принцип замещения Лискова. Кроме того, для правильного использования наследования требуется, чтобы разработчик подкласса хорошо знал суперкласс. Если вы когда-либо пытались расширить нетривиальный класс в сложной среде, вы знаете, что всегда есть кривая обучения. Кроме того, детали реализации суперкласса часто «просачиваются» в подкласс, что является большой проблемой в @$& для разработчиков фреймворка.

Apple во многих случаях использовала делегирование для решения этих проблем; нетривиальные классы, такие как UIApplication, предоставляют общие точки расширения через объект-делегат, поэтому у большинства разработчиков есть как более легкая кривая обучения, так и более слабо связанный способ добавления поведения, специфичного для приложения — прямое расширение UIApplication редко требуется.

В вашем случае для кода вашего приложения используйте те методы, которые вам удобны и которые лучше всего подходят для вашего дизайна. Наследование — отличный инструмент, если его правильно использовать.

Я часто вижу, как программисты приложений извлекают уроки из фреймворков и пытаются применить их к своему коду приложений (это распространено в мирах Java, C++ и Python, а также в Objective-C). Хотя полезно подумать и понять выбор, сделанный разработчиками фреймворка, эти уроки не всегда применимы к коду приложения.

person Don McCaughey    schedule 09.05.2009

В общем, вам следует избегать создания подклассов классов API, если существуют делегаты и т. д., которые выполняют то, что вы хотите сделать. В вашем собственном коде подклассы часто лучше, но это действительно зависит от ваших целей, например. если вы предоставляете API, вы должны предоставить API на основе делегата, а не предполагать подклассы.

При работе с подклассами API есть больше потенциальных ошибок, например. если какой-либо класс в иерархии классов получает новый метод с тем же именем, что и ваше добавление, вы делаете перерыв. А также, если вы предоставляете полезную/вспомогательную функцию типа, есть шанс, что в будущем что-то подобное будет добавлено к фактическому классу, который вы создали подклассом, и это может быть более эффективным и т. д., но ваше переопределение скроет это.

person olliej    schedule 09.05.2009

Прочтите документацию Apple "Добавление поведения в Какао-программа"!. В разделе Наследование от класса Cocoa см. второй абзац. Apple четко указывает, что создание подклассов — это основной способ добавления поведения конкретного приложения в структуру (обратите внимание, FRAMEWORK).
Шаблон MVC не полностью запрещает использование подклассов или подтипов. По крайней мере, я не видел эту рекомендацию ни от Apple, ни от других (если я пропустил, пожалуйста, не стесняйтесь указать мне правильный источник информации об этом). Если вы создаете подклассы классов API только внутри своего приложения, пожалуйста, продолжайте, никто вас не останавливает, но позаботьтесь о том, чтобы это не нарушало поведение класса/API в целом. Создание подклассов — отличный способ расширить функциональность фреймворка API. Мы также видим множество подклассов в рамках API-интерфейсов Apple IOS. Как разработчик, вы должны позаботиться о том, чтобы реализация была хорошо задокументирована и не была случайно продублирована другим разработчиком. Совсем другое дело, если ваше приложение представляет собой набор классов API, которые вы планируете распространять как повторно используемый компонент.

ИМХО, вместо того, чтобы спрашивать, что лучше всего, сначала внимательно прочитайте соответствующую документацию, внедрите и протестируйте ее. Сделайте свой собственный вывод. Ты лучше всех знаешь, что ты задумал. Другим (таким, как я и многие другие) легко просто читать материал из разных источников в Сети и разбрасываться терминами. Судите сами, до сих пор это работало на меня.

person Community    schedule 13.10.2012

Я действительно думаю, что это зависит от того, что вы пытаетесь сделать. Если игра-головоломка, которую вы описываете в примере, действительно имеет набор уникальных элементов, которые имеют общие атрибуты, и нет предоставленных классов — скажем, например, «NSPuzzlePiece», — которые соответствуют вашим потребностям, то я не вижу проблемы. с подклассами экстенсивно.

По моему опыту, делегаты, методы источника данных и неформальные протоколы гораздо полезнее, когда Apple предоставила класс, который уже делает что-то близкое к тому, что вы хотите.

Например, предположим, что вы создаете приложение, использующее таблицу. Существует (и я говорю здесь о iPhone SDK, так как там у меня есть опыт) класс UITableView, который делает все маленькие тонкости создания таблицы для взаимодействия с пользователем, и гораздо эффективнее определить источник данных для экземпляр UITableView, чем для полного подкласса UITableView и переопределения или расширения его методов для настройки его поведения.

Аналогичные концепции подходят для делегатов и протоколов. Если вы можете вписать свои идеи в классы Apple, то обычно проще (и будет работать более плавно) сделать это и использовать источник данных, делегаты и протоколы, чем создавать свои собственные подклассы. Это поможет вам избежать дополнительной работы и траты времени и, как правило, менее подвержено ошибкам. Классы Apple позаботились о том, чтобы сделать функции эффективными и отладить; чем больше вы сможете с ними работать, тем меньше ошибок будет в вашей программе в долгосрочной перспективе.

person Tim    schedule 09.05.2009

мое впечатление об акценте ADC «против» создания подклассов больше связано с наследием того, как развивалась операционная система ... в те дни (Mac Classic, также известный как os9), когда C ++ был основным интерфейсом для большинства инструментов Mac, создание подклассов был стандартом де-факто для того, чтобы программист мог изменить поведение обычных функций ОС (и это действительно иногда было головной болью и означало, что нужно было быть очень осторожным, чтобы любые модификации вели себя предсказуемо и не нарушить любое стандартное поведение).

при этом МОЕ ВПЕЧАТЛЕНИЕ об упоре ADC на создание подклассов состоит в том, что не выдвигает доводы в пользу разработки иерархии классов приложения без наследования, а НО ВМЕСТО указывает, что в новый способ ведения дел (например, OSX), в большинстве случаев есть более подходящие средства для настройки стандартного поведения без необходимости создания подкласса.

Итак, во что бы то ни стало, проектируйте архитектуру вашей программы-головоломки как можно более надежно, используя наследование так, как считаете нужным!

с нетерпением жду вашего нового крутого приложения-головоломки!

|K<

person kent    schedule 11.05.2009
comment
Это интересная идея, но на самом деле основные принципы разработки Cocoa были установлены задолго до того, как C++ стал обычным языком программирования для Mac OS. В Cocoa упор делается на делегирование и композицию от NEXTSTEP, который был разработан таким образом, потому что ответственные люди считали композицию более надежным способом создания больших программных систем из повторно используемых компонентов. И эта идея восходит к Smalltalk и шаблону проектирования Model-View-Conroller. - person Mark Bessey; 15.05.2009
comment
Есть также некоторый элемент болезненных уроков хрупких базовых классов, которые были извлечены в проекте Taligent. Это был совместный проект Apple и IBM, в рамках которого частично пытались создать общую структуру для разработки приложений. Их метафору «Люди, места и вещи» стоит изучить только для того, чтобы понять структуру фреймворка. Этот проект был, вероятно, самой большой демонстрацией того, как C++ терпит неудачу как язык для фундаментального проектирования фреймворка (я говорю это как профессиональный разработчик C++). - person Andy Dent; 25.07.2013
comment
Небольшая поправка: C++ никогда не был основным интерфейсом для большей части MacOS Toolbox. Это всегда были Си и Паскаль. - person Stefan Arentz; 16.01.2014

Apple действительно пассивно препятствует созданию подклассов с помощью Objective-C.

Это аксиома ООП-дизайна, предпочитающая композицию реализации.

person Community    schedule 19.01.2014