абстрактные методы в скелетных реализациях интерфейсов - PullRequest
2 голосов
/ 26 октября 2008

Я перечитывал Effective Java (2nd edition), пункт 18, предпочел интерфейсы абстрактным классам . В этом пункте Джош Блох приводит пример скелетной реализации интерфейса Map.Entry<K,V>:

// Skeletal Implementation
public abstract class AbstractMapEntry<K,V>
        implements Map.Entry<K,V> {
    // Primitive operations
    public abstract K getKey();
    public abstract V getValue();

 // ... remainder omitted
}

Из этого примера вытекают два вопроса:

  1. Почему getKey и getValue явно объявлены здесь как абстрактные методы? Они являются частью интерфейса Map.Entry , поэтому я не вижу причины для избыточного объявления в абстрактном классе.
  2. Зачем использовать идиому оставления этих примитивных методов, как их называет мистер Блох, как абстрактных? Почему бы просто не сделать это:

    // Скелетная реализация открытый абстрактный класс AbstractMapEntry реализует Map.Entry { закрытый ключ K; частное значение V;

        // Primitive operations
        public K getKey() {return key;}
        public V getValue() {return value;}
    
     // ... remainder omitted
    

    }

Преимущества этого состоят в том, что каждый подкласс не должен определять свой собственный набор полей и может по-прежнему получать доступ к ключу и значению посредством своих методов доступа. Если подкласс действительно должен определить свое собственное поведение для методов доступа, он может напрямую реализовать интерфейс Map.Entry. Другим недостатком является то, что в методе equals, предоставляемом скелетной реализацией, абстрактные методы доступа называются:

// Implements the general contract of Map.Entry.equals
@Override public boolean equals(Object o) {
    if (o == this)
        return true;
    if (! (o instanceof Map.Entry))
        return false;
    Map.Entry<?,?> arg = (Map.Entry) o;
    return equals(getKey(),   arg.getKey()) &&
           equals(getValue(), arg.getValue());
}

Bloch предостерегает от вызова переопределяемых методов (пункт 17) из классов, предназначенных для наследования, поскольку он оставляет суперкласс уязвимым для изменений, вносимых подклассами. Может быть, это вопрос мнения, но я надеялся определить, есть ли что-то еще в этой истории, поскольку Блох в действительности не уточняет это в книге.

Ответы [ 4 ]

1 голос
/ 13 декабря 2010

Блох предостерегает от звонков переопределяемые методы (пункт 17) из классы, предназначенные для наследования, как это оставляет суперкласс уязвимым для изменения, сделанные подклассами

Он предупреждает о вызове переопределенных методов в конструкторе, а не в других методах.

1 голос
/ 04 июня 2009
  1. Я бы сказал, что это помогает подчеркнуть то, с чем конкретно должен работать конкретный класс, вместо того, чтобы просто оставить это на усмотрение компилятора (или вам нужно сравнить оба, чтобы увидеть, чего не хватает). Вид самодокументируемого кода. Но это, конечно, не обязательно, это, насколько я вижу, скорее стиль.
  2. Существует более значительная логика в возвращении этих значений, чем простой метод получения и установки. Каждый класс, который я проверял в стандартном JDK (1.5), делал что-то непростое по крайней мере с одним из методов, так что я бы предположил, что он считает такую ​​реализацию слишком наивной, и это будет стимулировать использование ее подклассами вместо того, чтобы продумывать проблема самостоятельно.

Относительно проблемы с равными ничего не изменится, если абстрактный класс реализует их, потому что проблема переопределена able . В этом случае я бы сказал, что equals пытается быть аккуратно реализован, чтобы предвидеть реализации. Обычно равные в общем случае не должны быть реализованы, чтобы возвращать true между собой и его подклассом (хотя есть много, что делают) из-за проблем ковариации (суперкласс будет думать, что он равен подклассу, но подкласс не будет думать, что он равен суперклассу) , так что этот тип реализации равно хитрый, независимо от того, что вы делаете.

0 голосов
/ 04 июня 2009
  1. Не вижу причин

  2. Позволяет реализации определить, как хранятся ключ и значение.

0 голосов
/ 26 октября 2008

Одной из причин того, что AbstractMapEntry#getKey и getValue являются абстрактными (то есть не реализованными), является то, что Map.Entry является внутренним интерфейсом к Map. Использование вложенных классов / интерфейсов - это то, как Java реализует композицию. Идея в композиции состоит в том, что составная часть не является первоклассной концепцией. Скорее, составная часть имеет смысл, только если она содержится в целом. В этом случае составной частью является Map.Entry, а корневым объектом составного объекта является Map. Очевидно, что выраженная концепция состоит в том, что Map имеет много Map.Entry с.

Следовательно, семантика AbstractMapEntry#getKey и getValue будет существенно зависеть от реализации Map, о которой мы говорим. Как вы уже написали, простая старая реализация getter прекрасно подойдет для HashMap. Это не будет работать для чего-то вроде ConcurrentHashMap, что требует безопасности потоков. Вероятно, что реализация ConcurrentHashMap getKey и getValue делает защитные копии. (Рекомендую проверить исходный код для себя).

Другая причина не реализовывать getKey и getValue состоит в том, что символы, которые реализуют Map, радикально отличаются, начиная от тех, которые никогда не должны были принадлежать (т. Е. Properties), до совершенно разных вселенных от интуитивных значений Map (например, Provider, TabularDataSupport).

В заключение, не реализует AbstractMapEntry#getKey и getValue из-за этого золотого правила проектирования API:

Если сомневаетесь, оставьте его (см. здесь )

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