Интерфейсы на разных логических уровнях - PullRequest
12 голосов
/ 13 августа 2008

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

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

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

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

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

Ответы [ 9 ]

6 голосов
/ 13 августа 2008

Если я правильно понял вопрос, вы создали модель предметной области и хотели бы написать объектно-реляционное сопоставление для сопоставления записей в вашей базе данных и объектов вашего домена. Тем не менее, вы обеспокоены загрязнением вашей доменной модели с помощью «слесарного» кода, который был бы необходим для чтения и записи в поля вашего объекта.

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

* 1005 например *

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 имеет полный доступ к полям имени и состояния объекта.

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

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

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

5 голосов
/ 13 августа 2008

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

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

ps название вопроса немного ошибочно

1 голос
/ 13 августа 2008

@ Ice ^^ тепла:

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

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

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

Пользовательский интерфейс знает сервис на бизнес-уровне, который снова знает об уровне данных.

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

0 голосов
/ 14 августа 2008

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

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

Напишите хранимые процедуры для инкапсуляции ваших данных. При необходимости используйте наборы результатов, DataSet, DataTable, SqlCommand (или java / php / любой эквивалент) из кода для взаимодействия с базой данных Вам не нужны эти объекты. Отличный пример - встраивание SqlDataSource в страницу .ASPX.

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

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

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

0 голосов
/ 13 августа 2008

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

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

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

public class BusinessObject : IView, IData

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

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

public class BusinessObject : DomainObject

public class ViewManager<T> where T : DomainObject

public class DataManager<T> where T : DomainObject

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

0 голосов
/ 13 августа 2008

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

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

public class BusinessLayer : ISimpleBusiness
{}

public class Some3LayerObject : ISimpleSome3LayerObject
{}
0 голосов
/ 13 августа 2008

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

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

namespace Data
{
    public class BusinessObjectDataManager
    {
         public void SaveObject(BusinessObject object)
         {
                // Exec stored procedure
         {
    }
}
0 голосов
/ 13 августа 2008

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

public class BusinessObjectRecord : BusinessObject
{
}
0 голосов
/ 13 августа 2008

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

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

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

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