Как разработать модель предметной области с интерфейсами, которые зависят от свойств модели - PullRequest
1 голос
/ 23 января 2011

У меня есть модель предметной области (см. Пример ниже), которая имеет несколько интерфейсов, которые определяются на основе значения свойств в самой модели.Хотя приведенный ниже код «работает», он не выглядит правильным.Я уверен, что, возможно, есть лучший подход, но я не смог придумать один.Мне было бы очень интересно получить обратную связь с некоторыми альтернативными подходами.

public interface IPickListGenerator
    {
        void Execute();
    }

public class SportsPicklistGenerator : IPickListGenerator
{
        public void Execute()
        {
            // Do something
        }
}

public class EntertainmentPicklistGenerator : IPickListGenerator
{
    public void Execute()
    {
        // Do something
    }
}

public interface IQuestionIsAnswerableDeterminer
{
    void Execute();
}

public class GameQuestionIsAnswerableDeterminer : IQuestionIsAnswerableDeterminer
{
    public void Execute()
    {
        // Do something
    }
}

public class RoundQuestionIsAnswerableDeterminer : IQuestionIsAnswerableDeterminer
{
    public void Execute()
    {
        // Do something
    }
}

public class Pool
{
    public enum PoolType
    {
        Sports,
        Entertainment
    }

    public enum DeadlineType
    {
        Game,
        Round
    }

    private IPickListGenerator mPickListGenerator = null;
    private IQuestionIsAnswerableDeterminer mQuestionIsAnswerableDeterminer = null;

    public PoolType Type { get; set; }
    public DeadlineType Deadline { get; set; }

    public IQuestionIsAnswerableDeterminer IsQuestionAnswerableDeterminer
    {
        get
        {
            if (mQuestionIsAnswerableDeterminer == null) SetPoolSpecificInterfaces();
            return mQuestionIsAnswerableDeterminer;
        }
    }

    public IPickListGenerator PickListGenerator
    {
        get
        {
            if (mPickListGenerator == null) SetPoolSpecificInterfaces();
            return mPickListGenerator;
        }
    }

    private void SetPoolSpecificInterfaces()
    {
        switch (Type)
        {
            case Pool.PoolType.Sports:
                mPickListGenerator = new SportsPicklistGenerator();
                break;
            case Pool.PoolType.Entertainment:
                mPickListGenerator = new EntertainmentPicklistGenerator();
                break;
        }

        switch (Deadline)
        {
            case Pool.DeadlineType.Game:
                mQuestionIsAnswerableDeterminer = new GameQuestionIsAnswerableDeterminer();
                break;
            case Pool.DeadlineType.Round:
                mQuestionIsAnswerableDeterminer = new RoundQuestionIsAnswerableDeterminer();
                break;
        }
    }
}
// Example usages:
    var pool = new Pool{ Type = Pool.PoolType.Sports, Deadline = Pool.DeadLineType.Game};
    pool.IsQuestionAnswerableDeterminer.Execute();
    pool.PickListGenerator.Execute();

Ответы [ 2 ]

4 голосов
/ 23 января 2011

Кроме потенциальных логических несоответствий (впоследствии установка Type или Deadline приведет к тому, что «неверное» значение для вашего детерминанта и генератора будет возвращено пользователю), я не вижу ничего плохого в дизайне избольшая перспектива.Это очень похоже на работу многих фабричных шаблонов: внешний интерфейс (или базовый класс), который реализуется несколькими внутренними конкретными классами, правильный экземпляр которого определяется каким-то различающим значением.

Если конструкция не является дорогой (и не всегда будет необходимой), я бы предложил перейти к более умным свойствам для Type и Deadline, которые устанавливают соответствующие переменные реализации интерфейса в их set блоках, а не загружают их лениво вдругие свойства, как вы делаете сейчас.

Если конструкция стоит дорого, я все равно переключился бы на более умные свойства для Type и Deadline и очистил бы (и сделал бы любую необходимую очисткуна) значения, которые могли быть ранее установлены.

1 голос
/ 23 января 2011

Возможно, вам нужно удалить зависимости из вашего кода. Чрезмерность из вашего кода существует в двух отношениях:

  • у вас избыточный код (интерфейсы)
  • у вас чрезмерные ограничения (перечисления)

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

public interface IPool
{
    void Execute();
}

public class Pool : IPool
{
    private Pool() { }

    public IPickListGenerator PickListGenerator { set; private get; }
    public IQuestionIsAnswerableDeterminer IsQuestionIsAnswerableDeterminer { set; private get; }

    public static IPool GetSportsGame() 
    {
        return new Pool
        {
            PickListGenerator = new SportsPicklistGenerator(),
            IsQuestionIsAnswerableDeterminer = new GameQuestionIsAnswerableDeterminer()
        };
    }
    public static IPool GetSportsEntertainment() 
    {
        return new Pool
        {
            PickListGenerator = new SportsPicklistGenerator(),
            IsQuestionIsAnswerableDeterminer = new EntertainmentPicklistGenerator()
        };
    }
    public void Execute()
    {
        IsQuestionIsAnswerableDeterminer.Execute();
        PickListGenerator.Execute();
    }
}

--- История этого поста, приведшая к этому решению:

Вы можете улучшить свой код, упростив код, потому что в вашей реализации класс Pool ограничен перечислениями, и если это так, класс Pool не является расширяемым. Вам, вероятно, придется переписывать SetPoolSpecificInterfaces () снова и снова, когда появятся новые требования и реализация интерфейса. Таким образом, самым слабым местом текущего проекта является конкретный метод SetPoolSpecificInterfaces () и, если это так, не расширяемый класс Pool.

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

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // Example usages:
            var pool = new Pool
            {
                PickListGenerator = new SportsPicklistGenerator(),
                IsQuestionIsAnswerableDeterminer = new GameQuestionIsAnswerableDeterminer()
            };
            pool.ExecuteIsQuestionIsAnswerableDeterminer();
            pool.ExecutePickListGenerator();
        }
    }

    public class Pool
    {
        public IPickListGenerator PickListGenerator { set; private get; }
        public IQuestionIsAnswerableDeterminer
            IsQuestionIsAnswerableDeterminer { set; private get; }

        public void ExecuteIsQuestionIsAnswerableDeterminer()
        {
            IsQuestionIsAnswerableDeterminer.Execute();
        }

        public void ExecutePickListGenerator()
        {
            PickListGenerator.Execute();
        }
    }
}

Вы, вероятно, можете остановиться здесь, но что, если вы всегда будете вызывать ExecuteIsQuestionIsAnswerableDeterminer () перед вызовом ExecutePickListGenerator ()? тогда вы можете изменить код следующим образом:

    public class Pool
    {
        public IPickListGenerator PickListGenerator { set; private get; }
        public IQuestionIsAnswerableDeterminer 
            IsQuestionIsAnswerableDeterminer { set; private get; }

        public void Execute()
        {
            // improvements, seen by Arnis
            if (IsQuestionIsAnswerableDeterminer == null)
            {
                thrown new ArgumentException("IsQuestionIsAnswerableDeterminer");
            }
            if (PickListGenerator == null)
            {
                thrown new ArgumentException("PickListGenerator");
            }
            IsQuestionIsAnswerableDeterminer.Execute();
            PickListGenerator.Execute();
        }
    }

Если это так, вы можете написать что-то похожее на это:

var pool = new Pool
{
    PickListGenerator = new SportsPicklistGenerator(),
    IsQuestionIsAnswerableDeterminer = new GameQuestionIsAnswerableDeterminer()
};
pool.Execute();

// reuse the same class logic in Pool class in Execute method
pool.PickListGenerator = new GamePickListGenerator();
pool.Execute();

// reuse the same class logic in Pool class in Execute method
pool.IsQuestionIsAnswerableDeterminer = new RoundQuestionIsAnswerableDeterminer();
pool.Execute();

Как сказал @ Adam Robinson, это меняет некоторую логику пула (чтобы исправить это, вы можете использовать реализацию близкого пула), так что вы можете исправить это таким образом, добавив новый интерфейс, IPool, добавив приватный конструктор в класс Pool и static логическая реализация, которая полностью устранит необходимость в легком утверждении:

public interface IPool
{
    void Execute();
}

public class Pool : IPool
{
    private Pool() { }

    public IPickListGenerator PickListGenerator { set; private get; }
    public IQuestionIsAnswerableDeterminer IsQuestionIsAnswerableDeterminer { set; private get; }

    public static IPool GetSportsGame() 
    {
        return new Pool
        {
            PickListGenerator = new SportsPicklistGenerator(),
            IsQuestionIsAnswerableDeterminer = new GameQuestionIsAnswerableDeterminer()
        };
    }
    public static IPool GetSportsEntertainment() 
    {
        return new Pool
        {
            PickListGenerator = new SportsPicklistGenerator(),
            IsQuestionIsAnswerableDeterminer = new EntertainmentPicklistGenerator()
        };
    }
    public void Execute()
    {
        IsQuestionIsAnswerableDeterminer.Execute();
        PickListGenerator.Execute();
    }
}

Это отредактировал Арнис:

public class Pool{
  private readonly IPickListGenerator _generator;
  private readonly IQuestionIsAnswerableDeterminer _determiner;
  public Pool(IPickListGenerator generator, 
    IQuestionIsAnswerableDeterminer determiner){

    if(determiner==null||generator==null)
      throw new ArgumentNullException();
    _generator=generator;
    _determiner=determiner;
  }

  public void Execute(){
    _determiner.Execute();
    _generator.Execute();
  }
}

var generator=new SportsPicklistGenerator();
var determiner=new GameQuestionIsAnswerableDeterminer();
var pool = new Pool(generator, determiner);
pool.Execute();
...