Я столкнулся с несколькими случаями, когда у меня есть родительский объект с несколькими объектами и где изменения информации в одном дочернем объекте затрагивают другие.Распространение изменений в одном дочернем объекте на другой
Для примера рассмотрим следующий случай:
interface IUniverse
{
IStorage ShirtStorage;
IList<IHuman> Humans;
}
Interface IStorage:
{
string Location;
int Capacity;
IList<IShirt> Items;
}
Interface IHuman
{
string Name;
int Age;
IList<IShirt> Shirts;
}
Я хотел бы, чтобы удалить определенную рубашку из ShirtStorage в моей вселенной, но в то же время, поскольку рубашка удаляется из существования, оно должно также удаляться от всех людей.
Я думал 3 способа сделать это:
Во-первых, мы можем ввести
Add(IClothing)
и
Remove(IClothing)
методы к
IStorage<T>
и
IHuman
.
interface IUniverse
{
IStorage ShirtStorage;
IList<IHuman> Humans;
}
Interface IStorage
{
string Location;
int Capacity;
IList<IShirt> Items;
**void Add(IShirt);**
**void Remove(IShirt);**
}
Interface IHuman
{
string Name;
int Age;
IList<IShirt> Shirts;
**void AddShirts(IShirt);**
**void RemoveShirts(IShirts);**
}
Затем, реализация указанных интерфейсов не будет иметь ничего под капотом, который удаляет особую рубашку из всех людей, когда он удаляется из ShirtStorage.
Недостаток этой конструкции заключается в том, что каждый раз, когда программист удаляет рубашку со вселенной, ему придется вручную удалять каждую ссылку с каждого человека.
То есть, программист должен знать точную структуру вселенной, чтобы удалить одну рубашку. В случае, когда структура вселенной становится очень сложной, так что ссылки на определенную рубашку могут появляться больше, чем только в IHuman
, это может оказаться ошибочным и утомительным.
Во-вторых, мы так же ввести Add(IShirt)
и Remove(IShirt)
методы для интерфейсовIStorage
и IHuman
:
interface IUniverse
{
IStorage<IShirt> ShirtStorage;
IList<IHuman> Humans;
}
Interface IStorage
{
string Location;
int Capacity;
IList<IShirt> Items;
**void Add(IShirt);**
**void Remove(IShirt);**
}
Interface IHuman
{
string Name;
int Age;
IList<ICShirt> Shirts;
**void AddShirt(IShirt);**
**void RemoveShirt(IShirt);**
}
.. Однако на этот раз, мы используем реализацию вышеуказанных интерфейсов таких что под капотом происходит некоторое уведомление. То есть,
class Storage : IStorage
{
IUniverse parentUniverse;
string Location;
int Capacity;
IList<IShirt> Items;
// ... Implementation for Add(IShirt item) is trivial
void Remove(IShirt item)
{
this.Items.Add(item);
foreach (IHuman human in this.parentUniverse)
foreach(IClothing clothing in human.Clothings)
if (clothing == item)
human.RemoveClothing(clothing);
}
}
Действительно, помещая все уведомления в реализации интерфейса, потребители интерфейса не должны пройти через каждую возможную ссылку на конкретный IShirt
, когда он хочет, чтобы удалить его от существования, что делает его better
в этом смысле по сравнению с предыдущим решением.
Однако недостатком является то, что такая конструкция по своей сути приводит к патологическому лечению и нарушает принцип единой ответственности. Если программист называет Remove(IShirt)
на ShirtStorage
, он не будет знать, какая ссылка удаляется с того места.
Если программист желает написать графический интерфейс с использованием шаблона посредника, например, он не будет знать, какое уведомление следует отправить.
У каких именно людей удалены рубашки, что требует обновления графического интерфейса для какого-либо компонента, который отражает список рубашек, принадлежащих определенному человеку?Что делать, если у меня есть класс Catalog
с именами всех рубашек - не удалялась ли запись, соответствующая удаленной рубашке (под капотом)? Должен ли я также обновить соответствующий компонент графического интерфейса для моих каталогов?
В-третьих, мы вводим Add(IShirt)
и Remove(IShirt)
методы IUniverse
вместо:
interface IUniverse
{
IStorage ShirtStorage;
IList<IHuman> Humans;
void Add(IShirt);
void Remove(IShirt);
}
Interface IStorage:
{
string Location;
int Capacity;
IList<IShirt> Items;
}
Interface IHuman
{
string Name;
int Age;
IList<IShirt> Shirts;
}
Поступая таким образом, мы заставляем потребителей интерфейса, чтобы принять, что удаление рубашку влияет не только для хранения рубашки, но и других членов IUniverse
.
Однако недостатки подобны недостаткам во втором решении. Кроме того, IUniverse
экземпляры в конечном итоге превращаются в объект Бога. Каждое место, где мне нужно снять рубашку из вселенной, я должен иметь ссылку на вселенную.
Если конкретный компонент GUI просто хочет отображать информацию для ShirtStorage
и разрешить взаимодействие с хранилищем (например, добавление и удаление рубашек), не приведет ли это к некоторой связи между компонентом GUI и Universe
, когда единственная связь, которая должна существовать, - это компонент GUI и IStorage
?
Я написал несколько приложений, в которых использовалось сочетание всех трех решений. Действительно, некоторые решения кажутся лучше других в разных случаях, но несоответствия - это боль, потому что я почти всегда забывал делать определенные вещи при переходе от одного проекта к другому.