Как проектировать вокруг отсутствия const в C # / Java? - PullRequest
3 голосов
/ 14 октября 2010

При попытке смоделировать свой домен я столкнулся со следующей проблемой.Давайте представим, что у нас есть Вещи:

class Thing
{
    public int X { get; set; }
}

Вещи имеют свойство X. Затем, есть пакеты, которые объединяют Вещи.Но домен требует наличия некоторых ограничений на вещи, которые могут содержать пакеты.Пусть, к примеру, совокупное значение Xes не может быть выше определенного предела:

class Pack
{
    private readonly List<Thing> myThings = new List<Thing>();
    private const int MaxValue = 5;

    public void Add(Thing thing)
    {
        if (myThings.Sum(t => t.X) + thing.X > MaxValue)
            throw new Exception("this thing doesn't fit here");
        myThings.Add(thing);
    }

    public int Count
    {
        get { return myThings.Count; }
    }

    public Thing this[int index]
    {
        get { return myThings[index]; }
    }
}

Поэтому я проверяю перед добавлением Thing в пакет для условия, но все еще так легкопопасть в неприятности:

var pack = new Pack();
pack.Add(new Thing { X = 2 });
pack.Add(new Thing { X = 1 });

var thingOne = new Thing { X = 1 };
var thingTwo = new Thing { X = 3 };

//pack.Add(thingTwo); // exception
pack.Add(thingOne);   // OK

thingOne.X = 5;       // trouble
pack[0].X = 10;       // more trouble

В C ++ решением было бы сделать копию при вставке и вернуть ссылку на констант в индексаторе.Как спроектировать эту проблему в C # (и, вероятно, Java)?Я просто не могу придумать хорошего решения:

  1. сделать Вещи неизменными - но что, если она должна быть изменчивой?
  2. смотреть Вещи в пакете с событием / наблюдателем- но это означает, что Pack навязывает дизайн Thing;что если вещи имеют больше свойств?Тогда я закончу только одним событием из-за необходимости, чтобы Пак следил за изменениями - это кажется мне неловким.

Какие-нибудь идеи или предпочтительные решения?

РЕДАКТИРОВАТЬ:

Возвращаясь к этому вопросу ... Я принял ответ Итай.Он прав.Первоначальная проблема заключалась в том, что из одного контекста вы хотели бы, чтобы объект Thing был неизменным, а из другого контекста вы хотели бы, чтобы он был изменяемым.И это требует отдельного интерфейса ... возможно.Я сказал «возможно», потому что в большинстве случаев Pack будет Агрегатом вещей (в смысле DDD) и, следовательно, будет владельцем объектов - это означает, что он не должен давать вам возможность изменять принадлежащий объект (либовернуть копию или вернуть неизменный интерфейс).

Приятно, что в C ++ эта конкретная вещь может быть легко обработана модификатором const.Похоже, гораздо меньше кодирования, если вы хотите, чтобы все было в согласованном состоянии.

Ответы [ 7 ]

6 голосов
/ 14 октября 2010

Сделать Thing неизменным.

class Thing
{
    public Thing (int x)
    {
       X = x;
    }
    public int X { get; private set; }
}

Кроме того, вместо if (myThings.Sum(t => t.X) + thing.X > MaxValue) я думаю, что лучше хранить поле суммы в пакете, чтобы вам не приходилось каждый раз пересчитывать сумму.

EDIT
Извините - я пропустил тот факт, что вы заявили, что вам нужно, чтобы он был изменяемым.
Но ... Как будет работать ваше решение с ++? Я не очень хорошо знаю c ++, но разве константа c ++ не предотвратит изменения в экземплярах, которые есть в пакете?

EDIT2
Используйте интерфейс

public interface IThing
{
  int X { get; }
}

public class Thing : IThing
{
  int X { get; set; }
}

class Pack
{
    private readonly List<IThing> myThings = new List<IThing>();
    private const int MaxValue = 5;

    public void Add(IThing thing)
    {
        if (myThings.Sum(t => t.X) + thing.X > MaxValue)
            throw new Exception("this thing doesn't fit here");
        myThings.Add(new InnerThing(thing));
    }

    public int Count
    {
        get { return myThings.Count; }
    }

    public IThing this[int index]
    {
        get { return myThings[index]; }
    }

    private class InnerThing : IThing
    {
      public InnerThing(IThing thing)
      {
        X = thing.X;
      }
      int X { get; private set; }
    }
}
3 голосов
/ 14 октября 2010

У вас может быть Thing, реализующий интерфейс iReadOnlyThing, который имеет только ReadOnly доступ к каждому из свойств Thing.

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

Вы можете зайти так далеко, что реализовали контейнерный класс только для чтения так же, как

System.Collections.ObjectModel.ObservableCollection<T>

и

System.Collections.ObjectModel.ReadOnlyObservableCollection<T>

делать. Конструктор ReadOnlyObservableCollection принимает в качестве аргумента ObservableCollection и предоставляет доступ на чтение к коллекции, не предоставляя права на запись. Он имеет преимущество, заключающееся в том, что он не позволяет приводить коллекцию ReadOnlyObservableCollection обратно в коллекцию ObservableCollection.

Мне кажется, я должен указать, что шаблон Model-View-ViewModel, используемый при разработке WPF, фактически работает по предложенному вами принципу - иметь одно событие NotifyPropertyChanged, которое содержит строку, идентифицирующую измененное свойство. ..

Затраты на кодирование шаблона MVVM значительны, но некоторым людям это нравится ...

2 голосов
/ 14 октября 2010

Следуя вашим утверждениям, вы действительно имеете некоторую бизнес-логику за X, которая также должна применяться, когда установлена ​​X. Вы должны тщательно подумать о том, как вы проектируете X, если предполагается, что он запускает какую-то бизнес-логику. Это больше, чем просто собственность.

Вы можете рассмотреть следующие решения:

  • Вам действительно нужно разрешить изменить X? Даже если класс не является неизменяемым, вы все равно можете сделать X доступным только для чтения.
  • Предоставляет метод SetX(...), который инкапсулирует бизнес-логику, чтобы проверить, является ли Thing частью Pack, и вызвать эту логику проверки Pack.
  • Конечно, вместо метода установки вы также можете использовать свойство set { } для этой логики.
  • В Pack.Add создайте копию Thing, которая на самом деле является подклассом, добавляющим логику проверки.
  • ...

Что бы вы ни делали, вы не сможете обойти следующий очень простой вопрос проектирования: либо вы разрешаете изменять X, либо вам приходится вводить проверочную / бизнес-логику в каждую часть, которая позволяет изменять X. Или вы делаете X только для чтения и адаптировать части приложения, которые могут захотеть изменить X соответственно (например, удалить старый объект, добавить новый ...).

1 голос
/ 14 октября 2010

Как насчет того, чтобы изменить событие в Thing;Pack подписывается на это событие, и когда вы изменяете X в Thing, Pack выполняет необходимую проверку в обработчике событий, и если изменение не подходит;выдает исключение?

1 голос
/ 14 октября 2010

Для простых объектов вы можете сделать их неизменяемыми.Но не переусердствуйте.Если вы делаете сложные объекты неизменяемыми, вам, вероятно, потребуется создать некоторый класс построителя, чтобы сделать работу с ним приемлемой.И это, как правило, не стоит.

И вы не должны делать объекты с идентичностью неизменными.Обычно вы делаете объект неизменным, если хотите, чтобы он вел себя как тип значения.

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

0 голосов
/ 14 октября 2010

Я бы подумал о том, чтобы изменить способ использования пакета.

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

Конечно, недостатком является то, что вы должны помнить, чтобы положить его обратно в пакет!

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

0 голосов
/ 14 октября 2010

К сожалению, в C # нет ничего, что обладает такой мощью, как const в C / C ++, чтобы гарантировать (во время компиляции), что значение не будет изменено.

Единственное, что им приходит в голову - это неизменность (как упомянуто вами и Итай), но она не обладает такой гибкостью, как const. (

Если вам нужно, чтобы он был изменяемым в некоторых сценариях, вы можете сделать что-то только во время выполнения, как это

class Thing
{
    public bool IsReadOnly {get; set;}

    private int _X;
    public int X
    {
        get
        {
            return _X;
        }
        set
        {
            if(IsReadOnly)
            {
                throw new ArgumentException("X");
            }

            _X = value;
        }
    }
}

но он загрязняет ваш код с помощью try/catch и изменяет значение IsReadOnly. Так что не очень элегантно, но, может быть, стоит подумать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...