Экспорт непубличного типа через общедоступный API

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

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

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

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

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

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

Пользователь может только использовать их таким образом:

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

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

7
задан Aleks Selivanov 30 April 2010 в 13:37
поделиться

2 ответа

Сама идея верна. Но это не сработает, если вы сделаете пакет корневого типа (здесь: 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 . Он не может даже вызвать общедоступные методы в экземпляре 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 . Основное преимущество его наличия: клиент может объявлять переменные этого типа, а компилятор может выполнять проверку типа. Однако ваша структура остается единственным местом, где могут быть созданы конкретные экземпляры этого типа, и, таким образом, ваша структура имеет полный контроль над всеми значениями.

9
ответ дан 7 December 2019 в 03:13
поделиться

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

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

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

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

0
ответ дан 7 December 2019 в 03:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: