В нашей кодовой базе Java я вижу следующий шаблон:
/**
This is a stateless utility class
that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
public int foo(...) {...}
public void bar(...) {...}
}
/**
This class does applied foo-related things.
*/
class FooSomething {
int DoBusinessWithFoo(FooUtil fooUtil, ...) {
if (fooUtil.foo(...)) fooUtil.bar(...);
}
}
Что меня беспокоит, так это то, что мне приходится проходить экземпляр FooUtil
везде, потому что тестирование.
- Я не могу сделать
FooUtil
методы статичными, потому что я не смогу имитировать их для тестирования обоихFooUtil
классов и их клиентов. - Я не могу создать экземпляр
FooUtil
в месте потребления с помощьюnew
, опять же, потому что я не смогу имитировать его для тестирования.
Я полагаю, что лучше всего использовать инъекцию (и я это делаю), но это добавляет свой набор неприятностей. Кроме того, передача нескольких экземпляров util увеличивает размер списков параметров метода.
Есть ли способ справиться с этим лучше, что я не вижу?
Обновление: поскольку служебные классы не сохраняют состояния, я мог бы, вероятно, добавить статический одноэлементный INSTANCE
член или статический getInstance()
метод, сохранив при этом возможность обновлять базовое статическое поле класса для тестирования. Тоже не кажется супер-чистым.
FooUtil
методы должны быть введеныFooSomething
, может быть, есть свойства,FooSomething
которые должны быть изменены / расширены, чтобыFooUtil
методы больше не нужны. Пожалуйста, дайте нам более конкретный пример того, какFooSomething
помочь нам дать хороший ответ на эти вопросы.Ответы:
Прежде всего позвольте мне сказать, что существуют разные подходы к программированию для разных задач. Для своей работы я предпочитаю сильный дизайн ОО для организации и обслуживания, и мой ответ отражает это. У функционального подхода будет совсем другой ответ.
Я склонен считать, что большинство служебных классов создаются, потому что объект, на котором должны быть методы, не существует. Это почти всегда происходит из одного из двух случаев: кто-то раздает коллекции или кто-то раздает бины (их часто называют POJO, но я считаю, что группу сеттеров и геттеров лучше всего описать как бин).
Когда у вас есть коллекция, которую вы передаете, должен быть код, который хочет быть частью этой коллекции, и это в конечном итоге будет разбросано по всей вашей системе и в конечном итоге будет собрано в служебные классы.
Мое предложение состоит в том, чтобы обернуть ваши коллекции (или bean-компоненты) любыми другими тесно связанными данными в новые классы и поместить код «полезности» в реальные объекты.
Вы можете расширить коллекцию и добавить туда методы, но таким образом вы потеряете контроль над состоянием вашего объекта - я предлагаю вам полностью его инкапсулировать и предоставлять только те методы бизнес-логики, которые необходимы (подумайте: «Не спрашивайте ваши объекты об их данных, дайте вашим объектам команды и дайте им действовать на основе их личных данных ", важного OO-арендатора, который, когда вы об этом думаете, требует подхода, который я предлагаю здесь).
Это «обертывание» полезно для любых объектов библиотеки общего назначения, которые содержат данные, но вы не можете добавить код к ним. Помещение кода вместе с данными, которыми он манипулирует, является еще одной важной концепцией в ОО-дизайне.
Есть много стилей кодирования, где эта концепция переноса была бы плохой идеей - кто хочет написать целый класс, просто реализуя одноразовый скрипт или тест? Но я думаю, что для ОО обязательна инкапсуляция любых базовых типов / коллекций, которые вы не можете добавить к себе в код.
источник
Вы можете использовать шаблон поставщика и выполнение вашего
FooSomething
сFooUtilProvider
объектом (может быть в конструкторе, только один раз).FooUtilProvider
будет иметь только один метод:FooUtils get();
. Затем класс будет использовать любойFooUtils
экземпляр, который он предоставляет.Теперь это один шаг от использования инфраструктуры внедрения зависимостей, когда вам нужно будет только дважды подключить DI cointainer: для производственного кода и для вашего набора тестов. Вы просто привязываете
FooUtils
интерфейс к любомуRealFooUtils
или кMockedFooUtils
, а остальное происходит автоматически. Каждый объект, от которого зависит,FooUtilsProvider
получает правильную версиюFooUtils
.источник