Шаблон проектирования для реализации по умолчанию с пустыми методами

Существует ли конкретный шаблон проектирования, описывающий сценарий, в котором предусмотрена неабстрактная реализация по умолчанию, которая реализует все или некоторые методы интерфейса с пустыми реализациями NO-OP. Это делается с целью облегчения подклассов с бременем реализации методов, которые сами могут не нуждаться / не использовать:

public interface MyInterface {
    public void doThis();
    public void doThat();
    public void done();
}

public class MyClass implements MyInterface {
    public void doThis() {
        // NO-OP
    }
    public void doThat() {
        // NO-OP
    }
    public void done() {
        // Some standard implementation
    }
}

public class MuSubClass extends MyClass {
    public void doThat() {
        // Subclass only cares about doThat()
    }
}

Я видел, как этот шаблон использовался несколько раз, включая Java DefaultHandler в платформе SAX и MouseAdapter. В некоторых случаях такие классы называются адаптерами, но у меня сложилось впечатление, что шаблон адаптера транслируется между двумя различными интерфейсами.

Учитывая, что в этих случаях существует только один объявленный интерфейс, который переводится в неопределенное подмножество этого интерфейса - мне не ясно, как это происходит в духе шаблона адаптера.

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

9 ответов

Решение

Для реализации по умолчанию отсутствуют шаблоны проектирования.

Я обычно добавляю DoNothing префикс к названию класса. В зависимости от намерения я использую также Base или же Default (последний широко используется). Наверное MouseAdapter должен быть назван DefaultMouseListener,

В случае, если вам не безразлично, вы можете систематически заглушить интерфейс с помощью простого DynamicProxy, вы должны возвращать только "хорошее" значение по умолчанию (ноль для объекта, 0 для числа и т. Д.).

Кстати, это очень хороший вопрос.

РЕДАКТИРОВАТЬ

Кроме того, это не Stub или Mock: может быть, его можно спутать с Stub, но намерение другое.

Вы должны следовать другому принципу дизайна: принцип разделения интерфейса

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

Вы не должны реализовывать БОЛЬШЕ И вы не должны реализовывать МЕНЬШЕ

Посмотрите на связанные вопросы SE для более подробной информации.

Принцип разделения интерфейса

Принцип разделения интерфейса - программа для интерфейса

Он также используется в Swing (WindowAdapter, который реализует WindowListener). Это всего лишь удобный адаптер, вам нужно только определить 1-2 метода таким образом, чтобы получить полезный оконный список. Это действительно пример шаблона Adapter, также демонстрирующий силу абстрактных классов. Это даже пример, иллюстрирующий, почему множественное наследование реализации иногда полезно.

Что касается обычных шаблонов проектирования, то в методе Temlate вы можете определить операции подключения, которые могут быть переопределены (в отличие от абстрактных методов, которые должны быть), но поведение по умолчанию (обычно NO-OP) также имеет смысл.

Я видел этот дизайн, используемый весной, когда у них есть класс с именем FlowExecutionListenerAdapter, который спасает вас от реализации всех операций FlowExecutionListener.

Тем не менее, это звучит как Null Object Pattern тоже. Однако я чувствую, что он лучше в мире адаптеров, потому что он меняет поведение интерфейса, позволяя вам реализовывать только тот бит, который вы хотите... но он сложный.

Я уверен, что этот вопрос задавался раньше?

Это звучит похоже нет? может быть стоит прочитать.

Отличный вопрос

Я начал использовать NoOp в качестве префикса имени класса для этого шаблона. Это коротко, ясно, и не перегружено (как Empty [ничего не содержит?], Null [Шаблон нулевого объекта, который отличается?], Abstract [Это обеспечивает некоторую реализацию?], Или Base [Обеспечивает ли это некоторую реализацию?]).

Я могу написать этот стиль класса, когда у меня есть сторонний API, который предоставляет "крючки" для ниструментации во время сложной операции. Рассмотрим следующие два класса, предоставляемые библиотекой:

public class LongRunningActionRunner {
    public void runSomethingLong(DecisionListener cdh) {
        // ...
    }
}

public interface DecisionListener {
    public void beforeFooHook();
    public void afterFooHook();
    public void beforeBarHook();
    public void afterBarHook();
    public void beforeBazHook();
    public void afterBazHook();
}

В этом случае вы можете исправить класс, используя этот шаблон следующим образом:

public class NoOpDecisionListener implements DecisionListener {
    @Override public Something beforeFooHook() {}
    @Override public Something afterFooHook() {}
    @Override public Something beforeBarHook() {}
    @Override public Something afterBarHook() {}
    @Override public Something beforeBazHook() {}
    @Override public Something afterBazHook() {}
}

Эта модель была распространена в более старых версиях Java. Это альтернатива Java 7 методам по умолчанию в интерфейсах.

Джош Блох называет это реализацией скелета. Хотя реализация скелета обычно абстрактна, вам не нужно заставлять клиентов создавать подкласс, если сам скелет достаточен.

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

Я полагаю, что Мартин Фаулер назвал бы это шаблоном нулевого объекта. В своей книге "Рефакторинг" [1] Мартин вводит нулевые объекты как таковые:

Суть полиморфизма заключается в том, что вместо того, чтобы спрашивать объект, какой у него тип, а затем вызывать какое-то поведение, основанное на ответе, вы просто вызываете поведение. Объект, в зависимости от его типа, поступает правильно. Одно из менее интуитивных мест для этого - где у вас есть нулевое значение в поле.

Позже он добавляет: "Вам выгодно, когда многие клиенты хотят делать то же самое; они могут просто полагаться на поведение по умолчанию, равное нулю". Он также представляет метод isNull() для клиентов, которым требуется различное поведение.

Я согласен, что иногда я вижу (часто абстрактную) реализацию, называемую адаптером. Например, в платформе Android AnimatorListenerAdapter (исходный код здесь) описывается так:

Этот класс адаптера предоставляет пустые реализации методов из Animator.AnimatorListener. Любой пользовательский слушатель, который заботится только о подмножестве методов этого слушателя, может просто создать подкласс этого класса адаптера вместо непосредственной реализации интерфейса.

[1] "Рефакторинг: улучшение дизайна существующего кода", Глава 9, "Упрощение условных выражений", "Введение нулевого объекта".

Вы спрашиваете о шаблоне нулевого объекта?

В дополнение к вашему редактированию, MyClass Объект - не более чем реализация по умолчанию. Я не думаю, что есть какой-то конкретный шаблон дизайна, который описывает это.

Мне это кажется наиболее близким к шаблону Special Case или Null Object.

Ваши обновления предполагают что-то похожее на шаблонный метод, ожидайте, что у вас нет ни одного метода, вызывающего каждый шаблонный метод, например

public void doEverything()
{
  doThis();
  doThat();
  done();
}
Другие вопросы по тегам