Интерфейсы на разных логических уровнях

скажем, у вас есть приложение, разделенное на 3 уровня: GUI, бизнес-логика и доступ к данным. На уровне бизнес-логики вы описали свои бизнес-объекты: геттеры, сеттеры, аксессоры и так далее... вы поняли идею. Интерфейс уровня бизнес-логики гарантирует безопасное использование бизнес-логики, поэтому все вызываемые методы и методы доступа будут проверять ввод.

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

но вот сложная часть, когда вы начинаете писать уровень доступа к данным, интерфейс к бизнес-логике не соответствует вашим потребностям. Вам нужно иметь больше аксессоров и геттеров для установки полей, которые/используются для скрытия. Теперь вы вынуждены подорвать интерфейс вашей бизнес-логики; теперь можно задать поля из уровня пользовательского интерфейса,который не имеет бизнес-настроек.

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

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

9 ответов


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

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

Е. Г

public class User
{
    private string name;
    private AccountStatus status;

    private User()
    {
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public AccountStatus Status
    {
        get { return status; }
    }

    public void Activate()
    {
        status = AccountStatus.Active;
    }

    public void Suspend()
    {
        status = AccountStatus.Suspended;
    }

    public static User GetById(int id)
    {
        User fetchedUser = new User();

        // Lots of database and error-checking code
        // omitted for clarity
        // ...

        fetchedUser.name = (string) reader["Name"];
        fetchedUser.status = (int)reader["statusCode"] == 0 ? AccountStatus.Suspended : AccountStatus.Active;

        return fetchedUser;
    }

    public static void Save(User user)
    {
        // Code to save User's internal structure to database
        // ...
    }
}

в этом примере у нас есть объект, представляющий пользователя с именем и AccountStatus. Мы не хотим, чтобы статус устанавливался напрямую, возможно, потому, что мы хотим проверить, что изменение является допустимым переходом статуса, поэтому у нас нет сеттера. К счастью, код сопоставления в методах GetById и Save static имеет полный доступ к полям имени и состояния объекта.

второй вариант-это второй класс, который отвечает за сопоставление. Это имеет то преимущество, разделяющей различные проблемы бизнес-логику и настойчивость, которые может позволить ваш дизайн, чтобы быть более проверяемым и гибкий. Этот проблема с этим методом заключается в том, как предоставить поля name и status внешнему классу. Некоторые варианты: 1. Используйте отражение (которое не имеет никаких угрызений совести о копании глубоко в личные части вашего объекта) 2. Предоставьте специально названные общедоступные сеттеры (например, префикс их словом "Private") и надейтесь, что никто не использует их случайно 3. Если ваш язык поддерживает его, сделайте сеттеры внутренними, но предоставьте доступ к модулю сопоставления данных. Е. Г. использовать атрибут internalsvisibletoattribute в .Объем 2.0 года или функции friend в C++

для получения дополнительной информации я бы рекомендовал классическую книгу Мартина Фаулера "Шаблоны корпоративной архитектуры"

однако, в качестве предупреждения, прежде чем идти по пути написания собственных картографов, я настоятельно рекомендую использовать сторонний инструмент реляционного картографирования объектов (ORM), такой как nHibernate или Entity Framework Microsoft. Я работал над четырьмя различными проектами, где по разным причинам мы написали свой собственный картограф, и это очень легко тратить много времени на поддержание и расширение картографа вместо написания кода, который предоставляет значение конечного пользователя. До сих пор я использовал nHibernate в одном проекте, и, хотя изначально он имеет довольно крутую кривую обучения, инвестиции, которые вы вкладываете в начале, значительно окупаются.


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

то, что я делаю, это использовать


Я всегда создаю отдельную сборку, которая содержит:

  • много небольших интерфейсов (думаю, ICreateRepository, IReadRepository, IReadListRepsitory.. список продолжается, и большинство из них сильно полагается на дженерики)
  • много конкретных интерфейсов, таких как IPersonRepository, который наследуется от IReadRepository, вы понимаете..
    Все, что вы не можете описать только с меньшими интерфейсами, вы помещаете в конкретный интерфейс.
    Пока вы используете IPersonRepository для объявления вашего объекта, вы получаете чистый, согласованный интерфейс для работы. Но Кикер, вы также можете сделать класс, который принимает f.X. ICreateRepository в своем конструкторе, поэтому код в конечном итоге будет очень легко сделать некоторые действительно фанки. Здесь также есть интерфейсы для служб бизнес-уровня.
  • наконец, я вставляю все объекты домена в дополнительную сборку, просто чтобы сделать базу кода немного чище и более свободно соединенный. Эти объекты не имеют никакой логики, они просто общий способ описания данных для всех 3 + слоев.

кстати. Почему вы определяете методы на уровне бизнес-логики для размещения уровня данных?
У уровня данных не должно быть причин даже знать о существовании бизнес-уровня..


@Ice^^Heat:

Что вы подразумеваете под тем, что уровень данных не должен знать уровень бизнес-логики? Как бы вы заполнили бизнес-объект данными?

пользовательский интерфейс запрашивает ServiceClass на бизнес-уровне для службы, а именно получение списка объектов, отфильтрованных объектом с необходимыми данными параметров.
Затем ServiceClass создает экземпляр одного из классов репозитория на уровне данных и вызывает Метод getlist(фильтров значение parametertype).
Затем уровень данных обращается к базе данных, извлекает данные и сопоставляет их с общим форматом, определенным в сборке "домен".
BL больше не работает с этими данными, поэтому он выводит их в пользовательский интерфейс.

затем пользовательский интерфейс хочет изменить элемент X. Он отправляет элемент (или бизнес-объект) службе на бизнес-уровне. Бизнес-уровень проверяет объект и, если он в порядке, отправляет его на уровень данных для хранения.

в UI знает службу на бизнес-уровне, которая снова знает об уровне данных.

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


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

public class BusinessObjectRecord : BusinessObject
{
}

что вы подразумеваете под тем, что уровень данных не должен знать уровень бизнес-логики? Как бы вы заполнили бизнес-объект данными?

Я часто так делаю:

namespace Data
{
    public class BusinessObjectDataManager
    {
         public void SaveObject(BusinessObject object)
         {
                // Exec stored procedure
         {
    }
}

Итак, проблема в том, что бизнес-слой должен предоставлять больше функциональности слою данных, а добавление этой функциональности означает слишком большое воздействие на уровень пользовательского интерфейса? Если я правильно понимаю вашу проблему, похоже, вы пытаетесь удовлетворить слишком много с помощью одного интерфейса, и это просто заставляет его загромождаться. Почему бы не иметь два интерфейса на бизнес-уровне? Одним из них будет простой и безопасный интерфейс для уровня UI. Другой был бы более низким уровнем интерфейс для слоя данных.

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

public class BusinessLayer : ISimpleBusiness
{}

public class Some3LayerObject : ISimpleSome3LayerObject
{}

вы можете разделить свои интерфейсы на два типа, а именно:

  • view interfaces -- которые являются интерфейсами, которые определяют ваши взаимодействия с вашим пользовательским интерфейсом и
  • интерфейсы данных -- которые являются интерфейсами, которые позволят вам указать взаимодействия с вашими данными

можно наследовать и реализовать оба набора интерфейсов таким образом, что:

public class BusinessObject : IView, IData

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

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

public class BusinessObject : DomainObject

public class ViewManager<T> where T : DomainObject

public class DataManager<T> where T : DomainObject

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


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

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

запись хранимых процедур для инкапсуляции данных. Используйте наборы результатов, DataSet, DataTable, SqlCommand (или эквивалент java/php/whatever) по мере необходимости из кода для взаимодействия с базой данных. Тебе не нужны эти предметы. Отличным примером является внедрение SqlDataSource в a .страница ASPX.

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

объектно-реляционные картографы-это дьявол. Прекратите их использовать.

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