Я нарушаю практику ООП с этой архитектурой?

23

У меня есть веб-приложение. Я не верю, что технология важна. Структура представляет собой N-уровневое приложение, показанное на рисунке слева. Есть 3 слоя.

UI (шаблон MVC), уровень бизнес-логики (BLL) и уровень доступа к данным (DAL)

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

Типичный поток через приложение может быть:

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

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

введите описание изображения здесь

Версия справа - мое усилие.

Логика по-прежнему заключается в том, как приложение перемещается между пользовательским интерфейсом и DAL, но, скорее всего, свойств нет ... Только методы (большинство классов на этом уровне могут быть статическими, поскольку они не хранят никакого состояния). Слой Poco - это место, где существуют классы, которые имеют свойства (например, класс Person, в котором есть имя, возраст, рост и т. Д.). Они не имеют ничего общего с потоком приложения, они только хранят состояние.

Поток может быть:

Даже запускается из пользовательского интерфейса и передает некоторые данные в контроллер уровня пользовательского интерфейса (MVC). Это преобразует необработанные данные и преобразует их в модель Poco. Затем модель poco передается на уровень логики (который был BLL) и, в конце концов, на уровень командных запросов, которые потенциально могут быть обработаны в пути. Уровень командных запросов преобразует POCO в объект базы данных (это почти одно и то же, но один предназначен для персистентности, другой - для внешнего интерфейса). Элемент сохраняется, и объект базы данных возвращается на уровень командного запроса. Затем он преобразуется в POCO, где он возвращается на уровень логики, потенциально обрабатывается дальше и, наконец, возвращается к пользовательскому интерфейсу.

Общая логика и интерфейсы - это место, где у нас могут быть постоянные данные, такие как MaxNumberOf_X и TotalAllowed_X и все интерфейсы.

И общая логика / интерфейсы, и DAL являются «основой» архитектуры. Они ничего не знают о внешнем мире.

Все знают о poco кроме общей логики / интерфейсов и DAL.

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

Примером демонстрации Logic и Poco может быть:

public class LogicClass
{
    private ICommandQueryObject cmdQuery;
    public PocoA Method1(PocoB pocoB) 
    { 
        return cmdQuery.Save(pocoB); 
    }

    /*This has no state objects, only ways to communicate with other 
    layers such as the cmdQuery. Everything else is just function 
    calls to allow flow via the program */
    public PocoA Method2(PocoB pocoB) 
    {         
        pocoB.UpdateState("world"); 
        return Method1(pocoB);
    }

}

public struct PocoX
{
     public string DataA {get;set;}
     public int DataB {get;set;}
     public int DataC {get;set;}

    /*This simply returns something that is part of this class. 
     Everything is self-contained to this class. It doesn't call 
     trying to directly communicate with databases etc*/
     public int GetValue()
     {

         return DataB * DataC; 
     }

     /*This simply sets something that is part of this class. 
     Everything is self-contained to this class. 
     It doesn't call trying to directly communicate with databases etc*/
     public void UpdateState(string input)
     {        
         DataA += input;  
     }
}
MyDaftQuestions
источник
Я не вижу ничего принципиально неправильного в вашей архитектуре, как вы сейчас ее описали.
Роберт Харви
19
В вашем примере кода недостаточно функциональных деталей, чтобы обеспечить дальнейшее понимание. Примеры Foobar редко дают достаточную иллюстрацию.
Роберт Харви
1
Представлено вашему вниманию
Theraot
4
Можно ли найти лучшее название для этого вопроса , так что можно найти в Интернете легко?
Soner Gönül
1
Просто чтобы быть педантичным: слой и слой - это не одно и то же. «Ярус» говорит о развертывании, «слой» о логике. Ваш уровень данных будет развернут как на уровне кода на стороне сервера, так и на уровне базы данных. Ваш уровень пользовательского интерфейса будет развернут как на уровне кода веб-клиента, так и на уровне сервера. Архитектура, которую вы показываете, является трехслойной. Ваши уровни: «Веб-клиент», «Код на стороне сервера» и «База данных».
Лоран LA RIZZA

Ответы:

54

Да, вы, скорее всего, нарушаете основные концепции ООП. Однако не чувствуйте себя плохо, люди делают это все время, это не значит, что ваша архитектура «неправильна». Я бы сказал, что это, вероятно, менее ремонтопригодно, чем правильный дизайн ОО, но это скорее субъективно и не ваш вопрос в любом случае. ( Вот моя статья с критикой n-уровневой архитектуры в целом).

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

Конечно, всегда есть исключения, но этот дизайн, как правило, нарушает эти вещи.

Опять же, я хотел бы подчеркнуть, что «нарушает ООП»! = «Неправильно», так что это не обязательно является оценочным суждением. Все зависит от ваших архитектурных ограничений, сценариев использования, требований и т. Д.

Роберт Бройтигам
источник
9
Поднимите голос, это хороший ответ, если бы я писал свой собственный, я бы скопировал и вставил это, но также добавил, что, если вы обнаружите, что не пишете код ООП, возможно, вам следует рассмотреть язык, не являющийся ООП, поставляется с большим количеством дополнительных издержек, без которых вы можете обойтись, если не используете их
TheCatWhisperer
2
@TheCatWhisperer: Современные корпоративные архитектуры не отбрасывают ООП полностью, а только выборочно (например, для DTO).
Роберт Харви
@RobertHarvey Согласен, я имел в виду, если вы не используете ООП почти нигде в своем дизайне
TheCatWhisperer
@TheCatWhisperer Многие из преимуществ в oop, подобных c #, не обязательно в oop части языка, но в доступной поддержке, такой как библиотеки, Visual Studio, управление памятью и т. Д.
@Orangesandlemons Я уверен, что есть много других хорошо поддерживаемых языков ...
TheCatWhisperer
31

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

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

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

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

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

В вашем POCO, кажется, есть бизнес-логика, и я не буду слишком беспокоиться о анемии. Что меня беспокоит, так это то, что все они кажутся очень изменчивыми. Помните, что геттеры и сеттеры не обеспечивают реальной инкапсуляции. Если ваш POCO движется к этой границе, тогда хорошо. Просто поймите, что это не дает вам всех преимуществ реального инкапсулированного объекта ООП. Некоторые называют это объектом передачи данных или DTO.

Уловка, которую я успешно использовал, заключается в создании объектов ООП, которые питаются DTO. Я использую DTO в качестве объекта параметра . Мой конструктор читает из него состояние (читается как защитная копия ) и отбрасывает его в сторону. Теперь у меня есть полностью инкапсулированная и неизменная версия DTO. Все методы, связанные с этими данными, могут быть перемещены сюда, если они находятся на этой стороне этой границы.

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

Теперь, в конце концов, кое-что, где-нибудь, столкнется с другой границей, и все это снова развалится. Отлично. Раскрути еще один DTO и брось его через стену.

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

candied_orange
источник
5
« геттеры и сеттеры не обеспечивают реальной инкапсуляции » - да!
Борис Паук
3
@BoristheSpider - методы получения и установки абсолютно обеспечивают инкапсуляцию, они просто не соответствуют вашему узкому определению инкапсуляции.
Давор Адрало
4
@ DavorŽdralo: они иногда полезны в качестве обходного пути, но по своей природе геттеры и сеттеры нарушают инкапсуляцию. Предоставление способа получить и установить некоторую внутреннюю переменную - это противоположность ответственности за ваше собственное состояние и за его действия.
cHao
5
@cHao - ты не понимаешь, что такое добытчик. Это не означает метод, который возвращает значение свойства объекта. Это обычная реализация, но она может возвращать значение из базы данных, запрашивать его по http, вычислять на лету, что угодно. Как я уже сказал, геттеры и сеттеры нарушают инкапсуляцию только тогда, когда люди используют свои собственные узкие (и неправильные) определения.
Давор Адрало
4
@cHao - инкапсуляция означает, что вы скрываете реализацию. Это то, что инкапсулируется. Если у вас есть метод getSurfaceArea () для класса Square, вы не знаете, является ли площадь поверхности полем, рассчитывается ли она на лету (возвращаемая высота * ширина) или каким-либо третьим методом, поэтому вы можете изменить внутреннюю реализацию в любое время, потому что это инкапсулировано.
Давор Адрало
1

Если я правильно прочитал ваше объяснение, ваши объекты выглядят примерно так: (сложно без контекста)

public class LogicClass
{
    private ICommandQueryObject cmdQuery;
    public PocoA Method(PocoB pocoB) { ... }
}

public class PocoX
{
     public string DataA {get;set;}
     public int DataB {get;set;}
     ... etc
}

Так как ваши классы Poco содержат только данные, а ваши классы логики содержат методы, которые воздействуют на эти данные; да, вы нарушили принципы "Классик ООП"

Опять же, по вашему обобщенному описанию трудно сказать, но я бы рискнул, чтобы то, что вы написали, могло быть классифицировано как Anemic Domain Model.

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

Однако, когда вы ссылаетесь на Shared Logic, Pocos, которые почти одинаковы, и на статику, я начинаю беспокоиться о деталях вашего дизайна.

Ewan
источник
Я добавил в свой пост, по сути, копирую ваш пример. Извините, что не было ясно, с чего начать
MyDaftQuestions
1
Я имею в виду, что если бы вы сказали нам, что делает приложение, было бы легче писать примеры. Вместо LogicClass вы можете иметь PaymentProvider или что-то еще
Ewan
1

Одна потенциальная проблема, которую я видел в вашем дизайне (и она очень распространена) - некоторые из самых худших «ОО» -кодов, с которыми я когда-либо сталкивался, были вызваны архитектурой, которая отделяла объекты «Данные» от объектов «Код». Это вещи уровня кошмара! Проблема в том, что везде в вашем бизнес-коде, когда вы хотите получить доступ к своим объектам данных, вы склоняетесь к тому, чтобы просто кодировать их прямо в строке (вам не нужно, вы можете создать служебный класс или другую функцию для его обработки, но это то, что Я видел, как это происходило неоднократно с течением времени).

Код доступа / обновления, как правило, не собирается, поэтому у вас везде есть дублирующиеся функции.

С другой стороны, эти объекты данных полезны, например, для сохранения базы данных. Я пробовал три решения:

Копирование значений в и из «реальных» объектов и отбрасывание вашего объекта данных утомительно (но может быть правильным решением, если вы хотите пойти по этому пути).

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

Лучшее решение для меня - это концепция класса Wrapper, который инкапсулирует класс «Data» и содержит все функции обработки данных - тогда я вообще не раскрываю класс данных (даже сеттеры и геттеры) если они абсолютно не нужны). Это устраняет искушение напрямую манипулировать объектом и вынуждает вас добавлять общие функции к оболочке.

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

// Data Class
Class User {
    String name;
    Date birthday;
}

Class UserHolder {
    final private User myUser // Cannot be null or invalid

    // Quickly wrap an object after getting it from the DB
    public UserHolder(User me)
    {
        if(me == null ||me.name == null || me.age < 0)
            throw Exception
        myUser=me
    }

    // Create a new instance in code
    public UserHolder(String name, Date birthday) {
        User me=new User()
        me.name=name
        me.birthday=birthday        
        this(me)
    }
    // Methods access attributes, they try not to return them directly.
    public boolean canDrink(State state) {
        return myUser.birthday.year < Date.yearsAgo(state.drinkingAge) 
    }
}

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

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

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

Билл К
источник
1

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

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

gnasher729
источник
0

Категория «ООП» намного больше и более абстрактна, чем вы описываете. Это не заботится обо всем этом. Он заботится о четкой ответственности, сплоченности, сцеплении. Таким образом, на уровне, который вы спрашиваете, не имеет смысла спрашивать о «OOPS Practice».

Тем не менее, к вашему примеру:

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

  • Модель - содержит бизнес-данные + логика
    • Уровень данных как деталь реализации модели
  • Вид - код пользовательского интерфейса, шаблоны HTML, CSS и т. Д.
    • Включает в себя аспекты на стороне клиента, такие как JavaScript, или библиотеки для «одностраничных» веб-приложений и т. Д.
  • Контроль - клей на стороне сервера между всеми остальными частями
  • (Существуют такие расширения, как ViewModel, Batch и т. Д., В которые я не буду вдаваться, здесь)

Здесь есть несколько чрезвычайно важных базовых допущений:

  • Класс / объекты Model никогда не имеют никаких знаний о других частях (View, Control, ...). Он никогда не вызывает их, он не предполагает их вызова, он не получает никаких атрибутов / параметров sesssion или чего-либо еще по этой линии. Это совершенно один. На языках, которые поддерживают это (например, Ruby), вы можете запустить командную строку вручную, создать экземпляры классов Model, работать с ними в соответствии с вашими пожеланиями и делать все, что они делают, без какого-либо экземпляра Control или View или любой другой категории. Он не знает о сессиях, пользователях и т. Д., Самое главное.
  • Ничто не касается слоя данных, кроме как через модель.
  • Представление имеет только легкое касание модели (отображение материала и т. Д.) И ничего больше. (Обратите внимание, что хорошим расширением является «ViewModel», который представляет собой специальные классы, которые выполняют более существенную обработку для сложной визуализации данных, которая не очень хорошо вписывается ни в Model, ни в View - это хороший кандидат для удаления / предотвращения раздувания в Чистая модель).
  • Управление настолько легкое, насколько это возможно, но оно отвечает за сбор всех остальных игроков и передачу материалов между ними (т. Е. Извлечение пользовательских записей из формы и перенаправление их в модель, перенаправление исключений из бизнес-логики в полезную сообщения об ошибках для пользователя и т. д.). Для Web / HTTP / REST API и т. Д. Все авторизация, безопасность, управление сеансами, управление пользователями и т. Д. Происходят здесь (и только здесь).

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

Обратите внимание, что «толстые модели» означают, что вся бизнес-логика находится в категории «Модель» (пакет, модуль, независимо от названия на выбранном вами языке). Очевидно, что отдельные классы должны быть хорошо структурированы по ООП в соответствии с любыми указаниями по кодированию, которые вы сами себе даете (т. Е. Некоторые максимальные строки кода на класс или метод и т. Д.).

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

Возвращаясь к вашему вопросу: мне кажется, что между вашей новой архитектурой и схемой MVC, которую я описал, существует большое совпадение, так что вы не ошибаетесь, но вы, похоже, тоже изобретаете что-то новое, или используя его, потому что ваша текущая среда программирования / библиотеки предлагают такое. Трудно сказать за меня. Поэтому я не могу дать вам точный ответ о том, что вы намереваетесь, особенно хорошо или плохо. Вы можете узнать, проверив, имеет ли каждая «вещь» только один класс, ответственный за нее; все ли сплоченно и слабо спарено. Это дает вам хорошее представление и, на мой взгляд, достаточно для хорошего ООП-дизайна (или хорошего эталона того же, если хотите).

Anoe
источник