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

StackOverflow https://stackoverflow.com/questions/9240

  •  08-06-2019
  •  | 
  •  

Вопрос

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

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

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

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

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

Это было полезно?

Решение

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

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

Например,

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
        // ...
    }
}

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

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

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

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

Другие советы

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

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

ps название вопроса немного вводит в заблуждение

@Лед^^Тепло:

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

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

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

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

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

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

public class BusinessObjectRecord : BusinessObject
{
}

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

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

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

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

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

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

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

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

public class BusinessLayer : ISimpleBusiness
{}

public class Some3LayerObject : ISimpleSome3LayerObject
{}

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top