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

Давайте взглянем на простой пример:

[source,cpp]

std::string QuarterlyReportGenerator::generate() { std::vector < Result > results = database.queryResults( beginDate, endDate); std::string pageText; pageText += “” “Quarterly Report” “”; if (results.size() != 0) { for (std::vector < Result > ::iterator it = results.begin(); it != results.end(); ++it) { pageText += “”; pageText += “” + it - > department + “”; pageText += “” + it - > manager + “”; char buffer[128]; sprintf(buffer, “$%d”, it - > netProfit / 100); pageText += std::string(buffer); sprintf(buffer, “$%d”, it - > operatingExpense / 100); pageText += std::string(buffer); pageText += “”; } } else { pageText += “No results for this period”; } pageText += “”; pageText += “”; pageText += “”; return pageText; }

Давайте, предположим, что нам нужно добавить в этот код html заголовок для таблицы. Заголвок должен выглядеть вот так: [source,cpp]

“DepartmentManagerProfitExpenses”

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

[source,cpp]

using namespace std; class QuarterlyReportTableHeaderProducer { public: string makeHeader(); }; string QuarterlyReportTableProducer::makeHeader() { return “DepartmentManager” “ProfitExpenses”; } }

Позже мы сможем вызвать его методы из класса [source,cpp]

QuarterlyReportGenerator::generate(): … QuarterlyReportTableHeaderProducer producer; pageText += producer.makeHeader(); …

Уверен, что глядя, на этом изменение вы скажите -

“Для такого маленького изменения создавать целый класс? Как еще один класс улучшит мою архитектуру? Он только усложнит код.”

Да, с этой точки зрения это правда. единственная причина, по которой можно такое сделать - это большое количество зависимостей, но давай-те присмотримся поближе.

А что если назвать этот класс QuarterlyReportTableHeaderGenerator и вынести из него интерфейс?

[source,cpp]

class QuarterlyReportTableHeaderGenerator { public: string generate(); };

QuarterlyReportTableHeaderGenerator это такой же генератор, как и QuarterlyReportGenerator. У них обоих есть метод generate(), который возвращает строковое значение. Можно сделать интерфейс, который обобщит код создания: [source,cpp]

class HTMLGenerator { public: virtual ~HTMLGenerator() = 0; virtual string generate() = 0; }; class QuarterlyReportTableHeaderGenerator : public HTMLGenerator { public: … virtual string generate(); … }; class QuarterlyReportGenerator : public HTMLGenerator { public: … virtual string generate(); … };

Также мы можем покрыть QuarterlyReportGenerator тестами и изменить его реализацию так, чтобы он выполнял большинство своей работы с помощью генераторов. В этом случае, мы можем обернуть класс концепцией, которую уже используем в приложении. Возможно, что в некоторых классах, мы не сможем так сделать. Но это не значит, что нужно сдаваться. Некоторые “ветвящиеся классы” не приводятся к концепции всего приложения, вместо этого они становятся новыми! Может получится так, что через некоторое время после создания “ветвящегося класса” вы найдете похожу концепцию.

Через два месяца после создания “ветвящегося класса” ваше мнение о нем может изменится. Сам факт добавления нового класса в систему дает вам пищу для размышления. Когда вы будете делать изменения где-то рядом с ним обязательно подумайте - являются ли эти изменения частью новой концепции или требуют изменения старой концепции. Это все входит в процесс проектирования архитектуры вашего приложения.

По сути всего два случая, когда нужно задуматься о применении “ветвящегося класса”.

  • Первый - ваши изменения в классе добавляют новую зону ответсвенности в одном из классов. Например, в приложении для расчета налогов, некоторые отчисления возможны только в определенные времена года. Можно с легкостью добавить проверку на принадлежность к времени года к классу TaxCalculator, но эта проверка не входит в зону ответственности TaxCalculator - рассчитывать налог? Возможно, это должен быть какой-то другой класс.
  • Второй пример - как раз рассмотрен выше. У нас небольшая функциональность, которую, в принципе, мы можем добавить к существующему классу, но мы не сможем протестировать этот метод. Если нам повезло, и мы отдельно можем покрыть тестами только этот метод, то “метод ветвления” нам подойдет, но к сожалению, не всегда так везет.

Хотя у обоих подходов достаточно разная мотивация для применения, в конечном итоге результат примерно один. Является ли добавляемая функциональность настолько значимой, чтобы быть новой зоной ответсвенности - это тяжелое решение. Даже больше, из-за того, что код все время меняется, решение использовать подход “ветвящийся класс” часто выглядит лучше в ретроспективе.

=== Плюсы и минусы Основное преимущество использования “класса” - это своеобразный взгляд в будущее, уверенность в том, если придется менять еще раз, то вы будете к этому готовы.