Правильно ли в OOP программировании иметь ссылку на дочерний интерфейс в своих методах? - PullRequest
0 голосов
/ 19 апреля 2020

Мне интересно, является ли хорошей практикой использование конкретной реализации интерфейса (или абстрактного класса в этом отношении) в методе по умолчанию? Например:

interface Foo {

    String bar();

    default Foo withPrefix(String prefix) {
        return new PrefixFooImpl(prefix, this).bar();
    }
}

// proxy class for any Foo, adding a prefix to every bar() call
class PrefixFooImpl implements Foo {

    private String prefix;
    private Foo foo;

    // constructor with Foo and prefix parameter

    public String bar() {
        return prefix + " " + foo.bar();
    }
}

его использование будет следующим: вместо использования

new PrefixFooImpl("prefix", new OtherFoo());

можно просто сделать

new OtherFoo().withPrefix("prefix");

, это, безусловно, работает, и это удобно использовать, но должны ли методы интерфейса по умолчанию содержать ссылку на его реализации? Разве это не ненужная связь или плохая практика? И если да, есть ли другой способ сделать это правильно?

Ответы [ 2 ]

0 голосов
/ 20 апреля 2020

Смысл интерфейса в том, чтобы позволить реализации меняться, не влияя на вызывающих.

В вашем примере вызывающий withPrefix() может не предполагать, что он возвращает PrefixFooImpl, он может только предполагать Foo.

Даже в примере supercat , AsImmutableEnumerable() не "утечет" детали реализации, что означает, что изменение не повлияет на вызывающих.

Все это, конечно, предполагает, что если какой-то вызывающий объект решит преобразовать результат withPrefix() в конкретную c реализацию, будь то PrefixFooImpl или что-то еще, он не будет жаловаться при изменении .

Другая сторона, конечно, фактически позволяет изменять реализацию.

Опять же, с примером Foo, реализация всегда может переопределить withPrefix(), так что пока интерфейс не расширяется методами по умолчанию, все по-прежнему хорошо.

Если интерфейс расширяется методами по умолчанию после того, как Foo уже имеет существующие реализации (которые работают не переопределяя новые методы), тогда методы по умолчанию должны позаботиться о том, чтобы объект, который ожидает вызывающий объект, все еще демонстрировал поведение, намеченное тем, кто создавал реализацию (например, вы не можете вернуть клиент MySQL из реализации PostgreSQL клиента).

В вашем примере объект, который ожидает вызывающая сторона, проксирует исходный объект. Другая реализация может просто вернуть исходный объект. Это все допустимые поведения.

Таким образом, выполнение подобных вещей не является нарушением абстракции, предоставляемой интерфейсом. На самом деле выполнение этого строго для удобства syntacti c приводит к усложнению кода и усложнению. отладка.

Если мы возьмем пример Foo снова, вызов withPrefix() 10 раз создаст цепочку из 11 объектов. Помимо влияния на G C, представьте себе сложную реализацию PrefixFooImpl - то, что говорит с внешним миром (например, DnsResolvingFooProxy, который разрешает bar в качестве имени хоста в IP-адрес).

Или представьте два метода, asIpAddress() и resolveLoadBalance(), каждый из которых ссылается на свою реализацию .

В заключение, это не нарушение абстракции, но если вы его используете, очень рассудительно об этом.

0 голосов
/ 19 апреля 2020

Иногда это может быть хорошим шаблоном, и если бы язык поддерживал реализации интерфейса по умолчанию при реализации таких интерфейсов, как List и Enumerable, имело бы смысл включить в них гораздо больше методов, большинство из которых быть реализован с использованием общей реализации по умолчанию.

В качестве простого примера, имеет смысл иметь интерфейс ImmutableEnumerable и класс ImmutableArrayList, оба из которых реализуют Enumerable и гарантируют, что повторное перечисление всегда будет и навсегда дают одинаковую последовательность объектов. Интерфейс Enumerable мог бы тогда включать метод AsImmutableEnumerable, реализация которого по умолчанию вызывала бы конструктор ImmutableArrayList, который строит приватный ArrayList из Enumerable. Однако реализация AsImmutableEnumerable в ImmutableArrayList или любой другой неизменной коллекции переопределяет это определение, чтобы просто возвращать себя.

Если код содержит коллекцию Enumerable и хочет сделать ее снимок это может вызвать AsImmutableEnumerable. Если коллекция является изменяемой, этот метод будет копировать содержимое коллекции в новый неизменяемый объект, но если коллекция уже неизменна, дублировать ее содержимое не нужно.

...