Стратегическая модель и взрыв классов “действия”

Это - плохо политика иметь много классов "работы" (таких как классы Стратегии), это только делает одну вещь?

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

  1. Изолируйте класс, если я хочу. Позже, другие пользователи могут просто создать новый класс и все еще иметь полиморфизм посредством интерфейсов, которые я определил. Я не должен волновать, как люди (или я) могли бы хотеть измениться/добавить функции на базовый класс в будущем. Все классы наследовались Объекту, и они реализуют наследование через интерфейсы, не от родительских классов.
  2. Снова используйте стратегии, которые я использую с этим монстром для других членов моего игрового мира.

Довод "против": Эта модель тверда. Иногда мы хотели бы определить что-то, что легко не достигается, просто пытаясь соединить это "стандартные блоки".

public class AlienMonster : IWalk, IRun, ISwim, IGrowl {
    IWalkStrategy _walkStrategy;
    IRunStrategy _runStrategy;
    ISwimStrategy _swimStrategy;
    IGrowlStrategy _growlStrategy;

    public Monster() {
        _walkStrategy = new FourFootWalkStrategy();
       ...etc
    }

    public void Walk() { _walkStrategy.Walk(); }
    ...etc
}

Моя идея была бы рядом с, делают ряд различных Стратегий, которые могли использоваться различными монстрами. С другой стороны некоторые из них могли также использоваться в полностью различных целях (т.е. у меня мог быть корпус, который также "плавает"). Единственная проблема, которую я вижу с этим подходом, состоит в том, что он мог привести к взрыву чистых классов "метода", т.е. классов Стратегии, которые имеют как только, цель делает это или что другое действие. В другой руке этот вид "модульного принципа" допускал бы высокое повторное использование стратегий, иногда даже в полностью различных контекстах.

Каково Ваше мнение об этом вопросе? Действительно ли это - допустимое обоснование? Эта сверхразработка?

Кроме того, принятие, мы внесли бы надлежащие корректировки в пример, который я дал выше, будет он быть лучше определить IWalk как:

interface IWalk {
    void Walk();
}

или

interface IWalk {
    IWalkStrategy WalkStrategy { get; set; } //or something that ressembles this
}

быть тем выполнением этого, я не должен был бы определять методы на Монстре самого, у меня просто будут общедоступные методы считывания для IWalkStrategy (это, кажется, идет вразрез с идеей, что необходимо инкапсулировать все так, как Вы можете!), Почему?

Спасибо

6
задан devoured elysium 11 April 2010 в 21:12
поделиться

4 ответа

Прогулка, бег и плавание кажутся реализациями, а не интерфейсами. У вас может быть интерфейс ILocomotion и позволить вашему классу принимать список реализаций ILocomotion.

Growl может быть реализацией чего-то вроде интерфейса IAbility. А у конкретного монстра может быть набор реализаций IAbility.

Затем имейте пару интерфейсов, логику которых можно использовать для использования: IMove, IAct, например.

public class AlienMonster : IMove, IAct
{
  private List<IAbility> _abilities;
  private List<ILocomotion> _locomotions;

  public AlienMonster()
  {
    _abilities = new List<IAbility>() {new Growl()};
    _locomotion = new List<ILocomotion>() {new Walk(), new Run(), new Swim()}
  }

  public void Move()
  {
    // implementation for the IMove interface
  }

  public void Act()
  {
    // implementation for the IAct interface
  }

}

Составляя свой класс таким образом, вы избежите некоторой жесткости.

РЕДАКТИРОВАТЬ: добавлен материал об IMove и IAct

РЕДАКТИРОВАТЬ: после некоторых комментариев

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

void SomeFunction(IWalk alienMonster) {...}

Вышеупомянутая функция будет принимать все, что реализует IWalk, но если есть варианты SomeFunction для IRun, ISwim и т. Д., Вам нужно написать совершенно новую функцию для каждого из них или передать объект AlienMonster целиком. Если вы передадите объект, эта функция сможет вызывать любые без исключения интерфейсы на нем. Это также означает, что эта функция должна запросить объект AlienMonster, чтобы узнать, каковы его возможности, а затем решить, что использовать. Все это приводит к тому, что многие функции становятся внешними, и они должны оставаться внутренними по отношению к классу. Потому что вы воплощаете все это во внешнем виде, и между IWalk, IRun, ISwim нет ничего общего, поэтому некоторые функции могут невинно вызывать все три интерфейса, и ваш монстр может одновременно бегать-ходить-плавать. Кроме того, поскольку вы захотите иметь возможность вызывать IWalk, IRun, ISwim для некоторых классов, все классы в основном должны будут реализовать все три интерфейса, и вы в конечном итоге создадите стратегию, такую ​​как CannotSwim, для удовлетворения требований интерфейса для ISwim, когда монстр не умеет плавать.В противном случае вы можете попытаться вызвать интерфейс, который не реализован на монстре. В конце концов, вы фактически ухудшаете код для дополнительных интерфейсов, ИМО.

2
ответ дан 17 December 2019 в 00:06
поделиться

"Найдите то, что меняется, и инкапсулируйте это".

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

В целом: нет, инкапсуляция различных концепций в собственные классы определенно не является чрезмерной инженерией. Также нет ничего плохого в том, чтобы делать конкретные классы sealed или final. Это заставляет людей сознательно нарушать инкапсуляцию, прежде чем наследовать от чего-то, что, вероятно, не должно наследоваться.

1
ответ дан 17 December 2019 в 00:06
поделиться

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

В C #, являющемся языком с единым наследованием, я не вижу другого выхода, кроме создания для них интерфейсов. Если между классами используется много общего кода, то ваш подход IWalkStrategy отлично подойдет для уменьшения избыточного кода.В случае вашего примера вы также можете объединить несколько связанных действий (таких как ходьба, плавание и бег) в один класс.

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

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

2
ответ дан 17 December 2019 в 00:06
поделиться

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

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

2
ответ дан 17 December 2019 в 00:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: