NullObjectPattern и сопоставимый интерфейс
Проблема, с которой я столкнулся, уже задавалась ранее: как реализовать интерфейс с перечислением, где этот интерфейс расширяет Comparable?
Однако ни одно из решений не решает мою точную проблему, а именно:
У меня есть объект значения, похожий на BigDecimal
, Иногда это значение не может быть установлено с реальным объектом, потому что это значение еще не известно. Поэтому я хочу использовать шаблон нулевого объекта для представления времени, когда этот объект не определен. Это все не проблема, пока я не попытаюсь заставить свой Null Object реализовать Comparable
интерфейс. Вот SSCCE для иллюстрации:
public class ComparableEnumHarness {
public static interface Foo extends Comparable<Foo> {
int getValue();
}
public static class VerySimpleFoo implements Foo {
private final int value;
public VerySimpleFoo(int value) {
this.value = value;
}
@Override
public int compareTo(Foo f) {
return Integer.valueOf(value).compareTo(f.getValue());
}
@Override
public int getValue() {
return value;
}
}
// Error is in the following line:
// The interface Comparable cannot be implemented more than once with different arguments:
// Comparable<ComparableEnumHarness.NullFoo> and Comparable<ComparableEnumHarness.Foo>
public static enum NullFoo implements Foo {
INSTANCE;
@Override
public int compareTo(Foo f) {
return f == this ? 0 : -1; // NullFoo is less than everything except itself
}
@Override
public int getValue() {
return Integer.MIN_VALUE;
}
}
}
Другие проблемы:
- В реальном примере есть несколько подклассов того, что я называю
Foo
Вот. - Я мог бы, вероятно, обойти это, имея
NullFoo
не бытьenum
, но тогда я не могу гарантировать, что когда-либо будет только один его экземпляр, т.е. Effective Java Item 3, pg. 17-18
3 ответа
Я не рекомендую шаблон NullObject, потому что я всегда нахожусь в одной из следующих двух ситуаций:
- не имеет смысла использовать NullObject как объект, и он должен оставаться
null
- NullObject имеет слишком много значения, чтобы быть просто NullObject, и должен быть самим истинным объектом (например, когда он действует как полностью функциональное значение по умолчанию)
Согласно нашему обсуждению в комментариях, мне кажется, что ваш NullObject ведет себя очень похоже на значение 0 ваших обычных объектов.
На самом деле я бы использовал 0 (или любое другое значение по умолчанию, которое имеет больше смысла) и поставил флаг, если вам действительно нужно знать, было ли оно инициализировано. Таким образом, вы будете иметь 2 вещи для рассмотрения:
- все неинициализированные значения не будут использовать один и тот же экземпляр в моем решении
- по той же причине вы теперь можете инициализировать свой объект позже без необходимости создавать новый экземпляр
Вот код, о котором я думаю:
public static class VerySimpleFoo implements Foo {
private int value;
private boolean initialized;
public VerySimpleFoo() {
this.value = 0; // whatever default value makes more sense
this.initialized = false;
}
public VerySimpleFoo(int value) {
this.value = value;
this.initialized = true;
}
@Override
public int compareTo(Foo f) {
// possibly need some distinction here, depending on your default value
// and the behavior you expect
return Integer.valueOf(value).compareTo(f.getValue());
}
@Override
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
this.initialized = true;
}
public boolean isInitialized() {
return initialized;
}
}
Как вы и предполагали, я полагаю, что одним из решений будет использование класса вместо перечисления:
public class NullFoo implements Foo {
private NullFoo() {
}
public static final Foo INSTANCE = new NullFoo();
@Override
public int compareTo(Foo f) {
return f == this ? 0 : -1;
}
@Override
public int getValue() {
return 0;
}
}
Это имитирует поведение enum, но позволяет реализовать интерфейс Foo. Класс не может быть создан из-за закрытого конструктора, поэтому единственный доступный экземпляр - это тот, который доступен через NullFoo.INSTANCE
, который является потокобезопасным (благодаря final
модификатор).
Дело в том, что Enum уже изначально реализует Comparable, и поскольку универсальные шаблоны являются просто сахарным кодом и теряются после компиляции, фактически вы хотите реализовать один и тот же метод дважды для одного и того же интерфейса.
Я бы отбросил enum для NullFoo, преобразовал его в класс (как вы предложили) и сделал бы окончательную публичную статическую ссылку INSTANCE с приватным конструктором (это не так хорошо, как использование enum, но в большинстве случаев приемлемо).