Примечание - Я переместил исходный пост в конец, потому что я думаю, что он по-прежнему имеет ценность для новичков в этой теме. Ниже следует попытка переписать вопрос на основе обратной связи.
Полностью отредактированный пост
Хорошо, я постараюсь подробнее рассказать о моей конкретной проблеме. Я понимаю, что немного смешиваю доменную логику с логикой взаимодействия / представления, но, если честно, я не уверен, где ее отделить. Пожалуйста, потерпите меня :)
Я пишу приложение, которое (среди прочего) выполняет моделирование логистики для перемещения вещей. Основная идея заключается в том, что пользователь видит проект, аналогичный Visual Studio, где он может добавлять, удалять, присваивать имена, организовывать, комментировать и т. Д. Различные объекты, которые я собираюсь обрисовать в общих чертах:
Элементы и Местоположения являются базовыми элементами данных без поведения.
class Item { ... }
class Location { ... }
A WorldState - это коллекция пар предмет-местоположение. WorldState является изменчивым: пользователь может добавлять и удалять элементы или менять их местоположение.
class WorldState : ICollection<Tuple<Item,Location>> { }
A План представляет перемещение предметов в разные места в нужное время. Они могут быть импортированы в проект или сгенерированы в программе. Он ссылается на WorldState, чтобы получить начальное местоположение различных объектов. План также изменчив.
class Plan : IList<Tuple<Item,Location,DateTime>>
{
WorldState StartState { get; }
}
A Симуляция затем выполняет план. Он инкапсулирует много довольно сложного поведения и других объектов, но конечным результатом является SimulationResult , который представляет собой набор метрик, которые в основном описывают, насколько эта стоимость и насколько хорошо был выполнен План (представьте, что Проект треугольник)
class Simulation
{
public SimulationResult Execute(Plan plan);
}
class SimulationResult
{
public Plan Plan { get; }
}
Основная идея заключается в том, что пользователи могут создавать эти объекты, связывать их вместе и потенциально использовать повторно. WorldState может использоваться несколькими объектами Plan. Затем симуляция может быть запущена на нескольких планах.
Риск быть ужасно многословным, пример
var bicycle = new Item();
var surfboard = new Item();
var football = new Item();
var hat = new Item();
var myHouse = new Location();
var theBeach = new Location();
var thePark = new Location();
var stuffAtMyHouse = new WorldState( new Dictionary<Item, Location>() {
{ hat, myHouse },
{ bicycle, myHouse },
{ surfboard, myHouse },
{ football, myHouse },
};
var gotoTheBeach = new Plan(StartState: stuffAtMyHouse , Plan : new [] {
new [] { surfboard, theBeach, 1/1/2010 10AM }, // go surfing
new [] { surfboard, myHouse, 1/1/2010 5PM }, // come home
});
var gotoThePark = new Plan(StartState: stuffAtMyHouse , Plan : new [] {
new [] { football, thePark, 1/1/2010 10AM }, // play footy in the park
new [] { football, myHouse, 1/1/2010 5PM }, // come home
});
var bigDayOut = new Plan(StartState: stuffAtMyHouse , Plan : new [] {
new [] { bicycle, theBeach, 1/1/2010 10AM }, // cycle to the beach to go surfing
new [] { surfboard, theBeach, 1/1/2010 10AM },
new [] { bicycle, thePark, 1/1/2010 1PM }, // stop by park on way home
new [] { surfboard, thePark, 1/1/2010 1PM },
new [] { bicycle, myHouse, 1/1/2010 1PM }, // head home
new [] { surfboard, myHouse, 1/1/2010 1PM },
});
var s1 = new Simulation(...);
var s2 = new Simulation(...);
var s3 = new Simulation(...);
IEnumerable<SimulationResult> results =
from simulation in new[] {s1, s2}
from plan in new[] {gotoTheBeach, gotoThePark, bigDayOut}
select simulation.Execute(plan);
Проблема в том, когда выполняется нечто подобное:
stuffAtMyHouse.RemoveItem(hat); // this is fine
stuffAtMyHouse.RemoveItem(bicycle); // BAD! bicycle is used in bigDayOut,
Таким образом, в основном, когда пользователь пытается удалить элемент из WorldState (и, возможно, весь проект) с помощью вызова world.RemoveItem(item)
, я хочу убедиться, что этот элемент не упоминается ни в каких объектах Plan, которые используют этот WorldState. Если это так, я хочу сказать пользователю: «Эй! Следующий План X использует этот Предмет! Пойди и разберись с этим, прежде чем пытаться удалить его!». Тип поведения, который я не хочу получить от world.RemoveItem(item)
вызова:
- Удаление элемента, но план все еще имеет ссылку на него.
- Удаляя элемент, но с помощью Плана без уведомления удалите все элементы в своем списке, которые ссылаются на этот элемент. (на самом деле это, вероятно, желательно, но только как вторичный вариант).
Так что мой вопрос в основном состоит в том, как можно реализовать такое желаемое поведение в чистом виде. Я подумал о том, чтобы сделать это областью пользовательского интерфейса (поэтому, когда пользователь нажимает «del» на элементе, он запускает сканирование объектов Plan и выполняет проверку перед вызовом world.RemoveItem (item)) - но (a) I Я также позволяю пользователю писать и выполнять собственные сценарии, чтобы они могли сами вызывать world.RemoveItem(item)
, и (б) я не уверен, что это поведение является чисто «пользовательским интерфейсом».
Уф. Ну, я надеюсь, что кто-то все еще читает ...
Исходное сообщение
Предположим, у меня есть следующие классы:
public class Starport
{
public string Name { get; set; }
public double MaximumShipSize { get; set; }
}
public class Spaceship
{
public readonly double Size;
public Starport Home;
}
Итак, предположим, что существует ограничение, согласно которому размер космического корабля должен быть меньше или равен MaximumShipSize его дома.
Так как нам с этим справиться?
Традиционно я делал что-то похожее на это:
partial class Starport
{
public HashSet<Spaceship> ShipsCallingMeHome; // assume this gets maintained properly
private double _maximumShipSize;
public double MaximumShipSize
{
get { return _maximumShipSize; }
set
{
if (value == _maximumShipSize) return;
foreach (var ship in ShipsCallingMeHome)
if (value > ship)
throw new ArgumentException();
_maximumShipSize = value
}
}
}
Это легко сделать для простого примера, подобного этому (так что, вероятно, это плохой пример), но я нахожу, что ограничения становятся больше и сложнее, и я хочу больше связанных функций (например, реализовать метод bool CanChangeMaximumShipSizeTo(double)
илидополнительные методы, которые будут собирать корабли, которые слишком велики) Я заканчиваю тем, что пишу больше ненужных двунаправленных отношений (в этом случае SpaceBase-Spaceship, возможно, уместно) и сложный код, который в значительной степени не имеет отношения к уравнению владельцев.
Так как же с такими вещами обычно справляются?Вещи, которые я рассмотрел:
Я рассмотрел возможность использования событий, аналогичных шаблону ComponentModel INotifyPropertyChanging / PropertyChanging, за исключением того, что EventArgs будет иметь какую-то возможность Veto () или Error () (так же, как winforms позволяет вам использовать ключ или подавить выход из формы).Но я не уверен, является ли это злоупотреблением событиями или нет.
В качестве альтернативы, я могу самостоятельно управлять событиями через явно определенный интерфейс, например,
Мне нужна эта строка, иначе форматирование не будет работать
interface IStarportInterceptor
{
bool RequestChangeMaximumShipSize(double newValue);
void NotifyChangeMaximumShipSize(double newValue);
}
partial class Starport
{
public HashSet<ISpacebaseInterceptor> interceptors; // assume this gets maintained properly
private double _maximumShipSize;
public double MaximumShipSize
{
get { return _maximumShipSize; }
set
{
if (value == _maximumShipSize) return;
foreach (var interceptor in interceptors)
if (!RequestChangeMaximumShipSize(value))
throw new ArgumentException();
_maximumShipSize = value;
foreach (var interceptor in interceptors)
NotifyChangeMaximumShipSize(value);
}
}
}
Но я не уверен, что это лучше.Я также не уверен, что подобный поворот моих собственных событий будет иметь определенные последствия для производительности, или есть другие причины, по которым это может быть хорошей / плохой идеей.
Третий вариант - это, может быть, какой-то очень странный пример использования PostSharp или контейнера IoC / Dependency Injection.Я еще не вполне готов идти по этому пути.
Объект Бога, который управляет всеми проверками и т. Д. - просто ищу стек-поток для богаобъект создает у меня впечатление, что это плохо и неправильно
Моя главная проблема в том, что это кажется довольно очевидной проблемой, и то, что я думал, было бы разумнообщий, но я не видел никаких обсуждений по этому поводу (например, System.ComponentModel не предоставляет никаких средств для вето событий PropertyChanging - не так ли?);это заставляет меня бояться, что я (еще раз) не смог понять некоторые фундаментальные понятия в соединении или (что еще хуже) объектно-ориентированного проектирования в целом.
Комментарии?}