Что, если у меня есть немного методов фабрики, возвращая непубличный тип и соединяя набор методов, который дает переменные этого непубличного типа? Это приводит с названным предупреждающим сообщением к 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)) );
Это - плохо идея?
Сама идея верна. Но это не сработает, если вы сделаете пакет корневого типа (здесь: 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
. Основное преимущество его наличия: клиент может объявлять переменные этого типа, а компилятор может выполнять проверку типа. Однако ваша структура остается единственным местом, где могут быть созданы конкретные экземпляры этого типа, и, таким образом, ваша структура имеет полный контроль над всеми значениями.
Я могу придумать два способа сделать это, но ни один из них не работает:
Объявить NumberSet
как частный пакет, но изменить общедоступные методы API, которые в настоящее время используют NumberSet
type, чтобы использовать вместо него Object
, а реализации приводят аргументы из Object
в NumberSet
по мере необходимости. Это зависит от того, что вызывающие объекты всегда передают правильный тип объекта, и будут подвержены ClassCastException
s.
Реализуйте общедоступный интерфейс NumberSet
без методов и частный пакет InternalNumberSet
, который реализует NumberSet
и определяет требуемые методы для внутреннего использования. Приведение типов снова используется для приведения аргументов NumberSet
к InternalNumberSet
. Это лучше предыдущего подхода, но если кто-то создаст класс, реализующий NumberSet
, не реализовав также InternalNumberSet
, результатом снова будет ClassCastException
s.
Я лично считаю, что вам следует просто объявить интерфейс NumberSet
общедоступным. Нет никакого реального вреда в раскрытии геттеров в интерфейсе, и если вы хотите, чтобы ваши классы реализации были неизменными, объявите их как final и не раскрывайте сеттеры.