Шаблоны / структуры для обратной навигации отношения "многие ко многим"?
Пользовательская история:
Пользователь нашего приложения создает дорожные поездки. roadtrip
это последовательный ряд интересных направлений. каждый destination
есть некоторые подробности о деятельности или взгляде, чтобы увидеть в то время как там. Таким образом, наш пользователь определяет две дорожные поездки, где у каждой поездки было несколько уникальных мест назначения и несколько мест назначения, общих для обоих - например, обе поездки включают Смитсоновский институт. Приложение сохраняет все обновления в памяти и фиксирует данные в базе данных только тогда, когда пользователь нажимает "Сохранить". Пользователь активно обновляет обе поездки и может переключаться между ними по желанию. В некоторых точках нашего приложения мы имеем дело со Смитсоновским пунктом назначения, но иногда нам нужно перемещаться вверх по нашей иерархии объектов от пункта назначения до содержащего его маршрута. Проблема в том, что пункт назначения участвует в двух дорожных поездках.
RoadTrip1
|
+-Destination1
+-Destination2
+-Destination3
+-Smithsonian (A) //Navigate up to RoadTrip1
RoadTrip2
|
+-Destination4
+-Smithsonian (B) //Navigate up to RoadTrip2
+-Destination5
Какой хороший шаблон проектирования или структуру данных мы могли бы использовать для обратной навигации, гарантируя, что у нас есть только одна копия нашего конечного объекта?
Требования:
- Ваша модель участвует во многих отношениях.
- Представьте все модели только один раз в памяти (Identity Map).
- Ваша структура данных должна быть легко ориентируемой. Вы можете не только переходить от родителя к потомку, но вы также можете переходить от потомка к родителю, по которому изначально был получен потомок.
- Я хочу избежать введения дополнительной схемы в модель данных.
Моя лучшая идея на данный момент - обернуть каждый целевой объект контекстным объектом (аналогично тому, как связанные списки оборачивают узлы). Объект контекста будет поддерживать указатель на родительский объект, из которого он был изначально выбран. Мы будем иметь дело с каждым пунктом назначения всегда через его оболочку. Я считаю, что это будет либо шаблон Proxy, либо Decorator (я склоняюсь к Proxy). (Разве это не было бы той же самой идеей, как то, как объект jQuery охватывает много элементов, и несколько объектов jQuery совместно используют ссылки на одни и те же элементы?)
Я подумал о том, чтобы сохранить контекстную переменную "текущая поездка" и использовать ее для навигации от пункта назначения до содержащей его поездки. Это не так надежно, как фактический "выборочный контекст". На самом деле, это совершенно другая тактика, и я не уверен, что мне это нравится.
Я помню, что у меня была такая же проблема с ActiveRecord (хотя с тех пор, как я с ней работал, прошло довольно много времени). В AR, если я начинал с RoadTrip1, а затем выбирал его пункты назначения, я не мог очень хорошо перемещаться от пункта назначения обратно до поездки (через некоторый контекст выборки). Вместо этого мне нужно было бы рассмотреть обоих родителей (поездки) и не иметь никаких указаний на то, как я туда попал. Правильно?
Другие сталкивались с этой проблемой раньше - то есть, желали бы перейти назад, где обратная навигация путается многими родителями? Вы когда-нибудь спрашивали "от какого родителя я приехал сюда?" Как ты ответил на это?
2 ответа
Мне удалось выработать решение, которое я получил после использования шаблона прокси. Что я действительно хотел, так это концепция "извлечения контекста". Я хотел знать, от какого родителя я первоначально выбрал модель (ребенок, имеющий несколько родителей).
Ключ к решению проблемы заключался в том, что поддержание извлекаемого контекста было ответственностью наших объектов запросов, а не наших моделей.
var activity = roadtrip.destinations().all().activities().first();
Начнем с roadtrip
модель и вызов destinations
функция. Эта функция возвращает объект запроса. Этот объект запроса похож по дизайну на реализацию Rails в Rails в том смысле, что он ленив, фактически не возвращает никаких записей, пока вы не вызовете all
, first
, each
и т. д. Объект запроса имеет context
переменная, которая указывает на его родителя - roadtrip
модель.
all
вызов возвращает объект коллекции, чей context
указывает на запрос, из которого он был вызван. Каждый элемент в коллекции является прокси (queried item
) который оборачивает каждый базовый destination
модель. Прокси поддерживает ссылку на свою коллекцию. Самый простой способ получить этот прокси-сервер:
var proxied_destination = Object.create(destination);
Таким образом, вы можете назначить context
на прокси, не влияя на оригинал.
proxied_destination.context = collection;
Это позволяет "основной" модели оставаться нетронутой и, таким образом, отображаться на личности. Это было бы невозможно, если бы наша модель поддерживала прямую ссылку на свою коллекцию, поскольку модель может принимать участие в нескольких результирующих наборах (мы можем выполнить столько запросов, сколько захотим), и в моем сценарии мы ожидаем только один контекст (родительский).
Мы называем activities
который предоставляет нам другой объект запроса, чей контекст является прокси destination
, Мы называем first
и вместо того, чтобы получить коллекцию, мы получаем прокси activity
имеющий контекст, который указывает на объект запроса действий.
Таким образом, использование прокси-серверов позволяет нам указывать и, таким образом, "подниматься" по иерархии объектов, в то же время поддерживая модели с отображением идентичности, которые не обращают внимания на эту иерархию и могут легко принимать участие в нескольких коллекциях (наборах результатов).
У вас должно быть 3 класса: Roadtrip, Destination и Place. Таким образом, ваши пункты назначения A и B - это два разных объекта, которые относятся к одному и тому же месту.