Экспорт непубличного типа через публичный API - PullRequest
7 голосов
/ 30 апреля 2010

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

В результате публичный API будет содержать только два набора методов сопряжения. Причина в том, чтобы сделать мою иерархию типов запечатанной (например, классы seald в Scala) и позволить пользователям создавать экземпляры этих типов только через фабричные методы. Таким образом, мы получаем DSL в некотором смысле.

Например, класс расписания, представленный ограничениями полей календаря. Существует несколько типов ограничений - Range, Singleton, List, FullSet - с интерфейсом NumberSet в качестве корневого. Мы не хотим раскрывать эти типы и то, как Расписание взаимодействует с ними. Мы просто хотим спецификацию от пользователя. Поэтому мы делаем пакет NumberSet закрытым. В классе Schedule мы создаем несколько фабричных методов для ограничений:

NumberSet singleton(int value);
NumberSet range(int form, int to);
NumberSet list(NumberSet ... components);

и некоторые методы для создания объекта Schedule:

Schedule everyHour(NumberSet minutes);
Schedule everyDay(NumberSet minutes, NumberSet hours);

Пользователь может использовать их только в следующем порядке:

Schedule s = Schedule.everyDay( singleton(0), list(range(10-15), singleton(8)) );

Это плохая идея?

Ответы [ 2 ]

9 голосов
/ 30 апреля 2010

Идея сама по себе является звуком. Но это не сработает, если вы сделаете пакет root type (здесь: NumberSet) закрытым. Либо предоставьте этот тип, либо используйте открытый интерфейс. Фактические типы реализации должны (и могут) быть скрытыми.

public abstract class NumberSet {

    // Constructor is package private, so no new classes can be derived from
    // this guy outside of its package.
    NumberSet() {
    }
}

public class Factories {

    public NumberSet range(int start, int length) {
        return new RangeNumberSet(start, length);
    }

    // ...
}

class RangeNumberSet extends NumberSet {
   // ... must be defined in the same package as NumberSet
   // Is "invisible" to client code
}

Редактировать Предоставление скрытого / закрытого корневого типа из общедоступного API является ошибкой. Рассмотрим следующий сценарий:

package example;

class Bar {
    public void doSomething() {
            // ...
    }
}

public class Foo {

    public Bar newBar() {
        return new Bar();
    }
}

и рассмотрим клиентское приложение, использующее этот API. Что может сделать клиент? Он не может должным образом объявить переменную типа Bar, поскольку этот тип невидим для любого класса вне пакета example. Он даже не может вызывать public методы для экземпляра Bar, который он получил откуда-то, поскольку он не знает, что такой открытый метод существует (он не может видеть класс, не говоря уже о любых членах, которые он предоставляет). Итак, лучшее, что может сделать клиент, это что-то вроде:

Object bar = foo.newBar();

что по сути бесполезно. Другое дело было бы иметь открытый интерфейс (или абстрактный класс) вместо закрытого пакета, как в коде, определенном выше. В этом случае клиент на самом деле может объявить переменную типа NumberSet. Он не может создавать свои собственные экземпляры или наследовать подклассы, поскольку конструктор скрыт от него, но он может получить доступ к общедоступному API, определенному.

Редактировать снова Даже если вам нужно «безликое» значение (с точки зрения клиента), т. Е. Значение, которое не определяет какие-либо интересные API, которые клиент может захотеть вызвать, оно все равно хорошая идея выставить публичный базовый тип описанным выше способом. И только для того, чтобы компилятор мог выполнять проверку типов и позволять клиентскому коду временно сохранять такое значение в (должным образом объявленной) переменной.

Если вы не хотите, чтобы ваш клиент вызывал какие-либо методы API для типа: это нормально. Ничто не мешает вам не предоставлять публичный API для вашего (иначе) публичного базового типа. Просто не объявляй ничего. Используйте «пустой» абстрактный базовый класс (пустой с точки зрения клиента, так как все интересные методы будут частными и, следовательно, скрытыми). Но, тем не менее, вы должны предоставить публичный базовый тип, или вы должны использовать обычный Object в качестве возвращаемого значения, но тогда вы потеряете проверку ошибок во время компиляции.

Практическое правило. Если клиент должен вызвать какой-то метод для получения значения и передать его другому API, то клиент на самом деле знает , что существуют некоторые магические специальные значения. И он должен быть в состоянии справиться с этим каким-либо образом (по крайней мере, «передать его»). Если вы не предоставите клиенту правильный тип для работы со значениями, вы ничего не купите, кроме (совершенно уместных) предупреждений компилятора.

public abstract class Specification {

    Specification() {
        // Package private, thus not accessible to the client
        // No subclassing possible
    }

    Stuff getInternalValue1() {
        // Package private, thus not accessible to the client
        // Client cannot call this
    }
}

Приведенный выше класс является «пустым» в отношении кода клиента; он не предлагает пригодный для использования API, за исключением того, что Object уже предлагает. Основное преимущество - клиент может объявлять переменные этого типа, а компилятор может проверять тип. Ваша платформа, тем не менее, остается единственным местом, где могут быть созданы конкретные экземпляры этого типа, и, таким образом, ваша среда имеет полный контроль над всеми значениями.

0 голосов
/ 30 апреля 2010

Я могу придумать два способа сделать это, но ни один из них не работает:

  1. Объявите NumberSet как частный пакет, но измените общедоступные методы API, которые в настоящее время используют тип NumberSet, чтобы вместо них использовать Object, и чтобы реализации приводили аргументы от Object до NumberSet требуется. Это зависит от вызывающих абонентов, всегда пропускающих правильный тип объекта, и будет склонен к ClassCastException s.

  2. Реализация общедоступного NumberSet интерфейса без методов и пакета private InternalNumberSet, который реализует NumberSet и определяет внутренние методы, необходимые. Приведение типов используется еще раз для приведения аргументов NumberSet к InternalNumberSet. Это лучше предыдущего подхода, но если кто-то создаст класс, который реализует NumberSet без реализации InternalNumberSet, результатом будет ClassCastException s еще раз.

Лично я считаю, что вы должны просто объявить интерфейс NumberSet как открытый. Нет ничего страшного в том, чтобы выставлять геттеры в интерфейсе, и если вы хотите, чтобы ваши классы реализации были неизменяемыми, объявите их как финальные и не выставляйте сеттеры.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...