Должно ли разделение между API и реализацией быть полным?

разделение между дизайном API и их реализацией часто рекомендуется в больших реализациях программного обеспечения. Но где-то они должны быть повторно подключены (т. е. реализация должна быть повторно подключена к API).

в следующем примере показан дизайн API и вызов его реализации через объект экземпляра:

import java.util.List;

public abstract class Separation {

    public static final Separation INSTANCE = new SeparationImpl();

    // Defining a special list
    public static interface MySpecialList<T> extends List<T> {
        void specialAdd(T item);
    }

    // Creation of a special list
    public abstract <T> MySpecialList<T> newSpecialList(Class<T> c);

    // Merging of a special list
    public abstract <T> MySpecialList<? extends T> specialMerge(
        MySpecialList<? super T> a, MySpecialList<? super T> b);

    // Implementation of separation
    public static class SeparationImpl extends Separation {

        @Override
        public <T> MySpecialList<T> newSpecialList(Class<T> c) {
            return ...;
        }

        @Override
        public <T> MySpecialList<? extends T> specialMerge(
            MySpecialList<? super T> a, MySpecialList<? super T> b) {
            return ...;
        }

    }

}

некоторые будут утверждать, что API не должен ссылаться на код реализации. Даже если мы отделяем код API от реализации через отдельные файлы, часто приходится импортировать код реализации (по крайней мере, имя класса) в API.

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

мой вопрос: есть ли какая-либо польза полностью отделить или изолировать код API от кода реализации? Или это просто попытка пуриста достичь совершенства с помощью маленькие практические выгоды?

4 ответов


Я всегда понимал требование отделить интерфейс от реализации, чтобы означать, что вы не смешиваете как реализации с что. Таким образом, в приведенном выше примере смешивание api и реализации означало бы предоставление в api чего-то, что было специфичным для того, как SeparationImpl реализовал ваш api.

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

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


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


ответов Питера и Стива достаточно, но я хотел бы добавить больше - если у вас когда-либо была только одна реализация интерфейса или абстрактного класса, то его бессмысленно иметь интерфейс или абстрактный класс, поскольку его поражение цели абстракции.
В вашем случае я действительно не понял-почему вы реализовали Separation как абстрактный класс, а SeparationImpl сам может быть классом API или если у вас разные реализации Separation может быть inetrface и если у вас есть некоторые общие функциональность тогда вы можете иметь другой абстрактный класс, реализующий ваш интерфейс, а затем SeparationImpl наследование от абстрактного класса. пример иерархии классов будет выглядеть как

interface Separation --> AbstractSeparation --> SeparationImpl 

так же, как стандартная библиотека коллекции

interface List --> AbstractList --> ArrayList

В дополнение к хорошим моментам от других авторов я бы упомянул цели модульного тестирования:

макетировать объекты очень проще при наличии интерфейсов intstead классов.