Comments 104
Единственное что, возможно выскажу непопулярное мнение, но:
- Интерфейсы — это такой костыль для языков, разработчики которых не хотели реализовывать множественное наследование, а точнее бороться с «ромбами» реализаций. Классическая иллюстрация — Java, где после появления default реализаций, интерфейсы это по сути классы без non-static field'ов. То есть абстракция получилась очень дырявая.
- Паттерны проектирования, в общем случае — зло. Либо вынужденное зло — когда используются как костыль, чтобы скрыть недостатки используемых языков / платформ (например паттерн Model-View), либо, что еще хуже — лень, когда разработчик не пытается использовать медленное мышление — структурировать задачу и придумать правильное решение, а включает быстрое мышление и начинает мыслить шаблонами, городя «фабрики фабрик» со всеми вытекающими.
Ну и к инкапсуляции, как к коктейлю, который одновременно отвечает за обеспечение модульности / пространств имен, реализацию полиморфизма (причем одиночного), синтаксический сахар в виде автоподстановки первого параметра (this), ну и функционал структур (то есть реализацию работы с первичными данными — field'ами) тоже есть много вопросов. В частности, например, она усугубляет объектно-реляционный семантический разрыв. Но это отдельная большая тема.
Зло — это когда машинный код после компиляции все еще имеет в себе куски типизации и тратит время и стек на проверку типов в рантайме.
Насколько я знаю, мало какой машинный код проверяет типы просто ради проверки (чтобы выкинуть ошибку). Обычно машинный код использует информацию построенную на основе типов, например, таблицы виртуализации, но альтернатива им в виде условных переходов не сильно оптимальнее.
Ну и не понятно какое отношение это имеет к «дырявости» интерфейсов (в смысле их дублированию логики классов).
Паттерны проектирования — это процесс стандартизации индустрии. Как минимум чтобы люди друг друга понимали. Как максимум — чтобы была возможность переиспользовать код. Зло — это когда почти каждая программа имеет свой текстовый редактор и спеллчекер, когда нам нужна 20Гб операционка чтобы отредактировать *.txt файл.
Для того, чтобы люди друг друга понимали, нужно проектировать и писать понятный код. Какое отношение паттерны проектирования имеют к повторному использованию кода (в частности к переписыванию текстовых редакторов) тоже неясно. То есть существует пару паттернов именно для этой задачи, но это именно что костыли, когда либо используемый модуль, либо использующий модуль спроектированы не очень качественно и их нужно «склеить».
Если честно я тоже не совсем понимаю что вы так прямо чуть ли не ненавидите паттерны. Возьмём те же IOC или Dependency Injection: вы считаете что они не нужны, что они «зло», «костыль», «неправильная проектировка архитектуры» или «нежеленаие разработчиков найти правильное решение»?
А причем тут IOC и DI. Они не создают дырявые абстракции (а точнее классы/объекты), и достаточно спрятаны от разработчика (в коде всего одна аннотация).
Я про всякие фабрики, хранители, посредники и другие суррогаты по ссылке в статье.
А причем тут IOC и DI.
При том что они на мой взгляд вполне себе являются «паттернами проектирования», а вы выше написали:
Паттерны проектирования, в общем случае — зло
И даже если не относить IoC и DI к паттернам, с чем многие вполне себе могут поспорить, то IoC и DI тоже можно использовать не к месту и/или криво и тем самым насоздавать себе кучу проблем. Они в этом плане мало чем отличаются от фабрик, декораторов, фасад и прочего.
Практически любой паттерн имеет свою область применения и вредит если используется не к месту. Но это не значит что паттерны зло «в общем случае»…
При том что они на мой взгляд вполне себе являются «паттернами проектирования»
Я специально зашел по ссылке автора и там ни IoC ни DI нет. А я под паттернами проектирования подразумеваю достаточно конкретную вещь (помню даже книжку по ним лет 15 назад читал) и именно то, что описано по ссылке автора.
И, еще раз, эти концепции (IoC и DI) не создают дополнительные публичные классы и объекты. Это принципиальный момент, поэтому они практически не вносят дополнительной сложности.
Я специально зашел по ссылке автора и там ни IoC ни DI нет.
Если я не ошибаюсь, то внизу там была ссылка на DI, да и так нелюбимая вами «фабрика» это вроде бы один из вариантов IoC.
Кроме того как минимум в английской версии статьи они присутствуют оба: en.wikipedia.org/wiki/Software_design_pattern
А я под паттернами проектирования подразумеваю достаточно конкретную вещь (помню даже книжку по ним лет 15 назад читал) и именно то, что описано по ссылке автора.
Вы извините но языки программирования развиваются и появляются новые языки и фреймворки. И это приводит к появлению новых паттернов. И заявлять, что что-то не является паттерном программирования потому что вы не прочитали об этом в какой-то книжке 15 лет назад, это на мой взгляд немного странно.
И, еще раз, эти концепции (IoC и DI) не создают дополнительные публичные классы и объекты.
Во первых они создают оверхед, как минимум в виде конфигурации и мэппинга. Во вторых они часто создают проблемы с зависимостями от библиотек или их отсуствием. И в третьих я конечно не знаю что вы сейчас конкретно подразумеваете под «дополнительные публичные классы и объекты», но практически все DI варианты на C#, которые я видел, имеют какой-нибудь DependencyLocator и/или коллекцию мэппингов.
Вы извините но языки программирования развиваются и появляются новые языки и фреймворки. И это приводит к появлению новых паттернов. И заявлять, что что-то не является паттерном программирования потому что вы не прочитали об этом в какой-то книжке 15 лет назад, это на мой взгляд немного странно.
Просто если абстрагировать паттерны проектирования до всех «техник применяемых в программировании», то они автоматически потеряют всякий смысл, так как будут значить все что угодно. Так и само ООП можно паттерном проектирования считать.
Если я не ошибаюсь, то внизу там была ссылка на DI, да и так нелюбимая вами «фабрика» это вроде бы один из вариантов IoC.
Давайте так, чтобы уточнить, когда я говорил про IoC и DI я имел ввиду, что они встроены в язык / платформу (вроде как в Java Spring / .Net). И все создаваемые при этом классы разработчику не видны.
А когда IoC и DI делают явно в коде (создавая proxy-классы и заменяя на них field'ы и конструкторы) это ад. Такой код читать / рефакторить очень тяжело.
Просто если абстрагировать паттерны проектирования до всех «техник применяемых в программировании», то они автоматически потеряют всякий смысл, так как будут значить все что угодно.
Угу. Вот только почему-то большинство известных мне программистов считаю DI и IoC паттернами. Да и гугл с вики с этим вроде бы согласны :)
Давайте так, чтобы уточнить, когда я говорил про IoC и DI я имел ввиду, что они встроены в язык / платформу (вроде как в Java Spring / .Net). И все создаваемые при этом классы разработчику не видны.
Более менее «встроены» они начиная с .Net Core и даже там они невидимыми не являются, так как минимум конфигурировать всё это дело кому-то надо.
А когда IoC и DI делают явно в коде (создавая proxy-классы и заменяя на них field'ы и конструкторы) это ад. Такой код читать / рефакторить очень тяжело
Угу. И именно так всё это делалось пока не появились удобные фреймворки. И даже такой вариант на мой взгляд раньше часто являлся лучшим решением чем не иметь никакого IoC :)
Угу. И именно так всё это делалось пока не появились удобные фреймворки. И даже такой вариант на мой взгляд раньше часто являлся лучшим решением чем не иметь никакого IoC :)
До фреймворков они были вынужденным злом:
когда используются как костыль, чтобы скрыть недостатки используемых языков / платформ
После фреймворков, они стали частью языка / платформы и перестали быть паттернами, в моем понимании этого слова.
Но, если вы мне предложите термин именно для явных паттернов (то есть классы которые создает разработчик руками и они видны в коде, попадают во все поиски использований, графы вызовов и т.п.), буду использовать его.
И честно говоря пока не в одном определении паттерна, которое я встречал в своей жизни, не видел чтобы паттерн описывали как «классы которые создает разработчик руками и они видны в коде, попадают во все поиски использований, графы вызовов и т.п.» :)
Я про всякие фабрики, хранители, посредники и другие суррогаты по ссылке в статье.
И почему вы считаете, что они суррогаты?
Для того, чтобы люди друг друга понимали, нужно проектировать и писать понятный код.
И понятным он становится в том числе и из-за паттернов. А когда человек изобретает те же паттерны, только называет их по своему, код становится непонятным.
Интерфейсы — это такой костыль для языков, разработчики которых не хотели реализовывать множественное наследование, а точнее бороться с «ромбами» реализаций. Классическая иллюстрация — Java, где после появления default реализаций, интерфейсы это по сути классы без non-static field'ов. То есть абстракция получилась очень дырявая.
С другой стороны, проблема "ромба" именно при наличии полей в базовом классе и проявляется наиболее сильно!
Что конкретно?
Думаю, что там сильно больше одной мысли. Но если по верхам:
Интерфейсы — это такой костыль для языков, разработчики которых не хотели реализовывать множественное наследование
Мне не нравится слово "костыль", но по сути это недалеко от того, что я думаю: для потребителя (то есть того, кто откуда-то получает Something some
, а потом делает ему some.Do()
) различие между интерфейсом и классом сугубо техническое, и без него прекрасно можно обойтись. При этом разделение на реализацию и абстракцию (или, точнее, внешний контракт) — очень важно; не важно, чем этот контракт выражается, важно, чтобы были способы его знать и, по возможности, навязывать.
Паттерны проектирования, в общем случае — зло.
… а по этому поводу уже все сказали: в общем случае, паттерны проектирования — это совсем не зло, потому что это типовые решения типовых задач. Общий словарь.
Паттерны проектирования, в общем случае — зло.Паттерны проектирования — это просто типовые решения типовых задач, только и всего (а чтобы не путаться, вместо слова «паттерн» употребляйте более привычное русскому уху слово «шаблон»). Зло появляется, когда в них пытаются увидеть что-то большее, от этого происходят всякие бессмысленные холивары, как у вас с Kanut.
Почему бы для рисования сразу не использовать UML?
Почему бы для рисования сразу не использовать UML?
В статье написано, почему. Или я не понял вопрос.
Я не стал использовать UML-диаграммы, посчитав их недостаточно наглядными, хоть и более гибкими.
Слабое объяснение. Крайне субъективное и слабое. Но мало того: UML — есть стандарт, а ваша методика рисования — есть только ваша методика рисования. Вот возьмёт новичок и по вашим диаграммам, пойдёт в профессию и столкнётся с профессионалом, который не долго думаючи спросит: "а почему ты, новичок, рисуешь диаграммы как-то странно? Ты что — дурак? Ведь есть же UML — зачем ты переизобрёл велосипед в 115 раз?"
Как будет чувствовать себя новичок?
Новичок будет вполне разумно раздражен, потому что каждый рисует диаграммы по-своему. И все утверждения "UML — есть стандарт" разбиваются о "не принятый в этой компании".
Так что профессионал, который заранее не сказав, что ему нужен UML, обзывает других людей дураками, несколько не прав.
К любому "рисованию диаграмм по своему" шли через БАЗУ рисования диаграм вообще. А база в IT для этого — UML диаграммы.
Мне когда то, ещё в те времена молодому профессионалу, вложили в голову, что без знания базы — разработчики не будут понимать вообще что нужно делать и я с этим мнением — полностью согласен. Потому что именно база приводит идеи к общему знаменателю, понятному всем заинтересованным персонам.
Да и бритву Оккама я уважаю — и не вижу смысла для переизобретания велосипеда. Особенно если учесть, что UML не имеет строгой нотации и любой вид агрегации можно заменить обычной прямой линией.
А база в IT для этого — UML диаграммы.
… а на чем основано это утверждение?
Мне когда то, ещё в те времена молодому профессионалу, вложили в голову, что без знания базы — разработчики не будут понимать вообще что нужно делать и я с этим мнением — полностью согласен. Потому что именно база приводит идеи к общему знаменателю, понятному всем заинтересованным персонам.
Тут есть системная ошибка: разработчики не будут понимать, что делать, без отсутствия общего языка. Но вы почему-то считаете, что UML таким языком является, хотя если я сейчас встану и спрошу в опен-спейсе, кто может без шпаргалки этот UML прочитать, ответов будет до смешного мало.
Особенно если учесть, что UML не имеет строгой нотации
Вот именно поэтому я и не вижу смысла за него ратовать, если споров за то, как в нем что-то правильно изобразить, будет больше, чем споров об изображенном (real life story).
… а на чем основано это утверждение?
Первая буква U из UML — отвечает на этот вопрос.
без отсутствия общего языка. Но вы почему-то считаете, что UML таким языком является
А чем вам UML не общий? И без шпаргалки его сможет прочесть любой, сноски нужны — для понимания нюансов. Но вот в чём штука — чтобы понять ЛЮБУЮ предлагаемую ВАМИ (или автором) диаграмму — сноски обязательны. Но в вашем случае — они нужны априори, а в UML без них, за счёт хоть какой-то известности — можно обойтись. Так что он уже более общий, нежели ваш (или автора) продукт.
как в нем что-то правильно изобразить
Кто определит понятие "правильность изображения" — тот собственно и решит все споры.
Первая буква U из UML — отвечает на этот вопрос.
Первая буква названия говорит только о том, как кто-то решил назвать свой язык. Никаких выводов о том, что это база в IT, из этого сделать нельзя.
А чем вам UML не общий?
Тем, что не все его знают.
И без шпаргалки его сможет прочесть любой
Неа. И, что важнее, все прочитают по-разному.
Но в вашем случае — они нужны априори, а в UML без них, за счёт хоть какой-то известности — можно обойтись.
Вот это как раз и иллюзия. Может быть можно. На практике я такого не видел.
как кто-то решил назвать свой язык.
Не кто-то, а люди, которые в далёком 1995 году уже столкнулись с проблемой рисованяи классов и постарались решить эту проблему, пройдя несколько процессов улучшения и попутно внедрив систему в общеобразовательный стандарт ВУЗов не только в США, но и по всему миру.
Неа. И, что важнее, все прочитают по-разному.
ну не знаю не знаю, но класс — я различу. Интерфейс тоже… С ассоциациями — морока конечно, но тоже можно разобраться.
А с другой стороны — я не знаю как в России, но я кроме двух ВУЗов в Латвии знаю как минимум один в Германии — в которых в IT на программиста преподают UML. И представляют как стандарт. Я знаю, что используется в некоторых ВУЗах США.
Это как минимум в несколько раз больше, нежели — изобрести велосипед заново в данной статье.
На практике я такого не видел.
Субъективное мнение. Я видел, как люди — не пугались UMLа :).
Не кто-то, а люди, которые в далёком 1995 году уже столкнулись с проблемой рисованяи классов и постарались решить эту проблему
Никто не спорит с тем, что постарались. Но решили ли?
ну не знаю не знаю, но класс — я различу. Интерфейс тоже…
Вы — различите. Но вы — это не все.
А с другой стороны — я не знаю как в России, но я кроме двух ВУЗов в Латвии знаю как минимум один в Германии — в которых в IT на программиста преподают UML. И представляют как стандарт.
А те, кто в этих вузах не учился, в разработке ПО не работают? Мне вот никто и никогда не преподавал UML, мне это работать не мешает никак в смысле "совсем никак".
Это как минимум в несколько раз больше, нежели — изобрести велосипед заново в данной статье.
Мне кажется, вы не понимаете целей "велосипеда" в данной статье. У него ровно одна задача: пояснять рассуждения в данной статье. Смысла сравнивать его с преподаванием UML ровно ноль.
Субъективное мнение.
Ну да, субъективное. Но оно прекрасно показывает общую идею: бесполезно ожидать, что если вы нарисуете диаграмму в UML, ее поймут все и одинаково.
У нас в ВУЗе UML преподавали и именно как общий стандарт.
Проблема только в том, что в каждой фирме где я работал был свой "диалект" и каждый раз приходилось привыкать/переучиваться заново.
изобрести велосипед заново в данной статье
Велосипед — это изобретение чего-то уже изобретенного. Я тоже сначала хотел UML, но уперся в то, что не нашел, как изобразить с помощью него некоторые моменты, описанные в данном комментарии
Понимаете, это как цифры и числа. Нельзя преподавать человеку числа, если он не знает цифр. UML — это числа, а мои схемы — это цифры. UML помогает описывать архитектуру с помощью известных всем механизмов. А мои картинки описывают, как работают эти самые механизмы. Это разные уровни.
Крайне субъективное и слабое
Никто и не говорил, что это объективное обоснование. Там так и написано — это мое субъективное мнение.
А вот, что объяснение слабое, давайте обсудим. Можете нарисовать с помощью UML так чтобы было понятно, что класс отличается от объекта, что код метода содержится в классе, а состояние в объекте, что в интерфейсе только сигнатура метода, а в классе — имплементация, что метод fire после перегрузки будет вызываться из имплементации класса десептикона, а не трансформера? Если сможете, то вы правы, это объяснение слабое.
Я убежден, что на ранних этапах знакомства, нужно отсечь лишние элементы из диаграмм, которые создают информационный шум, и выделить важное (что для новичка не очевидно) визуальными приёмами: цвет, заполнение, форма. А уже после того, как понимание пришло, можно перейти на профессиональный инструмент.
Если бы у меня была цель вместо букваря детям дать чертежи, я бы назвал статью «ООП в UML-диаграммах», а не в картинках. И тогда да, дети после первого класса могут сразу идти на собеседование, владея чертежами.
Хочется примеров «best practice», признаков того, что все сделано правильно.
И какого-то «best practice» на все случаи жизни лично я не знаю. Я даже не могу назвать «best practice» для выбора «best practice для какой-то конкретной проблемы» :)
и правильно применять паттерны, особенно в рефакторинге легаси-кода.
Делаете рефакторинг, делаете так как лучше, не оглядываясь на паттерны. Увидели сходство своих изменений с каким-то паттерном, добавили соответствующий суффикс к названию класса. Оценили отличия классической и вашей версии, изменили код либо добавили пояснения в документацию, почему отличия присутствуют. При этом паттерны это довольно абстрактная вещь, если отличий слишком много, возможно это другой паттерн, или вы делаете что-то неправильно.
Заголовок: ООП в картинках.
А по факту, наиболее подходящая под заголовок картинка лишь предпоследняя (со слоями абстракций), а всё остальное — простые UML-диаграммы.
ООП это, в контексте вашего вопроса, просто «инструмент», который по идее просто облегчает написание и поддержку кода.
И такое часто просто вопрос привычки и/или необходимости. И если лично вам проще без этого и/или вы без этого прекрасно обходитесь, то возможно ООП вам и не нужно.
Я привык к стилю программированию на чистом Си (в основном всякой математики), без ++, и соответственно возникает вопрос — а зачем мне использовать ООП, если можно и без него.
Действительно, зачем?
Цитата из статьи:
Любые описанные механизмы, принципы и паттерны, как и ООП в целом не стоит применять там, где это бессмысленно или может навредить.
Зачем натягивать сову на глобус, если у вас и без ООП все прекрасно получается. Возможно ООП вам только навредит.
Возьмем какой нить упрощенный пример. Вы делаете космические корабли, а ваши клиенты ими просто пользуются, не вникая в тонкости.
class Starship
{
private $position;
public function fly(Vector $coords) {}
public function moveTo($coords) {}
public function hyperjumpTo($coords) {}
public function shootAt($coords) {}
}
$myStarship=new Starship;
$myStarship->flyTo($someRandomPoint);
Вроде неплохо, но просто кораблики нам не интересны, заказчики хотят не просто космический корабль, а вполне осязаемое зло.
abstract class Starhip {}
class StarDestroyer extends Starship {}
Но некоторым нужно подчеркнуть свою индивидуальность
class SuperStarDestroyer extends Starship {}
Естественно нам все это уже хочется сгруппировать по сходным признакам, ибо хотелок много, а жизнь слишком коротка
//Название не совсем удачное, поскольку Imerial/Rebel на самом деле не указывает на владельца
abstract class ImperialStarship extends Starship {}
abstract class RebelStarship extends Starship {}
class StarDestroyer extends ImperialStarship {}
class SuperStarDestroyer extends ImperialStarship {}
class CorelianCorvette extends RebelStarShip {}
class MonCalamariCruiser extends RebelStarShip {}
Более того мы теперь можем построить верфи
//ну или Shipyard
class ImperialWarf () : ImperialStarship {}
class RebelWarf() : RebelStarship {}
class CorelianWarf() : Starship {}
Однако мир чуть сложнее, да и зло нам нужно не просто так, а чтобы творить, ну… собственно зло.
abstract class Starhip
{
public function shootAt(Starship $target)
}
И наверняка, просто зла недостаточно, нужно вселенское зло
interface Destroyable {}
abstract class Starship implements Destroyable {}
abstract class Planet implements Destroyable {}
//ее по канону конечно нужно сделать синглтоном, но кто знает....
class DeathStar extends Starship
{
public function destroy(Destroyable $target)
}
Ну и наведем чуточку красоты
class GalaxyFarFarAway
{
private static $planets;
public static function getPlanetByName(String $planetName) {}
public static function getRandomHabitablePlanet() {}
}
И как итог, сферический Люк Скайвокер в вакууме, может теперь:
$myNewShip = new MilleniumFalcon();
$destination=GalaxyFarFarAway::getPlanetByName('Alderaan')
$myNewShip->hyperjumpTo($destination->getCoords());
А губернатор Таркин, мерзко ухмыляясь:
$myPrecious = new DeathStar();
$victim=GalaxyFarFarAway::getRandomHabitablePlanet()
$myPrecious->destroy($victim);
А вы, в свою очередь продолжаете быть творцом, реализовывая лаконичные методы и сущности. Решаете нанять команду, делегируете штуки подчиненным/коллегам: ну скажем вам надо организовать лютую дизмораль в галактике при уничтожении обитаемой планеты. А сами при этом продолжаете строить корабли.
При этом: помнить или разбираться сразу во всем необходимости нет вообще никакой. По методам IDE подскажет прямо на ходу. По дереву абстракций ходить тоже легко и просто. Захотели сгенерить документацию? Тоже без проблем.
«губернатор Таркин, мерзко ухмыляясь»
))) Спасибо!!! Хоть прям бери и пишу игрушку по мотивам ЗВ ))).
Видимо мои программы слишком просты структурно. Хотя возможно я нахожусь в некой психологической ловушке сравнительно низкоуровневого программирования, впрочем высокоуровневое тоже может быть ловушкой — ваял что-то на С++ Builder — ну как ваял — складывал из кубиков, заметил, что мне уже лениво писать какие-то функции самому, лучше я поищу готовые компоненты, а до знакомства с этим чудесным продуктом такого не было.
какие задачи стоит решать именно через ООП?
Те, решение которых без ООП оказывается менее поддерживаемым. Если вы пишете математику и на C, скорее всего, вам это низачем не нужно.
Пример — цифровая фильтрация. Пока у нас только один фильтр, мы можем тупо написать функцию типа int filter(int input_value), а всякие там внутренние данные типа массива для фильтрации методом усреднения или хотя бы одного предыдущего значения для КИХ/БИХ спрятать в локальной области видимости модуля. Даже если нужно менять параметры фильтра на ходу — опять же добавляем функцию, присваивающую переданные ей значения внутренним переменным модуля, задающим коэффициенты, или даже флаги, меняющие тип фильтрации.
По сути, модуль обеспечивает одну из трех фишек ООП — инкапсуляцию. Но у такого модуля есть проблема — он один. Даже если мы размножим наборы хранимых внутренних данных (массив1, массив2, коэффА_1, коэффА_2, коэффБ_1, коэффБ_2), то как указать, с какими из них должна работать функция в каждом случае? Не говоря уже о том, что такое размножение просто неудобно и громоздко.
Окей, путь есть — упаковываем набор в структуру, используем несколько структур (можно даже динамически создавать экземпляры структур в рантайме по мере надобности) и получаем возможность при вызове функции указывать, с какой из структур работать: filter(value, &dataset1). Но это нарушает инкапсуляцию — чтобы передать в функцию указатель или ссылку на структуру, внешний код должен иметь эту структуру в своей видимости.
Хотя и это можно обойти — если наборы заданы статично и их немного, можно сразу присвоить им некие идентификаторы, и передавать функциям эти идентификаторы вместо ссылки/указателя. При динамическом же созданий наборов код еще усложняется — надо будет генерировать идентификаторы, где-то хранить соответствие идентификаторов и адресов структур в памяти и т.п. При том что мы по сути уже «изобрели объект».
И вот тут стоит только перейти от C к С++ и использовать волшебное слово class — и весь этот геморрой оказывается переложен на плечи компилятора, указатель на набор данных объект хранит скрыто внутри себя и снаружи опять виден простой «черный ящик».
Наследование и полиморфизм тоже можно подобными путями реализовать на чистом С. Это будет еще более громоздко, и что самое главное — это таки будет объектно-ориентированным программированием, только без использования ОО-возможностей компилятора.
Ну ок, наследование и полиморфизм мне лично в разработке прошивок для микроконтроллеров пока еще не пригождались (только для софта на ПК). А вот инкапсуляция — еще как. Кстати к дихотомии низкоуровневое/высокоуровневое — цифровая фильтрация или там программное подавление дребезга контактов это ведь как раз очень низкоуровневые задачи.
Вот, к примеру, одна моя софтина принимает от железки кучу разных типов данных (даты, числа с довольно разнообразным кодированием и т.п.) и отображает их в интерфейсе, выводит в отчеты и т.п.
Как это делается чисто процедурным путем — для каждого такого значения вызывается функция преобразования в строку — inttostr, datetostr, temperature1tostr, temperature2tostr.
Тут есть две засады. Во-первых, код тупо загромождается кучей вызовов этих функций
label1.Caption = inttostr(parameter1);
edit2.Text = inttostr(parameter2);
Во-вторых если у нас вдруг поменяется тип параметра или мы захотим изменить вид отображения параметра (скажем, с десятичного на шестнадцатеричный, или добавить/убрать ведущие нули) — придётся искать по всему коду, где у нас происходит такое отображение и везде менять функцию или ее дополнительные параметры вызова.
А можно сделать все эти типы данных наследниками абстрактного класса «параметр» с методом «Показать значение как строку», реализовав в каждом наследнике нужный вариант — и мы при необходимости можем в любом из наследников подкрутить эту функцию как надо в одном месте, не трогая всю остальную программу (вся остальная программа сможет вообще не париться про тип параметра и считать их все абстрактным «параметром» вызывая всегда «Показать», а уж как именно показать — конкретный экземпляр сам знает свой тип и умеет преобразовывать значение).
Не хватает раздела: «а зачем?». Ну серьезно, очень многие так увлекаются, что KISS, YAGNI итд для них не существует, есть только сферический ООП в вакууме. Причем с костылями естественно. Соответственно, растет сложность разработки и поддержки. Как раз то, с чем ООП призван бороться.
Заключение тоже неплохое. Но надо еще сказать, что даже при наличии ооп, многие переходят к классовому программированию. Ну вот холивара ради(ну все равно почти под каждой статьей про ооп такой есть): допустим ваши трансфомеры начали сражаться друг с другом, соответственно нам понадобиться расчитать опыт/здоровье/щиты/прочие штуки, а потом обработать собственно убийство. Тут сразу возникнет вопрос: а где и как? И на этом этапе многие скажут, что нужен класс CombatManager, которому нужно передать трансормеров, вместо optimus->attack(megatron). Вот только про его существование нужно обязательно знать и помнить, ибо IDE в данном случае просто бесполезна.
Не хватает раздела: «а зачем?». Ну серьезно, очень многие так увлекаются, что KISS, YAGNI итд для них не существует, есть только сферический ООП в вакууме.
Если вы внимательно прочитаете статью, заметите, что в ней весь упор делается на то, что нельзя ничем увлекаться и злоупотреблять, и что каждый механизм нужно применять там, где в этом появляется необходимость.
Ну вот холивара ради(ну все равно почти под каждой статьей про ооп такой есть): допустим ваши трансфомеры начали сражаться друг с другом, соответственно нам понадобиться расчитать опыт/здоровье/щиты/прочие штуки, а потом обработать собственно убийство. Тут сразу возникнет вопрос: а где и как? И на этом этапе многие скажут, что нужен класс CombatManager, которому нужно передать трансормеров, вместо optimus->attack(megatron).
Это не относится к ООП, это относится к проектированию. А само ООП — просто один из возможных инструментов реализации вашей архитектуры.
там, где в этом появляется необходимость.
Да это хорошо, но таки неплохо было бы понимать, при каких условиях она возникает. Это очень мало где поясняется.
Это не относится к ООП, это относится к проектированию. А само ООП — просто один из возможных инструментов реализации вашей архитектуры.
Ловко вы ушли от ответа. Даже возразить нечего)
Это очень мало где поясняется.
Потому что тут нет идеальной инструкции. В этом и заключается проблема, которую каждый решает, опираясь на свой и чужой опыт в похожих проектах.
Как правило есть два краеугольных камня: стоимость поддержки и разработки. ООП призвана решить их обе. Если в результате ваших манипуляций стоимость только возрастает, значит либо что-то пошло не так, либо ООП тут даром ненужен.
Почему нет?
Потому что, чтобы определить, какая парадигма подойдет для данного проекта лучше, нужно реализовать этот проект в разных парадигмах и сравнить стоимость разработки/поддержки в каждой из них. При этом требуются опытные команды разработчиков для каждой парадигмы. Иначе получится, что ООП-шники начнут пробовать реализовывать проект на ФП, у них получится плохо, и они решат, что ФП отстой, ООП — сила. (Я привел две популярные парадигмы, но их на самом деле много, это тоже учитывайте)
А поскольку никто такие дорогостоящие эксперименты не ставит, нам приходится сравнивать похожие проекты в разных парадигмах, не имея на руках данных о стоимости поддержки/разработки, оценивая разницу на глазок. Это и есть «свой и чужой опыт», на который мы можем опираться, об этом я и сказал выше.
— Сложная и/или иерархическая структура «реальных» объектов/предметов с которыми надо работать.
— Большой объем работы/проекта.
— Большое количество разработчиков, участвующих в проекте.
— Большая вероятность частых и/или больших изменений.
— Отсутствие критериев исключающих существующие ООП языки/рантаймы/фреймворки(например перформанс или доступные ресурсы в момент выполнения ).
Но опять же это всё всего лишь индиции и не больше.
Ну если навскидку, то есть несколько «критериев», которые по той или иной причине могут указывать на «полезность» ООП:
Хочется сразу уточнить. Под полезностью ООП вы имеете в виду пользу наличия/отсутствия ООП или пользу сравнительно с какой-то другой парадигмой? Если второе, то с какой именно?
Да и вообще если мы будем честны, то сейчас уже практически никто не выбирает парадигмы как таковые. Выбирают скорее языки программирования или даже сразу фреймворки. И при этом выборе часто главную роль играют факторы абсолютно не связанные с парадигмами.
При использовании первого или второго уровня для написания ключевого функционала длительность разработки средней игры будет стремиться к бесконечности. Ибо при изменение требований, а меняются они постоянно, придется постоянно переписывать классы базового трансформера и его наследников. А в игре со временем появятся не только трансформеры, но и другие объекты, которые могут обладать не хилой частью функционала трансформеров.
В играх 3-ий и 4-ый уровни абстракции используются. Хотя, может тут лучше выделить еще один уровень, или я сейчас 4-ый уровень опишу, не знаю.
В общем, вводятся понятия «компонент», «контейнер». Нет никаких классов трансформеров, пушек. Есть компоненты, отвечающие за характеристики, способности, различный функционал. После добавления компонентов в объект-контейнер, получается конкретный трансформер, пушка или радар.
К тому же, при таком подходе количество интерфейсов уменьшается раз в 100, т.к. их более эффективно заменяют компоненты. Там, где раньше у класса проверялось наличие интерфейса, будет проверяться наличие компонента.
По поводу 4-го уровня. Не понятно, тут разве не о крафте в играх говорится?)
Еще, похоже что относящееся к этому уровню встречается в играх при написании ИИ. Например, в деревьях поведений. Там составными частями являются действия, условия.
По поводу 4-го уровня. Не понятно, тут разве не о крафте в играх говорится?)
Похоже вы не видите разницу между уровнями, придуманными мной для данной статьи. Система крафта в играх — это обычно второй или третий уровень.
Второй: есть рецепты крафта, которые позволяют из приведенных в рецепте деталей крафтить продукт. Игрок не может добавлять новые рецепты или улучшать существующие.
Третий: поверх рецептов есть система улучшений или модулей, но рецепты все равно жестко прописаны в игре. Например, добавив редкие материалы, вы можете получить продукт с более высокими характеристиками. Либо можете улучшать продукт дополнительными модулями, добавляя тем самым новый функционал.
Четвертый: есть набор деталек, и никаких рецептов. Комбинируя эти детальки, игрок может получать продукты, ранее в игре не существующие. Функционал продукта при этом полностью формируется из свойств деталек и их комбинаций. Поэтому у таких продуктов не может быть названия, игрок должен его придумать сам. И вот такого уровня абстракции в играх я не встречал по указанным в статье причинам, хотя может он и есть.
Minecraft?
Это второй уровень по нашей вымышленной классификации. Там список рецептов, который игрок поменять не в силах. Получающиеся продукты никак не улучшаются. Но если для каких-то вещей есть зачарование, то тогда третий уровень.
Насколько я знаю, рецепта на процессор в minecraft нет.
Ну во-первых, это не относится к системе крафта и экипировки, об уровнях которых я говорил. В статье я упоминал, что в разных частях проекта может быть предусмотрен разный уровень абстракции.
А во-вторых, не соглашусь, что процессор — это пятый уровень. Скорее второй:
1 уровень — это блоки.
2 уровень — это прочность блоков и NBT-тэги.
3 уровень (которого нет без модов) — возможность в блоки вставлять модификаторы.
4 уровень (тоже отсутствует) — возможность создавать новые блоки с новыми названиями и свойствами.
Процессор работает полностью на блоках и их состояниях, именно поэтому он такой огромный и не масштабируемый, то бишь не гибкий. Вот если бы этот процессор можно было бы создать в виде нового блока и дать ему название «процессор», то да, это был бы четвертый уровень.
А если из этих артефактов создаётся новая «реальность», в исходной игре, или игра в игре, и исходная механика позволяет это — тут уже и 5-й уровень.
Вот если бы этот процессор можно было бы создать в виде нового блока и дать ему название «процессор», то да, это был бы четвертый уровень.— это если навесить два ограничения к 4-у уровню: создаваемое должно уметь замещать стандартные элементы, и это нужно уметь передавать другим игрокам как предмет.
Причём ему, как и авторам многих самостоятельных артефактов, пришлось крафтить одни блоки из других
Вы не путайте, эти уровни абстракции идут не поверх друг друга, а параллельно. Система крафта никакого отношения к окружающему миру и блокам не имеет. Как только игрок устанавливает блок, тот переходит из одной системы в другую уже со своими слоями, в которых нет информации, из чего этот блок скрафтили, есть только ID блока и его состояние. Об этом говорит полная независимость этих механик: если убрать из игры систему крафта, база данных блоков и их механика никуда не денется.
это если навесить два ограничения
Нет, смотрите. Дело то не в ограничениях. Мы же рассматриваем архитектуру не с какой-то абстрактной точки зрения игрока, а с вполне себе конкретной точки зрения описания программистом слоев с помощью композиции или наследования. Создавая процессор, игрок не добавляет в программный код игры новые абстракции, все работает поверх заложенных программистом.
Чтобы понять мою мысль, попробуйте описать архитектуру окружающего мира, в котором собрали процессор, с точки зрения разработчика программных сущностей и их ООП-отношений.
1 — базовые элементы, данные as-is
2 — крафт предметов/навыки персонажа
3 — аугментация характеристик персонажа/предметов.
Все три уровня укладываются не только в механики, но и любой вариант наверняка есть в некой «базе сущностей» на этапе разработки. Иначе говоря, чтобы не пытался создать игрок — это уже есть в БД игрового мира.
Естественно, всё что удастся создать — укладывается в механики игры, иначе этого в ней не создать. Но точно так же можно говорить, что всё, что написано на JavaScript, укладывается в его механики.
В играх-песочницах есть некий элемент мета-программирования, который позволяет создавать самостоятельные игровые сущности. Дальше вопрос лишь в том, как это реализовано и как много свободы у игрока.
Есть игры, где сущности рандомно генерируются из большого числа «винтиков», как в No Man’s Sky, или где игроку изначально даны большие возможности по созданию неведомого, как в Spore
Не напоминает процессор из майнкрафта? Автор этого сооружения настолько горд собой, что снял видео и написал статью на хабр. И ему действительно есть, чем гордиться, ведь это реальное достижение, без достаточного количества абстракций реализовать такую махину.
Возможность в игре соорудить какие-то сложные механизмы еще не говорит о гибкости архитектуры. Богатые возможности могут быть обусловлены банально большим количеством строго регламентированных возможностей с разделением на 2-3 уровня абстракции. Но богатые != гибкие.
Тот же Spore меня разочаровал, когда игра только вышла. Я ожидал увидеть песочницу, а увидел лишь имитацию с заранее регламентированными цепочками развития. Тот же майнкрафт в этом плане свободнее.
И пример с яваскриптом и процессором никак не связаны.
Иначе говоря, у любой гибкости есть свои пределы, после достижения которых конструкция полностью теряет форму.
Но таки если игрок может создать нечто новое, способное существовать и самостоятельно «работать» в механике игрового мира — то это всё же тот самый 4-й уровень абстракции, как он описан в этой статье.
Равно так же, в яваскрипте можно создавать новые типы и давать им новое поведение, но в конечном счёте гибкость архитектуры будет ограничена движком и платформой.
Вообще игр-песочниц сильно больше. Я просто не готов так сходу накидать ярких примеров. В той же spore эволюцию не осилили, но конструктор жизненных форм, емнип, был вполне гибок.
Новый уровень — это A+B+C == D при тех же условиях, что D не прописан в коде игры, а генерируется действиями игрока на лету.
В статье рассматривается четвертый уровень, где из деталек делаются штуки, которые вставляются в слоты. Процессор в майнкрафте никуда уже вставить нельзя, это просто груда деталек, которые взаимодействуют с соседними. Это богатые, но плоские возможности.
Если вы не видите разницу между этими двумя принципиально разными классификациями, это не значит, что ее нет.
А пример с яваскриптом вы, похоже, даже не попытались понять. Он идеально демонстрирует, как убирание слоев (которое вы как то не так понимаете) делает продукт ригидным и неудобным, но с сохранением богатых возможностей. Что мы и видим с процессором в майнкрафте: отсутствие необходимых слоев в игре не лишает игру возможности построить процессор, но превращает это мероприятие в подвиг. Как и любую программу на яваскрипте без функций.
С точки зрения игрока, не известно и не важно, вставляет он в слот композицию (A+B+C) или созданный новый компонент D.
Но если смотреть на то, что написано в статье — то главное отличие — это возможность любым способом создать такое нечто, которого нет в кодовой базе изначально.
В вашем примере про яваскрипт функции — это лишь удобная абстракция для написание кода, сиречь манипуляции данными и внешними/внутренними АПИ. Чем окажутся функции после компиляции — знает только конечный движок.
Есть игры, где доступны скриптовые модификации. Куда вы их отнесёте? С одной стороны, комбинация изначальных модулей и кода — явно что-то новое, т.е. D. Но во внутренней базе это может храниться как модуль+код, т.е. именно как (A+B+C).
Я вот к чему клоню — дайте простое, но исчерпывающее объяснение, что по-вашему будет 4-м уровнем.
Модули/субмодули/детальки не дают такого объяснения.
Достаточно рассмотрения двух уровней — на одном детали, на другом собираемые из них объекты.
Тут есть два варианта:
Первый: разработчики заранее прописали список объектов как возможных комбинаций деталек, и жестко сопоставили этим объектам некие характеристики (пример — предметы в Майнкрафте, список возможных создаваемых топоров-лопат-мечей фиксирован и характеристики зависят только от вида объекта. Другой пример — навешивание модулей на танки в WoT). Такой способ имеет ограничение — величина списка объектов сильно ограничена усидчивостью разработчиков или чуть менее сильно — техническими вопросами хранения такого списка.
Второй: разработчики заложили только список деталей и правила их комбинации, а свойства объекта либо вычисляются в зависимости от модификаторов, приписанных деталям. В этом случае количество возможных объектов подвержено комбинаторному взрыву. Примеры — Robocraft и система генерации оружия и прочей снаряги в Borderlands.
А дальше да, может быть иерархия уровней, детали тоже могут быть «сборочной единицей». При этом разные уровни могут быть устроены по разным принципам. Также в игре могут быть параллельные «сборочные цепочки», не связанные в иерархию.
Говоря про майнкрафт, тут есть две таких «сборочных цепочки», и в иерархию они связаны.
Есть крафт предметов и блоков по рецептам — он целиком первого типа. Список создаваемых объектов и их свойств жестко задан.
И есть конструирование всяких зданий и сооружений — имеется фиксированный набор блоков и некоторый набор правил, но количество их разнообразных комбинаций (по сути, весь мир — комбинация блоков) просто не поддается обозрению. Это второй тип. Правда, сооружения по большинству не обладают какими-то особыми «свойствами» как объекта — кроме как типа «замкнутая коробка из блоков обеспечивает полную изоляцию от солнечного света» или «с высокой башни можно упасть и разбиться». Ну разве что портал имеет некое свойство как объект — но это свойство как раз жестко прописано для группы объектов с определенной топологией — кольцо из блоков заданного типа, заполненное блоками другого заданного типа, то есть скорее относится к первому типу. Также чисто первым типом является сборка снеговиков и големов.
Поскольку при крафте могут создаваться не только предметы, но и блоки, являющиеся деталями для сооружений — вот и связь цепочек, образующая три уровня подчинения игровых объектов: сооружения < — блоки-стройматериал < — блоки-сырьё. Ну или два уровня сборки.
Ну и еще, при первом варианте список предметов и их характеристик может быть не кропотливо прописан разрабами вручную, а автоматически сгенерирован — рандомизатором или вообще полным перебором. Но суть остается та же — для игроков связь объектов, их характеристик и рецептов сборки остается фиксированной.
(переписываю эту телегу уже часа три, чувствую какие-то сомнительные моменты, но не могу сформулировать)
Давайте рассмотрим определение уровня абстракции из возможных вариантов на примере гипотетической игры «трансформеры-онлайн».
Это говорит о том, что это не какой-то эталон, а просто пример одного из вариантов иерархии отдельно взятой механики игры, а не всей игры целиком. Этих вариантов может быть множество, а уровней для каждой механики может быть разное количество. О чем нам говорит следующая цитата из статьи:
Еще важно понимать, что уровень абстракции определяется не для всего проекта в целом, а отдельно для разных компонентов.
То бишь, каждая игровая механика имеет свою иерархию слоев, и они не складываются, а идут параллельно.
Теперь еще раз объясняю, что значит идут параллельно.
Скрафтив какой-то блок, и поставив его в мире в виде блока, этот блок переходит из системы крафта (с одной иерархией слоев) в систему мира (с другой, своей иерархией уровней). Если бы иерархии этих двух механик складывались, то блок в игровом мире имел бы не только ID, но и ссылки на компоненты, из которых его скрафтили. А свойства блока определялись бы свойствами этих самых дочерних объектов.
В системе крафта тоже нет этих слоев, потому что предметы при крафте не сохраняют в себе ссылки на детали, из которых их крафтили. Получая новый предмет, он ложится в эту же самую плоскость в виде нового заранее прописанного предмета, не учитывая свойства деталей, из которых его скрафтили. Это просто новый, независимый предмет этого же уровня. Вот это важно, это ключевой момент.
Но в статье рассматривается вариант иерархии именно с сохранением ссылок и свойств дочерних объектов, и именно для этого и используются композиция и наследование, это тоже важный момент.
Надо было в статье эти ключевые моменты как-то выделить жирным, потому что они почему-то были проигнорированы, отсюда и все недопонимание, я полагаю.
Допустим, блок в майнкрафте имеет наследование, а значит хранит ссылку на родителя. Например, блок «песок» унаследован от блока «сыпучий», а значит это уже два уровня. При этому базовые характеристики хранятся в родительских атрибутах (положение и направление), а кастомные — в дочернем. А блок «рамка» помимо ссылки на родителя хранит ссылку на дочерний объект, который в него поместили (это агрегация). Это дает нам возможность менять предмет внутри рамки, меняя ее внешний вид. Вот это система уровней, а не просто ставить блоки рядышком, выдумывая новые слои.
Без сохранения связи между слоями, назвать это слоями можно только с точки зрения игрока, но не с точки зрения ООП, а статья именно про ООП.
Вы совершенно точно подметили, что важны именно отношения этих слоев, а не просто их положение относительно друг друга в одной плоскости.
Вот кстати еще пример игры, где довольно гибкая кастомизация, благодаря сочетанию глубины настоящих слоев, количества возможных объектов в каждом слое, и количеству их комбинаций: EVE Online.
1. Космический корабль.
2. В корабле помимо его собственных характеристик есть слоты под сменные модули (оружия, ускорители, щиты, реакторы и т. д.) — это агрегация.
3. В модулях есть хранилище расходников (боекомплект, батарейки, наниты и т. д.), которых тоже множество видов, и которые обладают своими характеристиками. Например, разные типы ракет могут быть хороши либо против больших целей, либо против маленьких и быстрых, либо вообще против сооружений. Это тоже агрегация.
Итоговые характеристики корабля динамически вычисляются из характеристик каждого слоя. Например мощность взрыва ракеты зависит от самого корабля, его скорости, от оружия, которое установлено в слот, и от типа снарядов, которым это оружие заряжено. Да, на борту может быть пять пушек, и в каждой разные снаряды, и эффект от них будет разный. То есть можно две пушки зарядить снарядами против мелких целей, и четыре пушки — против крупных, и стрелять одновременно в разные цели.
Вот это настоящие слои, а не просто взять корпус, кучу пушек, ракет, смешать это все в тазике и получить готовый корабль с деревянными неизменяемыми характеристиками. Либо вообще не смешивать, и получить просто кучу компонентов в одной плоскости.
Во-вторых, нагрузка то смешная. На корабле, ну максимум 20 модулей, из которых лишь половина одновременно активных. Посчитать за выстрел произведение из 3-5 множителей для каждого модуля это вообще фигня.
В-третьих, про смену конфигурации вы верно заметили, с одной стороны глупо просчитывать каждый выстрел. Но и держать просчитанные модели всех возможных комбинаций модулей, кораблей и снарядов тоже глупо, особенно учитывая, что даже текущая скорость корабля может влиять на получаемый урон и промахи. Возможно, какие-то коэффициенты кэшируются при смене снарядов, но в целом динамическая иерархия остается, ибо многие множители меняются в ходе боя постоянно.
В целом в eve online все довольно бодренько с быстродействием. В одной битве могут принимать участие тысячи кораблей
Хранить таблицы расчётов характеристик — дёшево. Даже если их в realm миллионы, на одну локацию приходят тысячи. При этом остаётся только рассчитывать параметры взаимодействия. Так что хранить надо не все возможные комбинации, а только тот набор, который есть в конкретном объекте.
Но даже так нагрузка не смешная. Сервер в единицу времени должен рассчитывать огромное количество параметров, ведь все эти корабли двигаются, игроки что-то делают, ещё и взаимодействуют… Недаром оно иногда лагает :)
И нагрузка все таки смешная, даже без кэширования, даже для тысячи кораблей. Мой домашний процессор такое может рассчитывать без проблем, и у него еще останется, чтоб крипту майнить. А лагает оно, я уверен, из-за IO (сеть, база, и т. д.), но уж точно не из-за CPU.
Ещё раз — игровые сервера обслуживают огромную массу событий, и для всякой онлайн-мясорубки главная проблема — это инпут-лаг. Проектировщики обычно стараются избавиться от любых действий, которые удлиняют цепочку обработки. Вы ещё учтите, что эти расчёты надо синхронизировать по времени между всеми участниками события.
Но… мы как-то сильно удалились от темы :)
Я же больше про модель, видимую пользователю, иначе говоря — бизнес-модель. Дальше вопрос будет в оптимизациях и подходах к разработке.
Но если не думать о конкретных оптимизациях и предположить, что реализация сделана именно так, как она видна снаружи, то ничто не мешает подумать, что один конкретный мир в minecraft — один объект, композиция из составных элементов, между которыми ещё и взаимодействие сложное. А ещё можно разделить большой мир на фрагменты по активным связям. Т.е. на кучки блоков, не имеющих активностей между кучками.
Да вообще много чего можно предположить :)
Функционал продукта при этом полностью формируется из свойств деталек и их комбинаций.
Таким образом генерируется оружие в серии игр Borderlands. Правда, игроку этот механизм внутри игры недоступен, только в виде чита — редактора сейвов.
При этом слова в названии тоже являются «детальками» и влияют на свойства. Есть вопрос совместимости деталек.
Вот здесь можно поперебирать варианты blmodding.wikidot.com/gear-calculator
Картинки топ, использование трансформеров топ, прям каеф! Спасибо за статью!
И некоторые отличия теории от практики.
Например, часто создаётся только 1 объект класса (не про Singletone) есть странное ощущение зачем городил целый класс, а создал только один инстанс.
Также полях класса тоже хранят данные (например ссылки на все сущности, чтоб их потом всех по удалять) и вызывают функции класса без создания объекта.
Дополнительный вопрос: что лучше — наследование или некое специальное поле, где хранится признак принадлежности объекта некоторому классу? Можно ли вместо того, чтобы создать два различных класса (Автоботы) и (Десептиконы), иметь некую метку, кто есть, кто? Тут ещё вопрос в чём? Если есть такая метка, то можно добавить в список возможных значений новое, то есть — добавить новый класс. Иначе придётся создавать код нового класса и перекомпиллировать приложение.
ООП в картинках