Правильный обходной путь для множественного наследования в Java (Android)

15

У меня есть концептуальная проблема с правильной реализацией кода, которая, по-видимому, требует множественного наследования, что не было бы проблемой во многих языках ОО, но поскольку проект для Android, такого понятия, как множественное, не существует extends.

У меня есть куча деятельности, полученная от различных базовых классов, таких как простой Activity, TabActivity, ListActivity, ExpandableListActivityи т.д. Кроме того, у меня есть некоторые фрагменты кода , которые мне нужно место в onStart, onStop, onSaveInstanceState, onRestoreInstanceStateи другие стандартные обработчик событий во всех видах деятельности.

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

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

Если бы подобная ситуация произошла в Windows, я бы создал подкласс базового класса (что-то, что «соответствует» Activityклассу в Android) и перехватывал там соответствующие сообщения (в одном месте).

Что для этого можно сделать в Java / Android? Я знаю, что есть интересные инструменты, такие как инструментарий Java ( с некоторыми реальными примерами ), но я не гуру Java и не уверен, стоит ли пытаться в этом конкретном случае.

Если я пропустил некоторые другие достойные решения, пожалуйста, упомяните их.

ОБНОВИТЬ:

Для тех, кто может быть заинтересован в решении той же проблемы в Android, я нашел простой обходной путь. Существует класс Application , который предоставляет, помимо прочего, интерфейс ActivityLifecycleCallbacks . Это делает именно то, что мне нужно, позволяя нам перехватывать и добавлять некоторую ценность в важные события для всех видов деятельности. Единственным недостатком этого метода является то, что он доступен начиная с уровня API 14, чего во многих случаях недостаточно (поддержка API уровня 10 является типичным требованием сегодня).

Стан
источник

Ответы:

6

Боюсь, вы не сможете реализовать свою классическую систему без дублирования кода в Android / Java.

Однако вы можете свести к минимуму дублирование кода, если объедините специальный промежуточный производный класс с составным вспомогательным объектом . Это называется Decorator_pattern :

    class ActivityHelper {
        Activity owner;
        public ActivityHelper(Activity owner){/*...*/}
        onStart(/*...*/){/*...*/}   
    }

    public class MyTabActivityBase extends TabActivity {
        private ActivityHelper helper;
        public MyTabActivityBase(/*...*/) {
            this.helper = new ActivityHelper(this);
        }

        protected void onStart() {
            super.onStart();
            this.helper.onStart();
        }
        // the same for onStop, onSaveInstanceState, onRestoreInstanceState,...
    }

    Public class MySpecialTabActivity extends MyTabActivityBase  {
       // non helper logic goes here ....
    }

поэтому каждый базовый класс вы создаете промежуточный базовый класс, который делегирует свои вызовы помощнику. Промежуточные базовые классы идентичны, за исключением базового класса, откуда они наследуются.

k3b
источник
1
Да спасибо. Я знаю decordator pattern. Это последнее средство, которое хотя на самом деле демонстрирует то, что я бы предпочел избежать - дублирование кода. Я приму ваш ответ, если нет других интересных идей. Могу ли я использовать дженерики для обобщения кода «промежуточных звеньев»?
Стэн
6

Я думаю, что вы пытаетесь избежать неправильного типа дублирования кода. Я верю, что Майкл Фезерс написал статью об этом, но, к сожалению, я не могу ее найти. Как он описывает это, вы можете думать о своем коде как о двух частях, похожих на оранжевый: кожура и мякоть. Оболочка - это такие вещи, как объявления методов, объявления полей, объявления классов и т. Д. Целлюлоза - это вещи внутри этих методов; реализация.

Когда дело доходит до СУХОГО, вы хотите избежать дублирования мякоти . Но часто в процессе вы создаете больше кожуры. И это нормально.

Вот пример:

public void method() { //rind
    boolean foundSword = false;
    for (Item item : items)
        if (item instanceof Sword)
             foundSword = true;
    boolean foundShield = false;
    for (Item item : items)
        if (item instanceof Shield)
             founShield = true;
    if (foundSword && foundShield)
        //...
}  //rind

Это может быть преобразовано в это:

public void method() {  //rind
    if (foundSword(items) && foundShield(items))
        //...
} //rind

public boolean foundSword(items) { //rind
    return containsItemType(items, Sword.class);
} //rind

public boolean foundShield(items) { //rind
    return containsItemType(items, Shield.class);
} //rind

public boolean containsItemType(items, Class<Item> itemClass) { //rind
    for (Item item : items)
        if (item.getClass() == itemClass)
             return true;
    return false;
} //rind

Мы добавили много кожуры в этот рефакторинг. Но, во втором примере намного чище method()с меньшими нарушениями.

Вы сказали, что хотите избежать шаблона декоратора, потому что это приводит к дублированию кода. Если вы посмотрите на изображение в этой ссылке, то увидите, что оно будет только дублировать operation()подпись (например, rind). operation()Реализации (пульпы) должны быть различными для каждого класса. Я думаю, что ваш код в результате станет чище и будет иметь меньшее дублирование.

Даниэль Каплан
источник
3

Вы должны предпочесть композицию наследству. Хорошим примером является « шаблон » IExtension в среде .NET WCF. По сути, у вас есть 3 интерфейса, IExtension, IExtensibleObject и IExtensionCollection. Затем вы можете составить различные варианты поведения с помощью объекта IExtensibleObject, добавив экземпляры IExtension в его свойство Extension IExtensionCollection. В Java это должно выглядеть примерно так, однако вам не нужно создавать собственную реализацию IExtensioncollection, которая вызывает методы присоединения и отсоединения при добавлении / удалении элементов. Также обратите внимание, что вам нужно определить точки расширения в вашем расширяемом классе. В примере используется механизм обратного вызова, похожий на событие:

import java.util.*;

interface IExtensionCollection<T> extends List<IExtension<T>> {
    public T getOwner();
}

interface IExtensibleObject<T> {
    IExtensionCollection<T> getExtensions();
}

interface IExtension<T> {
    void attach(T target);
    void detach(T target);
}

class ExtensionCollection<T>
    extends LinkedList<IExtension<T>>
    implements IExtensionCollection<T> {

    private T owner;
    public ExtensionCollection(T owner) { this.owner = owner; }
    public T getOwner() { return owner; }
    public boolean add(IExtension<T> e) {
        boolean result = super.add(e);
        if(result) e.attach(owner);
        return result;
    }
    // TODO override remove handler
}

interface ProcessorCallback {
    void processing(byte[] data);
    void processed(byte[] data);
}

class Processor implements IExtensibleObject<Processor> {
    private ExtensionCollection<Processor> extensions;
    private Vector<ProcessorCallback> processorCallbacks;
    public Processor() {
        extensions = new ExtensionCollection<Processor>(this);
        processorCallbacks = new Vector<ProcessorCallback>();
    }
    public IExtensionCollection<Processor> getExtensions() { return extensions; }
    public void addHandler(ProcessorCallback cb) { processorCallbacks.add(cb); }
    public void removeHandler(ProcessorCallback cb) { processorCallbacks.remove(cb); }

    public void process(byte[] data) {
        onProcessing(data);
        // do the actual processing;
        onProcessed(data);
    }
    protected void onProcessing(byte[] data) {
        for(ProcessorCallback cb : processorCallbacks) cb.processing(data);
    }
    protected void onProcessed(byte[] data) {
        for(ProcessorCallback cb : processorCallbacks) cb.processed(data);
    }
}

class ConsoleProcessor implements IExtension<Processor> {
    public ProcessorCallback console = new ProcessorCallback() {
        public void processing(byte[] data) {

        }
        public void processed(byte[] data) {
            System.out.println("processed " + data.length + " bytes...");
        }
    };
    public void attach(Processor target) {
        target.addHandler(console);
    }
    public void detach(Processor target) {
        target.removeHandler(console);
    }
}

class Main {
    public static void main(String[] args) {
        Processor processor = new Processor();
        IExtension<Processor> console = new ConsoleProcessor();
        processor.getExtensions().add(console);

        processor.process(new byte[8]);
    }
}

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

m0sa
источник
1
Может быть, это потому, что я не использую .NET, но я нашел этот ответ очень сложным для понимания. Можете ли вы добавить пример использования этих трех интерфейсов?
Даниэль Каплан
Спасибо, очень интересно. Но не проще ли зарегистрировать обратные вызовы расширений в расширяемом объекте? Нечто подобное processor.addHandler(console)при условии, что ConsoleProcessorбудет реализовывать сам интерфейс обратного вызова. Шаблон "extension" выглядит как миксин visitorи decorator, но так ли это необходимо в этом случае?
Стэн
Если вы регистрируете расширения непосредственно в процессоре (== ExtensibleObject), у вас жесткие ограничения. Идея здесь состоит в том, чтобы иметь расширения, которые можно повторно использовать между расширяемыми объектами с одинаковыми точками расширения. На самом деле паттерн похож на миксиновую симуляцию.
m0sa
Хм, я не уверен, что привязка интерфейсом - это тесная связь. Самое интересное, что даже по шаблону расширения и расширяемый объект, и расширение опираются на один и тот же рабочий интерфейс («обратный вызов»), поэтому связь остается неизменной, imho. Другими словами, я не могу подключить существующее расширение к новому расширяемому объекту без кодирования для поддержки рабочего интерфейса в «процессоре». Если «точка расширения» означает фактически рабочий интерфейс, то я не вижу разницы.
Стан
0

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

Другой вариант, в котором я также не уверен (не имея глубоких ноу-хау в Android), может заключаться в том, чтобы использовать Decorator противоположным способом, который предлагает k3b: создайте ActivityWrapperметоды обратного вызова, содержащие ваш общий код, и затем перенаправьте на обернутый Activityобъект (ваш фактические классы реализации), а затем Android запустить эту оболочку.

Майкл Боргвардт
источник
-3

Это правда, что Java не допускает множественного наследования, но вы можете более или менее смоделировать его, сделав каждый из ваших подклассов SomeActivity расширением исходного класса Activity.

У вас будет что-то вроде:

public class TabActivity extends Activity {
    .
    .
    .
}
Samer
источник
2
Это то, что классы уже делают, но базовый класс Activity является частью API Android и не может быть изменен разработчиками.
Майкл Боргвардт