Реализация принудительного отношения один ко многим в C #? - PullRequest
0 голосов
/ 12 сентября 2010

В моем приложении я работаю с объектами Simfile и Notechart.Simfile - это, по сути, контейнер Notechart со следующими ограничениями:

1) Каждая Notechart должна постоянно содержаться в одном родительском Simfile.
2) Учитывая объект Simfile, мне нужновозможность получить все содержащиеся в нем Блокноты (должно быть легко).
3) Учитывая Блокнот, мне нужно иметь возможность получить его родительский Simfile (сложнее).

Моя текущая реализация использует словарькак базовый класс Simfile, поскольку каждая Notechart имеет уникальный ключ, по крайней мере, в Simfile.Проблема, с которой я сталкиваюсь, заключается в том, как обеспечить, чтобы в Notechart всегда был родительский Simfile?Прямо сейчас я могу создать Блокнот независимо от Simfile и добавить его в словарь Simfile, не делая нужную ссылку из Блокнота -> Simfile.

В идеале, наилучшим разрешением будет Добавить () метод в Simfile, который можно вызывать только из конструкторов Notechart, но я не думаю, что это возможно.

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

Ответы [ 6 ]

2 голосов
/ 12 сентября 2010

Вы можете использовать структуру кода, подобную этой:

public class Simfile
{
   public Simfile()
   {
      Notecharts = new List<Notechart>();
   }
   public List<Notechart> Notecharts { get; private set; }
}

public class Notechart
{
   public Notechart(Simfile simfile)
   {
      Simfile = simfile;
      simfile.Notecharts.Add(this);
   }
   public Simfile Simfile { get; private set; }
}

Единственное, в чем вы должны убедиться, это то, что каждый другой конструктор, добавляемый вами в класс Notechart, должен принимать аргумент Simfile.

РЕДАКТИРОВАТЬ (на основании комментария)


Если вы не хотите, чтобы Notechart не мог быть добавлен в Simfile, если это не родительский simfile, вы можете сделать это изменение:

public class Simfile
{
   public Simfile()
   {
      ChildNotecharts = new List<Notechart>();
   }
   private List<Notechart> ChildNotecharts { get; set; }
   public IEnumerable<Notechart> Notecharts
   {
      get { return ChildNotecharts; }
   }
   public int AddNotechart(Notechart notechart)
   {
      if (ChildNotecharts.Contains(notechart)) return 0;
      if (notechart.Simfile != this) return -1;
      ChildNotecharts.Add(notechart);
      return 1;
   }
}

public class Notechart
{
   public Notechart(Simfile simfile)
   {
      Simfile = simfile;
      simfile.AddNotechart(this);
   }
   public Simfile Simfile { get; private set; }
}
2 голосов
/ 12 сентября 2010

Один из способов достижения этой цели - не разрешать внешним абонентам создавать экземпляры NoteChart и иметь метод в Simfile, который создает и возвращает экземпляры NoteChart, которые уже подключены к экземпляру Simfile.

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

public class NoteChart
{
  public SimFile Owner {get; private set;}
  public string Id { get; private set; }

  internal NoteChart(SimFile owner, string id)
  {
    this.Owner = owner;
    this.Id = id;
    Owner.Add(id, this);
  }
}

public class SimFile : Dictionary<string, NoteChart>
{
  public NoteChart CreateNoteChart(string id)
  {
    NoteChart noteChart = new NoteChart(this, id);
    return noteChart;
  }
}

В таком случае это будет выглядеть примерно так: 10000

  // Get instance of SimFile somehow. I just newed it here for the example
  SimFile sim = new SimFile();

  // Create a new NoteChart on the SimFile
  NoteChart chart = sim.CreateNoteChart("NoteChart-1");
1008Конструктор NoteChart помечен как внутренний.Это означает, что другие классы в других сборках не смогут напрямую создавать экземпляры класса NoteChart и будут вынуждены работать через метод SimFile.CreateNoteChart.

В той же сборке код все еще может вызывать конструктор internal, но ему придется передать собственные SiteFile и Id, и конструктор добавит NoteChart в SimFile.

Возможно, вы захотите пересмотреть проектный выбор наследования SimFile из словаря, может быть, лучше просто включить словарь в качестве члена и предоставить требуемую функциональность.Например, теперь NoteChart может быть случайно добавлен непосредственно в другой экземпляр SimFile через SimFle.Add, который унаследован от Dictionary, и с несовпадающим идентификатором.Но это всего лишь пища для размышлений.

1 голос
/ 13 сентября 2010

Ваше основное требование, по-видимому, заключается в том, что вы не хотите, чтобы внешний код изменял SimFile, за исключением создания нового NoteCharts.Поэтому нам определенно необходимо:

  • ограничить доступ к конструктору NoteChart (в противном случае вы можете иметь экземпляр NoteChart с родительским элементом SimFile, который фактически не содержит его)

  • ограничить доступ к словарю SimFile (в противном случае другой код может добавить что-то к нему)

  • разрешить доступ только для чтенияиз словаря SimFile.

Вот как бы я это сделал.

public class NoteChart
{
    public SimFile Parent { get; private set; }
    public string Id { get; private set; }

    // internal: code outside this assembly cannot instantiate this class
    internal NoteChart(SimFile parent, string id)
    {
        this.Parent = parent;
        this.Id = id;
        Parent.dictionary.Add(id, this);
    }
}

// Implement only IEnumerable, as that is read-only;
// all the other collection interfaces are writable
public class SimFile : IEnumerable<KeyValuePair<string, NoteChart>>
{
    // internal: not accessible to code outside this assembly
    internal Dictionary<string, NoteChart> dictionary =
        new Dictionary<string, NoteChart>();

    // Public method to enable the creation of a new note chart
    // that is automatically associated with this SimFile
    public NoteChart CreateNoteChart(string id)
    {
        NoteChart noteChart = new NoteChart(this, id);
        return noteChart;
    }

    // Read-only methods to retrieve the data. No writable methods allowed.
    public NoteChart this[string index] { get { return dictionary[index]; } }
    public IEnumerator<KeyValuePair<string, NoteChart>> GetEnumerator() {
        return dictionary.GetEnumerator(); }
    public int Count { get { return dictionary.Count; } }
    public IEnumerable<string> Keys { get { return dictionary.Keys; } }
    public IEnumerable<NoteChart> Values { get { return dictionary.Values; } }
}
1 голос
/ 12 сентября 2010

Вот два возможных решения из многих.

Иметь конструктор в NoteChart, который принимает объект Simfile и вызывает функцию внутри объекта Simfile, которая добавляет NoteChart. Если у вас есть такой параметризованный конструктор, конструктор по умолчанию больше не доступен. Примерно так:

public class SimFile
{
    public void AddNoteChart(NoteChart nc)
    {
        // Here you can add the note chart to the SimFile dictionary
    }
}

public class NoteChart
{
    public SimFile PapaSimFile { get; private set; }

    NoteChart(SimFile simFile)
    {
        if (simFile == null)
        {
            // throw exception here
        }
        PapaSimFile = simFile;
        PapaSimFile.AddNoteChart(this);
    }
}    

Другой вариант - сделать NoteChart доступным только из SimFile. Например:

public class SimFile
{
    public class NoteChart
    {
        public SimFile SimFile { get; internal set; }
    }

    public NoteChart CreateNoteChart()
    {
        NoteChart nc = new NoteChart();
        nc.SimFile = this;
        // Here you can add the note chart to the SimFile dictionary
        return nc;
    }
}

У каждого подхода есть свои преимущества и недостатки, вам нужно посмотреть на картину целиком, чтобы решить, что лучше.

0 голосов
/ 13 сентября 2010

Ну, я понял это. Я сделал класс Notechart абстрактным (полностью реализованным, но абстрактным для предотвращения его создания другими классами), а затем создал внутренний класс в Simfile, производный от него (NotechartWorking) с защищенными конструкторами. Затем я последовал предложенным здесь советам, чтобы Simfile генерировал для меня Notecharts. Итак, я создавал объекты NotechartWorking, но возвращал их как Notecharts, что в итоге получилось хорошо.

0 голосов
/ 13 сентября 2010

Следующее реализует Блокнот, у которого всегда есть родительский SimFile. Он работает, скрывая контейнер (чтобы предотвратить добавление объектов в SimFile без изменения родительского элемента) и делая Notechart частью класса SimFile, чтобы он мог обращаться к частной коллекции при изменении его родительского элемента. Это естественно, так как вы требуете, чтобы Notecharts не существовал без соответствующего SimFile; они действительно являются частью SimFiles.

public class SimFile
{
    private List<Notechart> _notecharts = new List<Notechart>();

    public IEnumerable<Notechart> Notecharts 
    {
        get {return _notecharts.AsEnumerable<Notechart>();}
    }

    public class Notechart
    {
        public Notechart(SimFile parent)
        {
            this.Parent = parent;
        }

        private SimFile _parent = null;
        public SimFile Parent
        {
            get {return _parent;}
            set 
            {
                if (value != null)
                {
                    if (_parent != null)
                    {
                        _parent._notecharts.Remove(this);
                    }
                    _parent = value;
                    _parent._notecharts.Add(this);
                }
                else
                {
                    throw new Exception("A Notechart MUST have a parent SimFile");
                }
            }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...