Как инкапсулировать вспомогательные функции (обычно используемые apis) в язык ООП, такой как java?
Эти функции могут быть трудно вписаться в конкретный класс,
какая лучшая практика для борьбы с ними?
7 ответов
Я полагаю, вы имеете в виду методы вместо функций? Вы должны действительно представить себе слово static
не существует в мире Java, там действительно нет случая, когда static
будет на самом деле сделать что-нибудь хорошее в долгосрочной перспективе.
На самом деле, в настоящее время я сталкиваюсь с подобной проблемой, у меня есть целая куча методов API, с которыми я хочу поделиться, чтобы пользователи могли вмешиваться, но я хочу скрыть фактические методы, чтобы скрыть детали реализации.
Проблема, конечно, в том, что если я просто соберу все в один класс, он станет громоздким и сложным в использовании даже с лучшими доступными инструментами автозаполнения. Скорее всего, у вас проблема не в том, где разместить эти методы, а в том, как их представить пользователю.
Чтобы решить эту проблему, я бы предложил вам создать иерархию объектов, где вы получаете доступ к своим вспомогательным методам, вызывая myUtil.someGroup().someMethod(param1, param2);
Это на самом деле так, как некоторые API уже работают, например, популярная веб-платформа Apache Wicket настраивается с помощью объекта " Настройки", который использует композицию, чтобы позволить себе иметь несколько групп различных функций.
Чтобы действительно воплотить это из теории в рабочий пример, давайте предположим, что у вас есть куча методов манипулирования изображением, которые либо преобразуют размеры изображения, либо изменяют его свойства, такие как цвет, яркость и контраст. Вместо этого
public class ImageUtil {
public static BufferedImage adjustHue(float difference,
BufferedImage original) {
/* ... */
return adjusted;
}
public static BufferedImage adjustBrightness(float difference,
BufferedImage original) {
/* ... */
return adjusted;
}
public static BufferedImage adjustContrast(float difference,
BufferedImage original) {
/* ... */
return adjusted;
}
public static BufferedImage setHeight(int newHeight,
BufferedImage original) {
/* ... */
return adjusted;
}
public static BufferedImage setWidth(int newWidth,
BufferedImage original) {
/* ... */
return adjusted;
}
}
вместо этого вы должны иметь эти интерфейсы для описания каждого набора операций
public interface IImageColorOperations {
BufferedImage adjustHue(float difference, BufferedImage original);
BufferedImage adjustBrightness(float difference, BufferedImage original;)
BufferedImage adjustContrast(float difference, BufferedImage original);
}
public interface IImageDimensionOperations {
BufferedImage setHeight(int newHeight, BufferedImage original);
BufferedImage setWidth(int newWidth, BufferedImage original);
}
и сопровождающий отдельный класс для каждого интерфейса, который вы создаете в своем "основном" служебном классе изображений, вот так
public class ImageUtils {
private final IImageDimensionOperations dimension;
private final IImageColorOperations color;
public ImageUtils() {
this(new ImageDimensionOperations(),
new ImageColorOperations());
}
/**
* Parameterized constructor which supports dependency injection, very handy
* way to ensure that class always has the required accompanying classes and
* this is easy to mock too for unit tests.
*/
public ImageUtils(IImageDimensionOperations dimension,
IImageColorOperations color) {
this.dimension = dimension;
this.color = color;
}
/* utility methods go here */
}
Но подождите, это еще не все! Теперь есть два пути, вы можете решить для себя, какой из них вы хотели бы выбрать.
Во-первых, вы можете использовать состав для непосредственного предоставления методов интерфейса:
public class ImageUtils implements IImageDimensionOperations,
IImageColorOperations {
private final IImageDimensionOperations dimension;
private final IImageColorOperations color;
public ImageUtils() {
this(new ImageDimensionOperations(),
new ImageColorOperations());
}
/* etc. */
}
При этом вам просто нужно делегировать вызовы различных методов фактическому классу операций. Недостатком этого является то, что если вы добавляете другой метод, вы должны изменить и этот служебный класс, и базовый класс реализации.
Ваш второй выбор - выставить сами классы операций (вот почему я добавил finals
там!):
public class ImageUtils {
public final IImageDimensionOperations dimension;
public final IImageColorOperations color;
public ImageUtils() {
this(new ImageDimensionOperations(),
new ImageColorOperations());
}
public ImageUtils(IImageDimensionOperations dimension,
IImageColorOperations color) {
this.dimension = dimension;
this.color = color;
}
/* Nothing else needed in this class, ever! */
}
Делая это, вы получаете эти красивые звонки, такие как
BufferedImage adjusted = imageUtils.color.adjustHue(3.2f, original);
и когда вы добавляете какой-либо метод к любому из интерфейсов, вы уже имеете их в своем служебном классе образов без каких-либо дополнительных изменений. Да, в общем, общедоступные поля в Java - это большое нет-нет, однако я думаю, что в этом случае это не так уж плохо, особенно с finals
пометить поля как неизменяемые (по крайней мере, в теории).
Для коротких и очевидных вспомогательных функций я обнаружил, что сам метод обеспечивает достаточно инкапсуляцию. И куда бы вы поместили их, кроме выделенного статического класса? Да, многие контролеры исходного кода предупреждают вас о том, что у вас есть полностью статические классы, но если они не могут предложить ничего лучшего, вы должны просто проигнорировать это. Объекты и классы являются инструментами, нет никаких оснований применять их религиозно, как Единый Истинный Путь - в любом случае, он редко встречается.
В зависимости от назначения этих вспомогательных функций, вы можете взглянуть на каждый Apache Commons. Если это, например, функции, которые должны удалить java.lang.*
связанный шаблонный код, затем взгляните на Commons Lang (Javadocs здесь). То же самое существует для java.io.*
Образец в духе Commons IO (Javadocs здесь) и java.sql.*
DbUtils (Javadocs здесь).
Если эти вспомогательные функции имеют действительно конкретные цели и не могут быть разумно использованы повторно в совершенно разных проектах, тогда я продолжу с ответом Esko, представленным в этой теме.
Я бы порекомендовал создать Util
Класс и сброс любой статической функции, которая не подходит ни к чему другому. Вы все равно должны разделить функции на группы в зависимости от того, для чего они используются. Как GUI API, Database API и т. Д.
Обычной практикой является создание класса со статическими методами.
Я предпочитаю не делать этого, а создавать экземпляр класса, а затем вызывать методы для него. Зачем? Потому что тогда я могу настроить этот экземпляр по-разному. Чтобы сделать это с помощью статических методов, потребуется передать настройку при каждом вызове метода.
Я думаю о служебных классах, что они мне не особенно нравятся. Всякий раз, когда я его создаю, я останавливаюсь и тщательно обдумываю, относится ли эта утилита к одному из объектов, которые я моделирую, и, если она не подходит, возможно, мое моделирование не правильно.
Вы должны взглянуть на Google Collections и Guava, так как они имеют некоторые функции, которые обычно предоставляются в виде статических методов в полном стиле OO, например
Конечно, первое, что нужно сделать, это попытаться найти (подходящее) место для них, подходящее для структуры класса.
Но если их нет, не рожайте обувь, создайте третий класс с помощниками. Для этого есть две основные стратегии:
Первая стратегия использует статические методы:
public class FooHelper
{
public static Bar doSomething(Foo) {
}
/* ... */
}
// usage:
Bar b = FooHelper.doSomething(fooInstance);
Другой почти такой же, но в экземпляре класса:
public class FooHelper
{
public Bar doSomething(Foo) {
}
/* ... */
}
// usage:
FooHelper fh = new FooHelper();
Bar b = fh.doSomething(fooInstance);
Иногда вы видите шаблон Singleton, чтобы получить экземпляр, но только один экземпляр:
public class FooHelper
{
private static FooHelper theInstance = new FooHelper();
public static FooHelper getInstance() {
return theInstance;
}
public Bar doSomething(Foo) {
}
/* ... */
}
// usage:
FooHelper fh = FooHelper.getInstance();
Bar b = fh.doSomething(fooInstance);
(Обратите внимание, что это упрощенная реализация Singleton, которая из реализаций Singleton, вероятно, подходит для помощников. Она подходит не для всех классов Singleton.)
У созданных экземпляров классов есть некоторые существенные преимущества по сравнению со статическими функциями, не в последнюю очередь то, что помощники могут быть расширены, как и любой другой класс. Думайте о помощнике как о чем-то отдельном, о посреднике. И мы моделируем вещи как классы с экземплярами в парадигме ООП на основе классов.