DDD - правило, согласно которому сущности не могут получить прямой доступ к хранилищам - PullRequest
156 голосов
/ 17 апреля 2011

В домене, управляемом дизайном, кажется, лоты из соглашения , что сущности не должны иметь прямой доступ к репозиториям.

Это из книги Эрика Эванса Дизайн, управляемой доменом , или откуда-то еще?

Где за этим есть хорошие объяснения?

edit: уточнить: я не говорю о классической ОО практике разделения доступа к данным на отдельный уровень от бизнес-логики - я говорю о конкретной договоренности, согласно которой в DDD сущности не должнывообще говорить со слоем доступа к данным (то есть они не должны содержать ссылки на объекты репозитория)

обновление: я дал награду BacceSR, потому что его ответ казался самым близким, но я все еще довольно в темнотеоб этом.Если это такой важный принцип, должны быть где-то хорошие статьи об этом где-то онлайн, не так ли?

обновление: март 2013 года, голосование по этому вопросу подразумевает, что в этом есть большой интерес, и хотя он былмного ответов, я все еще думаю, что есть место для большего, если у людей есть идеи по этому поводу.

Ответы [ 12 ]

43 голосов
/ 22 апреля 2011

Здесь есть некоторая путаница. Хранилища имеют доступ к совокупным корням. Совокупные корни являются сущностями. Причиной этого является разделение проблем и хорошее наслоение. Это не имеет смысла для небольших проектов, но если вы работаете в большой команде, вы хотите сказать: «Вы получаете доступ к продукту через репозиторий продуктов. Продукт - это совокупный корень для коллекции объектов, включая объект ProductCatalog. Если вы хотите обновить ProductCatalog, вы должны пройти через ProductRepository. "

Таким образом, вы получаете очень и очень четкое разделение бизнес-логики и того, где все обновляется. У вас нет ребенка, который сам по себе и пишет всю эту программу, которая делает все эти сложные вещи в каталоге продуктов, и когда дело доходит до интеграции его в основной проект, вы сидите и смотрите на это и понимаете, что все должно быть угроблено. Это также означает, что когда люди присоединяются к команде, добавляют новые функции, они знают, куда идти и как структурировать программу.

Но подожди! Репозиторий также относится к постоянному слою, как в шаблоне репозитория. В лучшем мире репозиторий Эрика Эванса и паттерн репозитория имели бы разные имена, потому что они имеют тенденцию частично перекрываться. Чтобы получить шаблон репозитория, у вас есть контраст с другими способами доступа к данным, с сервисной шиной или системой модели событий. Обычно, когда вы добираетесь до этого уровня, определение Эрик Эванса «Репозиторий» уходит на второй план, и вы начинаете говорить о ограниченном контексте. Каждый ограниченный контекст, по сути, является собственным приложением. У вас может быть сложная система одобрения для внесения вещей в каталог продукции. В вашем оригинальном дизайне продукт был центральной частью, но в этом ограниченном контексте каталог продукции. Вы по-прежнему можете получать доступ к информации о продукте и обновлять продукт через служебную шину, но вы должны понимать, что каталог продуктов вне ограниченного контекста может означать нечто совершенно иное.

Вернуться к исходному вопросу. Если вы обращаетесь к хранилищу из сущности, это означает, что сущность на самом деле не является бизнес-сущностью, а, вероятно, должна существовать на уровне сервисов. Это связано с тем, что сущности являются бизнес-объектами и должны быть максимально похожими на DSL (предметно-ориентированный язык). Только бизнес-информация в этом слое. Если вы устраняете проблему с производительностью, вам нужно искать в другом месте, так как здесь должна быть только деловая информация. Если вдруг у вас возникнут проблемы с приложением, вам будет очень трудно расширять и поддерживать приложение, что действительно является основой DDD: создание поддерживаемого программного обеспечения.

Ответ на комментарий 1 : Правильно, хороший вопрос. Таким образом, не проверка all происходит на уровне домена. Sharp имеет атрибут «DomainSignature», который делает то, что вы хотите. Это постоянная осведомленность, но будучи атрибутом, уровень домена остается чистым. Это гарантирует, что у вас нет повторяющегося объекта, в вашем примере с тем же именем.

Но давайте поговорим о более сложных правилах проверки. Допустим, вы Amazon.com. Вы когда-нибудь заказывали что-то с просроченной кредитной картой? У меня есть, где я не обновил карту и что-то купил. Он принимает заказ и пользовательский интерфейс сообщает мне, что все персиковое. Примерно через 15 минут я получу электронное письмо с сообщением о проблеме с моим заказом, моя кредитная карта недействительна. Здесь происходит то, что в идеале существует некоторая проверка регулярных выражений на уровне домена. Это правильный номер кредитной карты? Если да, сохраните порядок. Однако существует дополнительная проверка на уровне задач приложения, где запрашивается внешняя служба, чтобы узнать, можно ли произвести оплату по кредитной карте. Если нет, то на самом деле ничего не отправляйте, приостановите заказ и дождитесь клиента. Все это должно происходить на уровне сервиса.

Не бойтесь создавать объекты проверки на уровне службы, которые могут обращаться к репозиториям.Просто держите его вне доменного слоя.

29 голосов
/ 25 августа 2015

Сначала я был убежден, чтобы разрешить некоторым из моих сущностей доступ к репозиториям (т.е. отложенная загрузка без ORM). Позже я пришел к выводу, что не следует и что я могу найти альтернативные способы:

  1. Мы должны знать наши намерения в запросе и то, что мы хотим от домена, поэтому мы можем делать вызовы репозитория перед построением или вызовом Агрегированного поведения. Это также помогает избежать проблемы несовместимого состояния в памяти и необходимости отложенной загрузки (см. Эту статью ). Запах в том, что вы больше не можете создать экземпляр вашей сущности в памяти, не беспокоясь о доступе к данным.
  2. CQS (разделение командных запросов) может помочь уменьшить необходимость вызова хранилища для объектов в наших сущностях.
  3. Мы можем использовать спецификацию , чтобы инкапсулировать и сообщать о потребностях логики домена и передавать их в хранилище (сервис может организовать эти вещи для нас). Спецификация может исходить от объекта, который отвечает за поддержание этого инварианта. Репозиторий будет интерпретировать части спецификации в свою собственную реализацию запроса и применять правила из спецификации к результатам запроса. Это стремится сохранить доменную логику на уровне домена. Это также служит вездесущему языку и общению лучше. Представьте себе, что выражение «просроченная спецификация заказа» отличается от высказывания «порядок фильтрации из tbl_order, где place_at менее чем за 30 минут до sysdate» (см. Этот ответ ).
  4. Это усложняет рассуждения о поведении объектов, поскольку принцип единственной ответственности нарушается. Если вам нужно решить проблемы с хранением / постоянством, вы знаете, куда идти, а куда нет.
  5. Это исключает опасность предоставления объекту двунаправленного доступа к глобальному состоянию (через репозиторий и доменные службы). Вы также не хотите нарушать границы транзакции.

Вернон Вон в Красной книге «Реализация доменного дизайна» относится к этой проблеме в двух местах, которые мне известны (примечание: эта книга полностью одобрена Эвансом, как вы можете прочитать в предисловии). В главе 7 «Службы» он использует доменную службу и спецификацию, чтобы обойти необходимость в агрегате для использования репозитория и другого агрегата для определения того, прошел ли пользователь аутентификацию. Его цитируют так:

Как правило, мы должны стараться избегать использования репозиториев. (12) из ​​агрегатов, если это вообще возможно.

Вернон, Вон (2013-02-06). Реализация доменно-управляемого дизайна (Kindle Location 6089). Пирсон Образование. Kindle Edition.

А в главе 10 «Агрегаты» в разделе , озаглавленном «Навигация по модели» , он говорит (сразу после того, как он рекомендует использовать глобальные уникальные идентификаторы для ссылки на другие корни агрегатов):

Ссылка по идентификатору не полностью препятствует навигации по модель. Некоторые будут использовать репозиторий (12) из ​​агрегата для поиска. Этот метод называется моделью отсоединенного домена, и это на самом деле форма ленивой загрузки. Там по-другому рекомендуется подход, однако: используйте хранилище или службу домена (7) для поиска зависимые объекты перед вызовом Агрегированного поведения. Клиент Служба приложений может контролировать это, а затем отправить в агрегат:

Он показывает пример этого в коде:

public class ProductBacklogItemService ... { 

   ... 
   @Transactional 
   public void assignTeamMemberToTask( 
        String aTenantId, 
        String aBacklogItemId, 
        String aTaskId, 
        String aTeamMemberId) { 

        BacklogItem backlogItem = backlogItemRepository.backlogItemOfId( 
                                        new TenantId( aTenantId), 
                                        new BacklogItemId( aBacklogItemId)); 

        Team ofTeam = teamRepository.teamOfId( 
                                  backlogItem.tenantId(), 
                                  backlogItem.teamId());

        backlogItem.assignTeamMemberToTask( 
                  new TeamMemberId( aTeamMemberId), 
                  ofTeam,
                  new TaskId( aTaskId));
   } 
   ...
}     

Далее он также упомянул еще одно решение о том, как доменная служба может использоваться в методе совокупных команд вместе с double-dispatch . (Я не могу порекомендовать, насколько полезно читать его книгу. После того, как вы устали от бесконечного рытья в Интернете, раскошелитесь на заслуженные деньги и прочитайте книгу.)

Затем у меня было обсуждение с всегда любезным Марко Пиветтой @ Ocramius , который показал мне немного кода при извлечении спецификации из домена и использовании этого:

1) Это не рекомендуется:

$user->mountFriends(); // <-- has a repository call inside that loads friends? 

2) В доменном сервисе это хорошо:

public function mountYourFriends(MountFriendsCommand $mount) { /* see http://store.steampowered.com/app/296470/ */ 
    $user = $this->users->get($mount->userId()); 
    $friends = $this->users->findBySpecification($user->getFriendsSpecification()); 
    array_map([$user, 'mount'], $friends); 
}
27 голосов
/ 18 апреля 2011

Это очень хороший вопрос. Я с нетерпением жду некоторого обсуждения по этому поводу. Но я думаю, что это упомянуто в нескольких книгах DDD и Джимми Нильссона и Эрика Эванса. Я думаю, это также видно из примеров, как использовать шаблон хранилища.

НО давайте обсудим. Я думаю, что очень правильная мысль, почему сущность должна знать о том, как сохранить другую сущность? Важным для DDD является то, что каждая сущность несет ответственность за управление своей собственной «сферой знаний» и не должна ничего знать о том, как читать или писать другие сущности. Конечно, вы, вероятно, можете просто добавить интерфейс хранилища в Entity A для чтения Entities B. Но есть риск, что вы предоставите знания о том, как сохранить B. Будет ли объект A также выполнять проверку на B перед сохранением B в db?

Как вы можете видеть, сущность A может более активно участвовать в жизненном цикле сущности B, и это может усложнить модель.

Полагаю (без какого-либо примера), что юнит-тестирование будет более сложным.

Но я уверен, что всегда будут сценарии, когда вы склонны использовать репозитории через сущности. Вы должны посмотреть на каждый сценарий, чтобы принять правильное решение. Плюсы и минусы. Но решение для репозитория-сущности, на мой взгляд, начинается с множества минусов. Это должно быть особый сценарий с Профи, который уравновешивает Минусы ....

12 голосов
/ 27 апреля 2011

Зачем выделять доступ к данным?

Мне кажется, что первые две страницы главы «Модель на основе проектирования» дают некоторое обоснование того, почему вы хотите абстрагироваться от деталей технической реализации от реализации домена.модель.

  • Вы хотите сохранить тесную связь между моделью домена и кодом
  • Разделение технических проблем помогает доказать, что модель практична для реализации
  • Вы хотитеповсеместно распространенный язык, пронизывающий проект системы

Это, кажется, все для того, чтобы избежать отдельной "модели анализа", которая отделена от фактической реализации системы.

Из того, что я понимаю в книге, говорится, что эта "модель анализа" может в конечном итоге быть разработана без учета программной реализации.Как только разработчики пытаются реализовать модель, понятную бизнес-стороне, они по необходимости создают свои собственные абстракции, создавая стенку в общении и понимании.

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

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

Цитата:

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

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

Если для доступа к базе данных нужны такие вещи, как проверка уникальности, взгляните на:

Уди Даан: самые большие ошибки, которые команды совершают при применении DDD

http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/

в разделе «Все правила не созданы равными»

и

Использование шаблона модели домена

http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119

в разделе «Сценарии неиспользования модели предметной области», который затрагивает одну и ту же тему.

Как выделить доступ к данным

Загрузка данных через интерфейс

ДанныеУровень доступа "был абстрагирован через интерфейс, который вы вызываете для получения необходимых данных:

var orderLines = OrderRepository.GetOrderLines(orderId);

foreach (var line in orderLines)
{
     total += line.Price;
}

Плюсы: Интерфейс отделяет код" доступа к данным ", позволяя вам по-прежнему писать тесты.Доступ к данным может обрабатываться в каждом конкретном случае, что обеспечивает лучшую производительность, чем общая стратегия.

Минусы: вызывающий код должен предполагать, что было загружено, а что нет.

Скажем, GetOrderLines возвращает объекты OrderLine с нулевым свойством ProductInfo по соображениям производительности.Разработчик должен иметь глубокие знания о коде интерфейса.

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

Теперь разделение задач должно позволить разработчику сосредоточиться на одном аспекте кода одновременно, насколько это возможно.Интерфейсная техника удаляет, КАК эти данные загружены, но НЕ КАК МНОГО загружаются данные, КОГДА они загружаются, и ГДЕ они загружаются.

Вывод: довольно низкое разделение!

Ленивая загрузка

Данные загружаются по требованию.Вызовы для загрузки данных скрыты внутри самого графа объектов, где доступ к свойству может привести к выполнению запроса sql перед возвратом результата.

foreach (var line in order.OrderLines)
{
    total += line.Price;
}

Плюсы: «КОГДА, ГДЕ и КАК» доступа к данным скрыты от разработчика, сосредоточенного на предметной логике.В агрегате нет кода, который занимается загрузкой данных.Количество загружаемых данных может быть точным количеством, требуемым кодом.

Минусы: Когда вы сталкиваетесь с проблемой производительности, это трудно исправить, если у вас есть универсальное решение «один размер подходит всем».Ленивая загрузка может привести к ухудшению производительности в целом, а реализация отложенной загрузки может быть сложной.классом агрегатов, что позволяет обрабатывать стратегии загрузки данных для каждого варианта использования.

Стратегия выборки может выглядеть следующим образом:

public class BillOrderFetchingStrategy : ILoadDataFor<IBillOrder, Order>
{
    Order Load(string aggregateId)
    {
        var order = new Order();

        order.Data = GetOrderLinesWithPrice(aggregateId);

        return order;
    }

}

Тогда ваш агрегат может выглядеть следующим образом:

public class Order : IBillOrder
{
    void BillOrder(BillOrderCommand command)
    {
        foreach (var line in this.Data.OrderLines)
        {
            total += line.Price;
        }

        etc...
    }
}

BillOrderFetchingStrategy используется для построения агрегата, а затем агрегат выполняет свою работу.

Плюсы: позволяет настраивать код для каждого варианта использования, обеспечивая оптимальную производительность.Соответствует принципу разделения интерфейса .Нет сложных требований к коду.Агрегатные модульные тесты не должны имитировать стратегию загрузки.Общая стратегия загрузки может использоваться в большинстве случаев (например, стратегия «загрузить все»), и при необходимости могут быть реализованы специальные стратегии загрузки.

Минусы: Разработчик все еще должен настроить / пересмотреть стратегию выборки после изменения кода домена.

При подходе стратегии извлечения вы все равно можете изменить пользовательский код извлечения для изменения бизнес-правил.Это не идеальное разделение проблем, но в итоге будет более ремонтопригодным и лучше, чем первый вариантСтратегия выборки инкапсулирует данные HOW, WHEN и WHERE.У него лучшее разделение проблем, без потери гибкости, так как один размер подходит для всех подходов с отложенной загрузкой.

11 голосов
/ 28 ноября 2012

Я обнаружил, что у этого блога достаточно веские аргументы против инкапсуляции репозиториев внутри сущностей:

http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities

9 голосов
/ 16 ноября 2016

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

Итак (рискуя написать что-то, с чем я не согласен через год), вот мои открытия.

Прежде всего, нам нравится модель обогащенного домена , которая дает нам высокую возможность обнаружения (того, что мы можем сделать с агрегатом) и читаемость ( выразительные вызовы методов).

// Entity
public class Invoice
{
    ...
    public void SetStatus(StatusCode statusCode, DateTime dateTime) { ... }
    public void CreateCreditNote(decimal amount) { ... }
    ...
}

Мы хотим достичь этого без внедрения каких-либо сервисов в конструктор объекта, потому что:

  • Введение нового поведения (которое использует новый сервис) может привести к изменению конструктора, то есть изменение влияет на каждую строку, в которой создается объект !
  • Эти сервисы не являются частью модели , но конструктор-инъекция предполагает, что они были.
  • Часто сервис (даже его интерфейс) - это деталь реализации, а не часть домена. Модель предметной области будет иметь внешнюю зависимость .
  • Может быть сбить с толку , почему сущность не может существовать без этих зависимостей. (Служба кредитных нот, говорите? Я даже не собираюсь ничего делать с кредитными нотами ...)
  • Было бы трудно создать экземпляр, таким образом трудно проверить .
  • Проблема распространяется легко, потому что другие сущности, содержащие эту, получат те же зависимости - которые на них могут выглядеть очень неестественными зависимостями .

Как тогда мы можем это сделать? Пока я пришел к выводу, что зависимости метода и двойная отправка обеспечивают достойное решение.

public class Invoice
{
    ...

    // Simple method injection
    public void SetStatus(IInvoiceLogger logger, StatusCode statusCode, DateTime dateTime)
    { ... }

    // Double dispatch
    public void CreateCreditNote(ICreditNoteService creditNoteService, decimal amount)
    {
        creditNoteService.CreateCreditNote(this, amount);
    }

    ...
}

CreateCreditNote() теперь требуется служба, которая отвечает за создание кредитных нот. Он использует двойную отправку , полностью , перекладывая работу на ответственную службу, в то время как поддерживает обнаружение от объекта Invoice.

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

Для последнего, чтобы упростить клиентский код, мы могли бы вместо этого войти через IInvoiceService. В конце концов, регистрация счетов кажется довольно неотъемлемой частью счета. Такой единый IInvoiceService помогает избежать потребности во всевозможных мини-сервисах для различных операций. Недостатком является то, что становится неясным, что именно эта служба будет делать . Это может даже начать выглядеть как двойная отправка, в то время как большая часть работы все еще выполняется в самом SetStatus().

Мы все еще можем назвать параметр «logger» в надежде раскрыть наши намерения. Хотя кажется немного слабым.

Вместо этого я бы предпочел запросить IInvoiceLogger (как мы уже делали в примере кода) и IInvoiceService реализовать этот интерфейс. Клиентский код может просто использовать свой единственный IInvoiceService для всех Invoice методов, которые запрашивают любой такой очень специфический, встроенный в счет-фактуру «мини-сервис», в то время как сигнатуры методов по-прежнему ясно дают понять, о чем они просят.

Я заметил, что я не обращался к репозиториям явно. Ну, регистратор является или использует хранилище, но позвольте мне также привести более явный пример. Мы можем использовать тот же подход, если хранилище требуется всего одним или двумя методами.

public class Invoice
{
    public IEnumerable<CreditNote> GetCreditNotes(ICreditNoteRepository repository)
    { ... }
}

На самом деле, это альтернатива вечно проблемным ленивым нагрузкам .

Обновление: я оставил текст ниже для исторических целей, но я рекомендую избегать ленивых нагрузок на 100%.

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

public class Invoice
{
    // Lazy could use an interface (for contravariance if nothing else), but I digress
    public Lazy<IEnumerable<CreditNote>> CreditNotes { get; }

    // Give me something that will provide my credit notes
    public Invoice(Func<Invoice, IEnumerable<CreditNote>> lazyCreditNotes)
    {
        this.CreditNotes = new Lazy<IEnumerable<CreditNotes>>() => lazyCreditNotes(this));
    }
}

С одной стороны, хранилище, которое загружает Invoice из базы данных, может иметь свободный доступ к функции, которая будет загружать соответствующие кредит-ноты и внедрять эту функцию в Invoice.

С другой стороны, код, который создает фактический новый Invoice, просто пропустит функцию, которая возвращает пустой список:

new Invoice(inv => new List<CreditNote>() as IEnumerable<CreditNote>)

(Обычай ILazy<out T> может избавить нас от уродливого приведения к IEnumerable, но это усложнит обсуждение.)

// Or just an empty IEnumerable
new Invoice(inv => IEnumerable.Empty<CreditNote>())

Буду рад услышать ваше мнение, предпочтения и улучшения!

2 голосов
/ 06 июля 2012

Мне кажется, что это общая хорошая практика, связанная с ООД, а не специфическая для DDD.

Причины, о которых я могу подумать:

  • Разделение интересов (сущности следует отделять от способа их сохранения. Поскольку может существовать несколько стратегий, в которых одна и та же сущность будет сохраняться в зависимости от сценария использования)
  • Логически, сущности могут быть видны на уровне ниже уровня, на котором работают репозитории. Компоненты более низкого уровня не должны обладать знаниями о компонентах более высокого уровня. Поэтому записи не должны иметь знаний о репозиториях.
1 голос
/ 21 августа 2017

просто Вернон Вон дает решение:

Используйте службу хранилища или домена для поиска зависимых объектов впереди вызывать совокупное поведение. Служба клиентских приложений может контролировать это.

1 голос
/ 27 апреля 2011

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

В конце концов, я добавил промежуточный слой, потому что мне пришлось перейти надругой сервер базы данных.Я видел / слышал об одном и том же сценарии несколько раз.

Я думаю, что отделение доступа к данным (также называемое «Репозиторий») от вашей бизнес-логики - это одна из тех вещей, которые были повторно изобретены несколько раз, хотя иДомен Driven Design книга, сделать его много "шума".

В настоящее время я использую 3 слоя (GUI, логика, доступ к данным), как многие разработчики, потому что это хорошая техника.

Разделение данных на слой Repository (он же Data Access layer) может рассматриваться как хорошая техника программирования, а не просто правило, которому нужно следовать.

Как и во многих методологиях, вы можете захотетьначните, НЕ реализовав, и в конце концов обновите вашу программу, как только вы их поймете.

Цитата: Илиада не была полностью изобретена Гомером, Кармина Бурана не была полностью изобретена Карлом Орффом, и в обоихслучаи, человек, который заставил других работать, все вместе, получил кредит; -)

0 голосов
/ 07 мая 2019

Цитируя Каролину Лилиенталь, «Шаблоны должны предотвращать циклы» https://www.youtube.com/watch?v=eJjadzMRQAk,, где она ссылается на циклические зависимости между классами. В случае хранилищ внутри агрегатов существует соблазн создать циклические зависимости из-за удобства навигации по объектам в качестве единственной причины. Шаблон, упомянутый выше prograhammer, рекомендованный Верноном Воном, где другие агрегаты ссылаются на идентификаторы вместо корневых экземпляров (есть ли название для этого шаблона?), Предлагает альтернативу, которая может привести к другим решениям.

Пример циклической зависимости между классами (признание):

(Time0): два класса, Sample и Well, ссылаются друг на друга (циклическая зависимость). Well относится к образцу, а Sample относится к лунке, из-за удобства (иногда зацикливание образцов, иногда зацикливание всех лунок в планшете). Я не мог представить случаи, когда Образец не ссылался бы на скважину, где он находится.

(Время 1): Год спустя, многие варианты использования были реализованы .... и теперь есть случаи, когда Образец не должен ссылаться на скважину, в которой он находится. В рабочем шаге есть временные пластины. Здесь скважина относится к образцу, который, в свою очередь, относится к скважине на другой пластине. Из-за этого странное поведение иногда возникает, когда кто-то пытается реализовать новые функции. Требуется время, чтобы проникнуть.

Мне также помогла упомянутая выше статья о негативных аспектах отложенной загрузки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...