Soluzione corretta per l'ereditarietà multipla in Java (Android)

15

Ho un problema concettuale con una corretta implementazione del codice che sembra richiedere un'ereditarietà multipla, che non sarebbe un problema in molti linguaggi OO, ma poiché il progetto è per Android, non esiste una cosa come multiplo extends .

Ho un sacco di attività, derivate da diverse classi di base, come semplice Activity , TabActivity , ListActivity , ExpandableListActivity , ecc. Inoltre ho alcuni frammenti di codice che devo inserire in onStart , onStop , onSaveInstanceState , onRestoreInstanceState e altri gestori di eventi standard in tutte le attività.

Se ho una singola classe base per tutte le attività, inserisco il codice in una speciale classe derivata intermedia e quindi creo tutte le attività estendendola. Sfortunatamente, questo non è il caso, perché ci sono più classi base. Ma posizionare le stesse porzioni di codice in diverse classi intermedie non è un modo per andare, imho.

Un altro approccio potrebbe essere quello di creare un oggetto helper e delegare tutte le chiamate degli eventi summenzionati all'helper. Ma ciò richiede che l'oggetto helper sia incluso e che tutti i gestori vengano ridefiniti in tutte le classi intermedie. Quindi non c'è molta differenza per il primo approccio qui - ancora molti duplicati di codice.

Se si verifica una situazione simile sotto Windows, sottoclassi la classe base (qualcosa che "corrisponde" alla classe Activity in Android) e intrappoli lì i messaggi appropriati (in un unico posto).

Cosa si può fare in Java / Android per questo? So che ci sono strumenti interessanti come strumentazione Java ( con alcuni esempi reali ), ma non sono un guru di Java e non sono sicuro che valga la pena provare in questo caso specifico.

Se mi mancassero altre soluzioni decenti, per favore, citale.

UPDATE:

Per coloro che potrebbero essere interessati a risolvere lo stesso problema in Android, ho trovato una soluzione semplice. Esiste la classe Application , che fornisce, tra le altre cose, l'interfaccia ActivityLifecycleCallbacks . Fa esattamente ciò di cui ho bisogno permettendoci di intercettare e aggiungere qualche valore in eventi importanti per tutte le attività. L'unico inconveniente di questo metodo è che è disponibile a partire dal livello API 14, che in molti casi non è sufficiente (il supporto per il livello API 10 è un requisito tipico oggi).

    
posta Stan 23.12.2012 - 13:50
fonte

5 risposte

6

Temo che tu non possa implementare il tuo sistema di classi senza codificauplicazione in android / java.

Tuttavia puoi ridurre al minimo l'errore di codifica se combini classe derivata intermedia speciale con oggetto helper composito . Questo è chiamato 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 ....
    }

così ogni classe base crei una base di livello intermedia che delega le sue chiamate all'helper. Le basesclasses intermedie sono identiche tranne le baseclase da cui ereditano.

    
risposta data 23.12.2012 - 16:38
fonte
6

Penso che tu stia cercando di evitare il tipo sbagliato di duplicazione del codice. Credo che Michael Feathers abbia scritto un articolo a riguardo, ma sfortunatamente non riesco a trovarlo. Il modo in cui lo descrive è che puoi pensare al tuo codice come se avesse due parti come un'arancia: la buccia e la polpa. La crosta è roba come dichiarazioni di metodi, dichiarazioni di campo, dichiarazioni di classe, ecc. La polpa è la roba dentro questi metodi; l'implemento.

Quando si tratta di asciugare, si vuole evitare di duplicare pulp . Ma spesso nel processo crei più buccia. E va bene

Ecco un esempio:

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

Questo può essere rifattorizzato in questo:

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

Abbiamo aggiunto un sacco di crosta in questo refactoring. Ma il secondo esempio ha un method() molto più pulito con meno violazioni DRY.

Hai detto che vorresti evitare il schema di decorazione perché porta alla duplicazione del codice. Se guardi l'immagine in quel link vedrai che duplicherà solo la operation() signature (es: crosta). L'implementazione operation() (pulp) dovrebbe essere diversa per ogni classe. Penso che il tuo codice finirà per essere più pulito e avere meno duplicati della polpa.

    
risposta data 23.12.2012 - 21:18
fonte
3

Dovresti preferire la composizione all'ereditarietà. Un bell'esempio è il file IExtension " " nel framework .NET WCF. Baiscally hai 3 interfacce, IExtension, IExtensibleObject e IExtensionCollection. Puoi quindi comporre i diversi comportamenti con un oggetto IExtensibleObject da addig istanze di IExtension alla sua proprietà Extension IExtensionCollection. In java dovrebbe sembrare qualcosa del genere, non è tuttavia necessario creare la propria implementazione di IExtensioncollection che richiama i metodi attach e stacca quando gli articoli vengono aggiunti / rimossi. Nota anche che spetta a te definire i punti di estensione nella tua classe estendibile L'esempio utilizza un meccanismo di callback simile ad un evento:

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

Questo approccio ha il vantaggio del riutilizzo dell'estensione se riesci ad estrarre punti di estensione comuni tra le tue classi.

    
risposta data 23.12.2012 - 17:52
fonte
0

A partire da Android 3.0, potrebbe essere possibile risolverlo in modo elegante utilizzando un frammento . I frammenti hanno i propri callback del ciclo di vita e possono essere inseriti in un'attività. Non sono sicuro che funzionerà comunque per tutti gli eventi.

Un'altra opzione di cui non sono sicuro (senza una conoscenza approfondita di Android) potrebbe essere quella di usare un decoratore in modo opposto suggerito da k3b: creare un ActivityWrapper i cui metodi di callback contengono il codice comune e poi inoltrano ad un oggetto Activity incartato (le tue effettive classi di implementazione), e poi avviare Android quel wrapper.

    
risposta data 23.12.2012 - 17:51
fonte
-3

È vero che Java non consente l'ereditarietà multipla, ma puoi simularlo più o meno facendo in modo che ognuna delle sottoclassi SomeActivity estenda la classe di attività originale.

Avrai qualcosa come:

public class TabActivity extends Activity {
    .
    .
    .
}
    
risposta data 23.12.2012 - 15:08
fonte

Leggi altre domande sui tag