Как стать автором
Обновить
0
0
Беликов Сергей @Belikov

Пользователь

Отправить сообщение
«Срезание углов» в данном случае не выбор программиста, так поставлена задача моделирования: что-то моделируется как есть, а в каких-то случаях мы сразу получаем упрощенный ответ, не имея возможности все моделировать. Допустим, мы разложили все это в объекты. Потом постановка задачи меняется. Теперь срезание углов требуется по-другому и в других местах. Если у нас все написано в виде монолитных алгоритмов, в которых функции вызывают функции, тогда все очень легко доработать: нужно просто найти и поменять требуемые функции. Если поведение разложено по объектам, то очень вероятно что придется сносить всю архитектуру классов и изобретать ее по новой. Потому, что архитектура объектов вынужденно отражала не реальный мир, а выдуманный неустойчивый мир методики моделирования.

Я точно также могу сказать что модель легко доработать — достаточно найти и поменять отдельные классы. ) Это просто два способа декомпозиции — процедурный и ОО. И вполне может быть такое изменение которое точно также потребует изменить весь ваш алгоритм, а не только отдельные функции. Но на практике я не сталкивался с необходимостью радикально переделать архитектуру классов.

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

А почему вы кстати решили что при алгоритмическом подходе он только один? Ту же сортировку можно множеством разных способов реализовать.

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

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

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

Согласен что ООП не для всех задач хорошо подходит.
Мне кажется, проблема в том, что подобные задачи не настолько распространены, как это кажется сторонникам чистого ООП.

Думаю просто многие не умеют видеть задачу таким образом.

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

А для меня вот это кажется более редкой ситуацией. Это уже какие-то математические вычисления по-моему. Я больше разными бизнес-приложениями занимаюсь.

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

Так а 100% точную модель реального мира описывать и не нужно. Это именно модель с уровнем абстрации и инкапсуляции соответствующим решаемой задаче. При решении задач небесной механики у нас целая планета — материальная точка, а в других задачах мы опускаемся до уровня атомов. В каких-то задачах трение учитывается, в каких-то им можно пренебречь.
Если правила создают сложностей, то нужно не срезать углы, а изменять модель чтобы эти правила ложились в нее естественным образом. Это нормальный и естественный процесс, который проходит достаточно безболезненно при должном умении. Но многие почему-то этого боятся и живут в иллюзии, что они нарисуют красивые диаграммы, напишут код и все так и будет работать. В реальной жизни так не бывает.

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

То что ООП дает больше вариантов реализации — это как плюс так и минус. Минус в том, что это требует знаний, навыков и опыта. Именно поэтому, как мне кажется сейчас и стали более популярны простые языки вроде Go. Из-за того что программирование стало слишком массовым. А плюс — что правильная модель делает задачу более простой и понятной.
С этим согласен. Я тоже стал со временем в работе и функциональные подходы использовать. Но по-моему ООП это единственный способ описать предметную область так чтобы она была понятна людям без математического бэкграунда и хотя бы как-то с заказчиком соглсовывать требования.
Вы видимо много писали с использованием Redux или похожих подходов. ) Он хорошо подходит когда логика достаточно проста и количество возможных состояний небольшое. Тогда действительно можно охватить взглядом весь алгоритм целиком и просто его реализовать.
В ООП нет такого явного перехода между состояний и самих состояний в разы больше (по сути это комбинации всех состояний объектов в каждый момент времени). Но фишка в том, что все эти состояния явно перечислять и не нужно — они инкапсулированы в поведении отдельных объектов. И если поведение отдельных объектов и связи между ними реализованы правильно, мы предполагаем что и вся система будет работать как надо. Это как рабочие на конвейере, каждый из которых знает свою часть работы — что он получает от других рабочих, что дает на выходе и во сколько обед. ) Но в то же время есть общая схема, как должен быть устроен весь конвейер. То есть получается декомпозиция системы на подсистемы которая сильно облегчает понимание системы и уменьшает сложность. Можно видеть задачу на нескольких уровнях — когда надо — опуститься до уровня прикручивания отдельной гайки, а в другой ситуации увидеть как система работает на высоком уровне. Подход с одним большим классом такой картины не дает. Преимущества ООП особенно ярко проявляются когда алгоритм уже становится слишком сложным чтобы засунуть его в один большой класс и вообще держать в голове целиком.
И эти сущности и концепции не искуственные, а как раз наоборот — соответствуют объектам реального мира (или той предметной области, которая автоматизируется). Это тоже облегчает понимание. За счет правильной комбинации получается нужное поведение. Я обычно привожу в пример радиоэлектронику — когда есть достаточно ограниченный набор элементов (транзисторы, резисторы и т.д.), но соединив их по-разному можно получить разные устройства.
Есть неплохая книга на эту тему: Г. Буч «Объектно-ориентированный анализ и проектирование с примерами приложений». Там как раз теоретические основы ООП хорошо изложены.
Сейчас, кстати, тот же подход с новой стороны используется — это микросервисы. Идея по сути та же — разделение системы на отдельных агентов, каждый из которых обладает состоянием и поведением и взаимодействует с другими по четко описанным контрактам.
Да, согласен. Это вообще больше вопрос терминологии — называть ли реализацию интерфейса наследованием или нет. Для меня в наследовании первична реализация контракта, поэтому я реализацию интерфейса отношу к наследованию. Но возможно я не прав.
Про DDD да, все так. Я только недавно начал его применять, но получается красиво.
По-моему у вас слишком узкое трактование наследования. Реализация интерфейса — это очень мощный инструмент (хотя это уже полиморфизм, как мне правильно выше написали). Он позволяет описать контракт взаимодействия между объектами, не делая их явно зависимыми друг от друга. В данном примере, монстр может получать урон и стена может получать урон, они никак не связаны, но за счет интерфейсов мы можем работать с ними единообрзно. И такая система легко расширяется, т.е. если еще какой-то объект может получать урон, достаточно в нем реализовать интерфейс и уже готовая часть системы с ним автоматически состыкуется.
Попробую объяснить. Алгоритмы-менеджеры будут явно не по десятку строк, если реализовать все кучи кейсов которые мне в комментариях написали. Кстати обратите внимание, что примеров этого кода в треде всего пара и они уже представляют собой плохо читаемую лапшу. По ощущениям это вообще подход ближе к функциональному или процедурному стилю — тоже вариант, но лично мне он кажется менее наглядным и понятным и сложнее модифицируемым.
Чем вас наследование так пугает? Это же просто использование интерфейсов, которые четко описывают возможные взаимоотношения между объектами.
Свободное от анемичных элементов ООП как раз наоборот хорошо подходит для систем сложным поведением. DDD как раз для таких проектов задумывался. По сути это «OOP done right».
Метод Hit — правильнее назвать Attack — это еще не нанесение урона. Для AOE оружия вроде гранаты ITarget это будет игровое поле и туда можно передавать координаты взрыва. И он будет реализовывать IsHitBy как-то так, например (убийство монстров тогда надо будет по-другому немного сделать, но это не принципиально): В классе Playfield
void IsHitBy(IAoeWeapon weapon, int attackPower, Point point)
{
     weapon.Hit(attackPower, point)
}


В классе граната, реализующей IAoeWeapon
void Hit(int attackPower, Point point)
{
      var targets = Playfield.GetTargets(point);
      foreach (var target in targets)
      {
             target.IsHitBy(this, attackPower);
      }
}
Но в реальности граната не знает ничего ни про каких монстров. Зачем делать заведомо неправильную модель, и потом решать проблемы, которые из-за этого возникают?

Граната знает радиус своего поражения и влияет на объекты, которые в него попадают — монстры, здания и любые другие ITarget. По-моему это более естетственная модель чем некий CombatManager, который всем управляет. Разве что это бог войны какой-то. )

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

Не понял в чем проблема. Граната получает координаты взрыва от игрока, когда он ее кидает и окружающие объекты от Playfield когда взрывается.

Так я и не придумывал, мы все еще про гранату говорим) У вас надо добавлять общие интерфейсы у точки на карте и гранаты, делать кучу иерархий AreaEffectWeapon, BulletWeapon, ClubWeapon, по которым будет разбросана физика, пробрасывать Playfield во все из них. И все для того, чтобы не делать выделенный класс с нужной ответственностью.

Общий интерфейс с точкой на карте возможно не лучшее решение — в этом случае в принципе можно и добавить Environment для AOE эффектов, его унаследовать от ITarget и туда передавать координаты, например. Но общая идея именно в том, чтобы разбросать логику по тем классам к которым она относится. Правила взрыва — в гранате, получение урона — в монстре и т.д. вместо одного огромного класса который через некоторое время станет очень болезненно поддерживать. В зависимости от изменений модели — можно делать рефакторинг и добавлять классы, переносить логику из одного класса в другой и т.д. Но по крайней мере есть модель из которой понятно что как работает.

А теперь допустим появляется требование добавить спецэффекты типа «граната не взорвалась, но попала монстру по башке, и он потерял сознание». А у нас в ней повреждение по area рассчитывается, а повреждение при прямом контакте рассчитывается в иерархии ClubWeapon. Теперь надо гранату от ClubWeapon наследовать?

Зачем? Если она такое умеет — можно в ней же эту логику и описать.

Например, игрок отправляет сообщение в Environment «Бросаю гранату типа G в точку X». Environment определяет радиус через G.calculateRadius(), определяет все объекты MonsterBody в этом радиусе от точки X, опеределяет силу взрыва через G.calcualtePower(), для каждого объекта расчитывает P в зависимости от расстояния и отправляет сообщение «Осколок гранаты силой P», каждый MonsterBodу меняет свои характеристики в соответствии с законами физики. Environment может иметь в составе компоненты для разных стихий или типов оружия, которые и делают конкретные расчеты, у объектов Monster есть ссылка на MonsterBody с доступом на чтение характеристик и к методам управления, Monster и MonsterBody могут иметь разные иерархии наследования или вообще их не иметь, а делать все через композицию. Возвращаясь к примеру с невзорвавшейся гранатой, если G.calcualtePower() вернула 0, то Environment берет массу гранаты G.getMass(), вызывает код, которые рассчитывает урон для холодного оружия, проверяет, есть ли какой-то объект конкретно в точке X, если есть отправляет ему сообщение «Камень массой M». Инкапсуляция не нарушена, нет никаких ограничений на наследование, новые правила боя добавляются без изменений архитектуры.

Ок, можно сделать большой Composite как вы предлагаете или какую-то шину сообщений, но засовывать туда всю логику неправильно. Если у вас Environment активно вызывает G.calculateRadius(), G.calcualtePower(), а потом G.getMass(), то по-моему это явный признак того, что этой логике место в G (сорри за каламбур).
Нет, GetAttackPower и Hit — это методы Unit — базового класса для Human и Krogan.
Если нужны какие-то более сложные эффекты можно и так сделать. Если нет, то сама граната вполне может быть AOE.
В смысле противокрогановое оружие? Я же написал комментарий — это можно легко обработать в IsHitBy в классе корган.
Элементарно. В базовом классе:
protected virtual int GetAttackPower(ITarget target)
{
       // При желании тут можно учесть разные эффекты от оружия и т.д.
       return AttackPower;
}
public void Hit(ITarget target)
{
   var attackPower = GetAttackPower(target);
   // В IsHitBy можно учесть противокрогановое оружие 
   // и сопротивляемость разным видам урона
   if (target.IsHitBy(CurrentWeapon, attackPower) <= 0) 
   {
        // За разрушение ящиков можно XP не давать - это легко проверить если надо
        XP += target.XP;
   }
}


В классе человек:
protected override int GetAttackPower(ITarget target)
{
var attackPower = base.GetAttackPower(target);
 if (target is Krogan)
   {
        attackPower *= 0.5;
   }
return attackPower;
}


Если нужна большая гибкость, то эти правила легко вынести в какой-то отдельный файл ресурсов и брать их оттуда в методах GetAttackPower для игрока, монстра и оружия.
Ну да, так и есть. Вызывать Monster.IsHitBy в окрестностях и все. Можно даже разный урон наносить в зависимости от расстояния до цели.
А как цели реагируют на осколки гранату и не волнует. Она вызывает все тот же Monster.IsHitBy. Точку указывает не граната сама, а игрок, когда ее кидает. То есть есть четкое разделение ответственности, а не CombatManager, который знает все обо всех и спокойно меняет состояние любых объектов нарушая инкапсуляцию. И кстати, в вашей модели любое изменение будет затрагивать его. Не спорю, перепроектировать ничего не придется, т.к. тут нечего перепроектировать — есть куча процедурного кода в одном огромном классе плюс куча классов без поведения. Вот только будет ли такой код понятным? Мне кажется что не очень.
Playfield пробросить можно разными способами — это не проблема. IoC легко это решает. Пока что вам не удалось придумать изменения которые требуют все заново перепроектировать, так что модель видимо неплохая. ) И развитие модели это вполне нормально.
Ага, я этого и ожидал. Не делать третий класс, чтобы сделать третий класс. Клёво)

Цель не в том чтобы сделать меньше классов, а в том, чтобы каждый класс отвечал за свою часть работы и работу системы в целом можно было легко и наглядно увидеть (это называется ОО-декомпозиция). Есть игрок — он кидает гранаты. Есть поле, оно выдает координаты. Есть граната — она взрывается и наносит урон. Есть монстры, они получают урон. Это ООП.
Подход «у нас есть классы Player и Monster, но на самом деле каждый из них это просто набор полей, а вся логика их взаимодействия находится в классе CombatManager» по-моему не совсем ООП. Или может быть я чего-то не понимаю? Можете написать код, как вы бы в своем подходе описали пример с гранатой?
А «все есть функция» из ФП — лучше?
В ООП при правильном подходе (DDD например), хотя бы часть кода описывает модель в более-менее понятных терминах и ее можно даже бизнесу объяснить (в отличие от ФП). А паттернами все обрастает от неумения строить объектную модель, по-моему.
А наносить урон целям это по-вашему не ответственность гранаты? И это не CombatManager т.к. он за combat не отвечает. Скорее уже какой-то Playfield у которого есть метод — дай мне игровые объекты в радиусе X от указанной точки. Что я с ними делаю — это уже не его ответственность. Например, точно также можно какие-то массовые баффы союзников имплементировать, действующие в радиусе от игрока или указанной точки.
Про заклинания вы правильно написали — для этого есть наследование. Только не заклинание от гранаты, а их обоих от какого-нибудь AreaEffectWeapon, куда можно вынести общий код, а различия имплементировать в дочерних классах.
Да и вообще классы вроде Environment, Core, Engine Manager зачастую быстро превращаются в god-object, а сами классы становятся просто структурами данных без поведения и весь смысл ООП пропадает.
В данной задаче предполагалось что тут как в классических rpg есть target. Но если нет, то модель все равно легко расширить не вводя псевдоклассы вида «SomethingManager». Если это граната, то вместо монстра через ITarget подставляется точка на карте и там уже граната (currentWeapon) через какой-то движок отвечающий за координаты получает координаты задетых целей и наносит урон. И вообще, когда появляются классы с суффиксом Manager уже стоит задуматься что что-то идет не так: blog.codinghorror.com/i-shall-call-it-somethingmanager
Изначально же ООП так и задумывался, разве нет? Потом оказалось что мало кто умеет нормально моделировать реальный мир и все ищут готовые паттерны, чтобы под копирку кучу фабрик фабрик наделать где надо и где не надо. Тогда стали популярны паттерны. А еще немного позже появилось DDD которое пытается вернуть все к истокам и использовать ООП так как оно задумывалось — для описания модели предметной области.
1
23 ...

Информация

В рейтинге
Не участвует
Откуда
Москва, Москва и Московская обл., Россия
Дата рождения
Зарегистрирован
Активность