Как инкапсулировать вспомогательные функции (обычно используемые 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.)

У созданных экземпляров классов есть некоторые существенные преимущества по сравнению со статическими функциями, не в последнюю очередь то, что помощники могут быть расширены, как и любой другой класс. Думайте о помощнике как о чем-то отдельном, о посреднике. И мы моделируем вещи как классы с экземплярами в парадигме ООП на основе классов.

Другие вопросы по тегам