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

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

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

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

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

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

12.08.2008 21:06:38
9 ОТВЕТОВ
РЕШЕНИЕ

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

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

Например

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 для одного проекта, и, хотя изначально у него довольно крутая кривая обучения, инвестиции, которые вы вложили раньше, окупаются.

7
13.08.2008 06:08:24

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

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

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

0
12.08.2008 21:21:06

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

public class BusinessObjectRecord : BusinessObject
{
}
0
12.08.2008 21:18:44

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

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

namespace Data
{
    public class BusinessObjectDataManager
    {
         public void SaveObject(BusinessObject object)
         {
                // Exec stored procedure
         {
    }
}
0
12.08.2008 21:28:05

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

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

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

5
17.08.2008 15:08:55

@ Ice ^^ тепла:

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

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

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

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

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

1
12.08.2008 21:34:47

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

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

public class BusinessLayer : ISimpleBusiness
{}

public class Some3LayerObject : ISimpleSome3LayerObject
{}
0
12.08.2008 21:52:01

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

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

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

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.08.2008 01:12:54

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

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

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

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

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

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

0
13.08.2008 22:45:03
Я полагаю, это может сработать, если все, что вы делаете, это помещаете экран поверх таблицы базы данных.
JC. 1.10.2008 20:45:50
Нет, я не говорю о приложениях, которые так просты. Я говорю о написании крупнозернистых процедур для обработки всей вашей бизнес-логики в сложных корпоративных приложениях, а также о том, чтобы код, вызывающий процессы, был максимально простым.
Eric Z Beard 1.10.2008 21:08:56
Я не понимаю, как перемещение бизнес-логики в хранимые процедуры помогает. Я бы предпочел держать их на хорошем бизнес-уровне на языке, который поддерживает хорошее модульное тестирование.
Ben Fulton 16.10.2008 19:50:22