Инкапсуляция ссылочных типов внутри коллекции

Я объявил класс с несколькими свойствами

class Soil
{
  public double AnglePhi { get; set; }
  public double AngleDelta { get; set; }
  .
  .
  .
}

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

class Soils
{
  private const Byte numberOPredefined = 10;   
  private IList<Soil> soils;

  public Soil this[ushort i]
  {
    get { return new Soil() { AngleDelta = soils[i].AngleDelta, ... }; }
    set { if (i > numberOPredefined) soils[i] = value; }
  }
  .
  .
  .
}

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

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

Я хотел бы спросить, имеет ли вообще вышеуказанное «решение» какую-либо ценность или оно плохо определено.

Я думаю, что это типичная ситуация, например, у вас есть коллекция ссылочных типов и вы хотите их инкапсулировать. Каковы типичные рамки мышления в этих ситуациях?

EDIT:
Хорошо, после прочтения ответов я изменил решение для этого

class Soil
{
  private readonly double _AnglePhi;
  public double AnglePhi { get { return _AnglePhi; } }

  private readonly double _AngleDelta;
  public double AngleDelta { get { return _AngleDelta; } }
  .
  .
}

class SoilCollection
{
  private List<Soil> _Soils;
  public IList<Soil> Soils { get { return _Soils.AsReadOnly(); } }
  .
  .
}

Я думаю, что класс Soil нуждался в логике внутри себя, а не внутри другого класса. Отпишусь, если найду недостатки.


person Dimi_Pel    schedule 21.01.2011    source источник


Ответы (4)


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

struct Soil
{
  private readonly double anglePhi;
  private readonly double angleDelta;

  public Soil(double phi, double delta) {
    this.anglePhi = phi;
    this.angleDelta = delta; 
  }

  public double AnglePhi { get { return anglePhi; } }
  public double AngleDelta { get { return angleDelta; } }
}

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

person Botz3000    schedule 21.01.2011
comment
Но, как уже говорилось, в классе есть несколько свойств, и это нарушило бы правило о том, что структуры должны быть небольшими. - person Dimi_Pel; 21.01.2011

Вы можете определить Soil как ValueObject, тогда он будет неизменяем после создания:

class Soil
{
  public Soil(double anglePhi, double angleDelta)
  {
      AnglePhi = anglePhi;
      AngleDelta = angleDelta;
  }

  public double AnglePhi { get; private set; }
  public double AngleDelta { get; private set; }
}

Думаю, лучше переименовать Soils в SoilCollection.

person Sergey Berezovskiy    schedule 21.01.2011
comment
Вы имеете в виду ValueObject, как объясняет эта ссылка? blogs.msdn.com/b/lucabol/archive/2007/12/03/ - person Dimi_Pel; 21.01.2011
comment
Ну, я не читал всю статью, но ДА, согласно заголовку Объекты-значения - это объекты, для которых идентичность основана на их состоянии, а не на их указателе в памяти. Неизменяемый объект значения — это объект значения, который нельзя изменить. Вы не можете изменить его состояние, вы должны создавать новые. - person Sergey Berezovskiy; 21.01.2011

Нет никаких причин, по которым вам нужно реализовывать и сеттер, и геттер в классе Soil. Вы можете выбрать только реализацию get, которая сделает объекты Soil доступными только для чтения.

Очевидно, вам понадобится какой-то другой метод установки внутренних значений - можно ли это сделать в конструкторе.

Например:

class Soil
{
    private double m_anglePhi;

    public Soil( double anglePhi )
    {
        m_anglePhi = anglePhi;
    }

    public double AnglePhi 
    {
        get { return m_anglePhi; }
    }
}
person Nick    schedule 21.01.2011

я бы предложил:

1) Сделать класс Soil неизменяемым.

2) Сделать класс Soils коллекцией только для чтения. Например, получить от IList и объявить методы добавления и т. д. как явную реализацию интерфейса.

person Manish Basantani    schedule 21.01.2011
comment
Спасибо за ответ, Эмби. - person Dimi_Pel; 21.01.2011