Когда вы перестаете инкапсулировать? - PullRequest
5 голосов
/ 01 ноября 2008

У меня есть некоторый обработчик событий в граничном классе, который управляет механизмом сохранения для данной общей транзакции:

void MyBoundaryClass::MyEventHandler(...)
{
  //retrieve stuff from the UI
  //...
  //declare and initialize trasaction to persist
  SimpleTransaction myTransaction(.../*pass down stuff*/);
  //do some other checks
  //...
  //declare transaction persistor
  TransactionPersistor myPersistor(myTransaction, .../*pass down connection to DB and other stuff*/);
  //persist transaction
  try
  {
    myPersistor.Persist();
  }
  catch(...)
  {
    //handle errors
  }
}

Было бы лучше иметь какой-нибудь TransactionManager, чтобы обернуть объекты SimpleTransaction и TransactionPErsistor?

Есть ли какое-то полезное правило, чтобы понять, нужен ли мне дополнительный уровень инкапсуляции?

На данный момент я придерживаюсь большого правила: «Если метод становится слишком большим - сделайте что-нибудь с этим». Иногда бывает трудно найти правильный баланс между процедурной и объектно-ориентированной при работе с обработчиками граничных событий.

Есть мнение?

Приветствия

Ответы [ 3 ]

3 голосов
/ 01 ноября 2008

Учитывая, что:

  • концепция инкапсуляции предназначена для определения контейнера, а
  • объектно-ориентированное проектирование основано на концепции передачи сообщений (вызова методов)

Я бы сказал, что API является хорошим показателем уместности новой высокоуровневой инкапсуляции (т.е. определения нового объекта)

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

В противном случае возможно перегиб.

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

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

Но если вы определите TransactionManager, это даст вам возможность снизить сцепление различных уровней архитектуры (GUI и данные), присутствующих в MyBoundaryClass, и экспортировать управление данными в выделенный класс.
Затем вы можете протестировать постоянство данных в независимом тестовом сценарии, уделяя особое внимание предельным значениям, сбою базы данных, а также неноминальным условиям и т. Д.

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

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

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

Если вы перегруппируете поведения, иным образом реализованные в нескольких разных местах, в ваш TransactionManager, то все будет в порядке, при условии, что его открытый API-интерфейс представляет четкие этапы того, что включает транзакцию, а не «материал о транзакции», как различные служебные функции. Само по себе имя недостаточно, чтобы судить о сплоченности класса. Необходима комбинация имени и его публичного API.

Например, одним интересным аспектом TransactionManager является полная инкапсуляция понятия Transaction, которое будет:

  • станет практически неизвестным для остальной системы и уменьшит связь между другими классами и «транзакцией»
  • усиливает связность TransactionManager, сосредотачивая его API на этапах транзакции (таких как initTransaction (), persistTransaction (), ...), избегая любого метода получения или установки для любого экземпляра транзакции.
2 голосов
/ 01 ноября 2008

Развивая предложение VonC, рассмотрите следующие рекомендации:

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

  • Если одна функция (или один объект) предоставляет набор средств, которые полезны по отдельности, целесообразно преобразовать ее в более мелкие компоненты.

Замечание VonC об API - отличный лакмусовый тест: создайте эффективные интерфейсы , и объекты часто становятся очевидными.

1 голос
/ 01 ноября 2008

Уровень инкапсуляции должен быть напрямую связан с единством вашего объекта. Ваш объект должен выполнять одну задачу или должен быть разделен на несколько классов и инкапсулировать все его поведение и свойства.

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

Для вашего случая , я бы заключил в капсулу вашу идею "TransactionManager". Таким образом, «TransactionManager» будет обрабатывать работу транзакции, а не «MyBoundaryClass».

...