Размер шрифта:
c# - принципы - принцип разделения интерфейса
Play

c# - принципы - принцип разделения интерфейса

Принцип единственной ответственности(SRP) и структура класса моего rpg выглядит «странно», (5)

Я играю в ролевую игру просто для удовольствия и больше узнаю о принципах SOLID. Одна из первых вещей, на которых я сосредотачиваюсь, - SRP. У меня есть класс «Персонаж», который представляет персонажа в игре. У него есть такие вещи, как «Имя», «Здоровье», «Мана», «Возможности» и т. Д.

Теперь, как правило, я бы также поместил методы в свой класс Character, чтобы он выглядел примерно так .

Но из-за SRP я решил перевести все методы в отдельный класс. Я назвал этот класс «CharacterActions», и теперь подписи метода выглядят так .

Обратите внимание, что теперь я должен включить объект Character, который я использую во всех моих методах CharacterActions. Правильно ли это подходит для использования SRP? Это, похоже, полностью противоречит концепции инкапсуляции ООП.

Или я делаю что-то совершенно неправильное здесь?

ОДНА вещь, которая мне нравится в этом, заключается в том, что мой класс символов очень ясен в том, что он делает, он просто представляет объект Character.

Метод, который я использую при проектировании классов и в чем заключается OO, состоит в том, что объект моделирует объект реального мира.

Давайте взглянем на персонажа . Дизайн класса персонажей может быть весьма интересным. Это может быть договор ICharacter. Это означает, что все, что хочет быть персонажем, должно иметь возможность выполнять Walk (), Talk (), Attack (), иметь некоторые свойства, такие как «Здоровье», «Мана».

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

Я склонен не слишком сильно заставлять принципы дизайна, но и думаю о моделировании объекта реального мира.

Я думаю, это зависит от того, может ли поведение ваших персонажей измениться. Например, если вы хотите, чтобы доступные действия изменились (на основе чего-то еще, происходящего в RPG), вы могли бы пойти на что-то вроде:

Это позволяет вашему персонажу иметь столько действий, сколько вы хотите (что означает, что вы можете добавлять / удалять действия без изменения класса символов), и каждое действие отвечает только за себя (но знает о персонаже) и для действий, которые имеют более сложные требования, наследовать от IAction для добавления свойств и т. д. Вероятно, вам понадобится другое возвращаемое значение для Execute, и вам может понадобиться очередь действий, но вы получите дрейф.

Обратите внимание на использование ICharacter вместо Character, так как символы могут иметь разные свойства и поведение (Warlock, Wizard и т. Д.), Но у них, вероятно, все есть действия.

Отделяя действия, он также упрощает тестирование, так как теперь вы можете протестировать каждое действие без необходимости подключать полный символ, а с помощью ICharacter вы можете легко создать свой собственный (макет) символ.

Я не знаю, что я бы назвал этот тип класса SRP в первую очередь. «Все, что связано с foo», обычно является признаком того, что вы не следуете за SRP (это нормально, оно не подходит для всех типов конструкций).

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

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

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

Один из способов, которым вы часто можете сказать, нарушает ли объект SRP, - это посмотреть на переменные экземпляра, которые используют методы внутри объекта. Если существуют группы методов, которые используют определенные переменные экземпляра, но не другие, это обычно является признаком того, что ваш объект можно разделить по группам переменных экземпляра.

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

Например, ваши методы должны были бы измениться, если бы у вас был ElfCharacter и WizardCharacter ? Если ваши методы абсолютно никогда не меняются и полностью самодостаточны, то, возможно, статичность в порядке . но даже тогда это делает тестирование намного сложнее.

Обновление - я переделал свой ответ, потому что после полуночного сна я действительно не чувствовал, что мой предыдущий ответ был очень хорошим.

Чтобы увидеть пример действия SRP, рассмотрим очень простой символ:

Хорошо, так что это будет довольно скучная RPG. Но этот класс имеет смысл . Все здесь напрямую связано с Character . Каждый метод - это либо действие, выполняемое, либо выполняемое на Character . Эй, игры просты!

Давайте сосредоточимся на части Attack и постараемся сделать это наполовину интересным:

Теперь мы куда-то попадаем. Персонаж иногда пропускает, а урона / удара повышается с уровнем (при условии, что STR также увеличивается). Весело хорошо, но это все еще довольно скучно, потому что оно не учитывает ничего о цели. Давайте посмотрим, можем ли мы исправить это:

На данный момент вопрос должен уже формироваться в вашем сознании: почему Character несет ответственность за расчет своего собственного урона против цели? Почему у него даже есть эта способность? В чем-то странно странно, что делает этот класс, но на данный момент он все еще неоднозначен. Действительно ли стоит рефакторинг, чтобы переместить несколько строк кода из класса Character ? Возможно нет.

Но давайте посмотрим, что произойдет, когда мы начнем добавлять дополнительные функции - скажем, из типичной RPG 1990-х годов:

Это все прекрасно и денди, но разве это не смешно делать все это число-хруст в классе Character ? И это довольно короткий метод; в реальной RPG этот метод может широко распространиться на сотни строк с помощью сбрасывающих бросков и всех других способов бота. Представьте себе, вы привозите нового программиста, и они говорят: у меня есть запрос на оружие с двойным ударом, которое должно удвоиться, как обычно. где мне нужно внести изменения? И вы скажите ему, проверьте класс Character . Да ??

Хуже того, может быть, в игре добавляется какая-то новая морщина, например, о, я не знаю, бонус в спину или какой-то другой тип бонусов. Ну, как, черт возьми, вы должны понять это в классе Character ? Вероятно, вы в конечном итоге позвоните в один синглтон, например:

Тьфу. Это ужасно. Тестирование и отладка это будет кошмаром. Так что же нам делать? Выньте его из класса Character . Класс Character должен знать только , как делать то, что Character логически знает, как это сделать, и вычисление точного урона против цели действительно не является одним из них. Мы сделаем для него класс:

Намного лучше. Этот класс делает ровно одну вещь. Он делает то, что говорит на олове. Мы избавились от синглтонной зависимости, поэтому этот класс действительно можно протестировать сейчас, и он чувствует себя намного прав, не так ли? И теперь наш Character может сосредоточиться на действиях Character :

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

Теперь, надеюсь, вы понимаете, почему это:

. в основном бесполезно. Что с этим не так?

  • У него нет четкой цели; родовые «действия» не являются ни одной ответственностью;
  • Он не выполняет ничего, что Character уже не может сделать сам по себе;
  • Это полностью зависит от Character и ничего другого;
  • Вероятно, вам потребуется выставить части класса Character которые вам действительно нужны, чтобы они были защищены / защищены.

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

📎📎📎📎📎📎📎📎📎📎