Правильне рішення для багаторазового спадкування на Java (Android)


15

У мене є концептуальна проблема з правильною реалізацією коду, який, здається, вимагає багаторазового успадкування, це не буде проблемою для багатьох мов OO, але оскільки проект призначений для Android, не існує такого поняття, як кілька extends.

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

Якщо у мене є єдиний базовий клас для всіх видів діяльності, я ставлю код у спеціальний проміжний похідний клас, а потім створюю всі дії, що розширюють його. На жаль, це не так, оскільки існує кілька базових класів. Але розміщення одних і тих же частин коду в декількох проміжних класах - це не спосіб, імхо.

Іншим підходом може бути створення об’єкта помічника та делегування всім викликам вищезазначених подій. Але для цього потрібно включити хелперний об’єкт, а всі обробники переробити у всіх проміжних класах. Отже, тут немає великої різниці у першому підході - ще багато дублікатів коду.

Якщо подібна ситуація сталася в Windows, я б підклас базовий клас (те, що "відповідає" Activityкласу в Android) і вловити відповідні повідомлення там (в одному місці).

Що для цього можна зробити в Java / Android? Я знаю, що є цікаві інструменти, такі як інструментарій Java ( з деякими реальними прикладами ), але я не є гуру Java, і не впевнений, чи варто спробувати в цьому конкретному випадку.

Якщо я пропустив якісь інші гідні рішення, будь ласка, згадайте їх.

ОНОВЛЕННЯ:

Для тих, хто може бути зацікавлений у вирішенні тієї ж проблеми в Android, я знайшов просте вирішення. Існує клас Application , який забезпечує, серед іншого, інтерфейс ActivityLifecycleCallbacks . Це робить саме те, що мені потрібно, дозволяючи нам перехоплювати та додавати певну цінність важливим подіям для всіх заходів. Єдиним недоліком цього методу є те, що він доступний починаючи з рівня 14 API, що у багатьох випадках недостатньо (підтримка 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 ....
    }

тому кожен базовий клас ви створюєте проміжний базовий клас, який делегує свої дзвінки помічнику. Проміжні базові класи однакові, за винятком базової бази, від якої вони успадковують.


1
Так дякую. Я знаю decordator pattern. Це в крайньому випадку, котрий насправді демонструє те, чого я хотів би уникати - дублювання коду. Я прийму вашу відповідь, якщо жодних інших натхненних ідей не буде. Чи можу я використати дженерики для узагальнення коду "проміжних продуктів"?
Стен

6

Я думаю, ви намагаєтесь уникнути неправильного типу дублювання коду. Я вважаю, що Майкл Пірс написав статтю про це, але, на жаль, я не можу його знайти. Так, як він це описує, ви можете вважати свій код таким, що має дві частини, як апельсин: шкірку і м’якоть. Корінь - це такі речі, як декларації методів, декларації поля, декларації класу тощо. Целюлоза - це матеріал, що знаходиться в цих методах; впровадження.

Якщо мова заходить про DRY, ви хочете уникнути дублювання м'якоті . Але часто в процесі ви створюєте більше шкірки. І це нормально.

Ось приклад:

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]);
    }
}

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


1
Можливо, це тому, що я не використовую .NET, але цю відповідь мені було важко зрозуміти. Чи можете ви додати приклад використання цих трьох інтерфейсів?
Даніель Каплан

Дякую, дуже цікаво Але чи не було б набагато простіше просто зареєструвати зворотні виклики розширень у розширюваному об'єкті? Щось на зразок processor.addHandler(console)того, ConsoleProcessorщо реалізовувало б сам інтерфейс зворотного виклику. Шаблон "розширення" схожий на міксин visitorта decorator, але чи потрібен він у цьому випадку?
Стен

Якщо ви реєструєте розширення безпосередньо в процесорі (== ExtensibleObject), у вас є чітка купірування. Ідея тут полягає в тому, щоб розширення, які можна повторно використовувати між розширюваними об'єктами з однаковими точками розширення. Насправді модель є моральною як моделювання моделей Mixin .
m0sa

Гм, я не впевнений, що прив'язка по інтерфейсу - це тісна сполука. Найцікавіше, що навіть за схемою розширення і розширюваний об'єкт, і розширення покладаються на один і той же робочий інтерфейс ("зворотний виклик"), тому зв'язок залишається тим самим, imho. Іншими словами, я не можу підключити існуюче розширення до нового розширюваного об'єкта без кодування для підтримки робочого інтерфейсу в "процесорі". Якщо "точка розширення" означає фактично робочий інтерфейс, я не бачу різниці.
Стен

0

Починаючи з Android 3.0, це можливо можливо вирішити елегантно за допомогою фрагмента . Фрагменти мають власні зворотні дзвінки життєвого циклу, і їх можна розмістити всередині діяльності. Я не впевнений, що це спрацює для всіх подій.

Ще один варіант, про який я також не впевнений (бракує поглибленого ноу-хау для Android), можливо, використовувати декоратор у зворотному порядку, який пропонує k3b: створити, ActivityWrapperчиї методи зворотного виклику містять ваш загальний код, а потім переслати до загорнутого Activityоб'єкта (ваш фактичні класи впровадження), а потім запустіть програму для запуску програми Android.


-3

Це правда, що Java не допускає багатонаступне успадкування, але ви можете імітувати її більш-менш, зробивши кожен з ваших підкласів SomeActivity розширення початкового класу Activity.

У вас вийде щось на кшталт:

public class TabActivity extends Activity {
    .
    .
    .
}

2
Це вже роблять класи, але базовий клас діяльності є частиною Android API і не може бути змінений розробниками.
Майкл Боргвардт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.