Solução adequada para várias heranças em Java (Android)

15

Eu tenho um problema conceitual com uma implementação adequada de código que parece exigir herança múltipla, que não seria um problema em muitas linguagens OO, mas como o projeto é para o Android, não existe tal coisa como extends .

Eu tenho um monte de atividades, derivadas de diferentes classes base, como Activity , TabActivity , ListActivity , ExpandableListActivity , etc. Também tenho alguns fragmentos de código que preciso colocar em onStart , onStop , onSaveInstanceState , onRestoreInstanceState e outros manipuladores de eventos padrão em todas as atividades.

Se eu tiver uma única classe base para todas as atividades, colocaria o código em uma classe derivada intermediária especial e, em seguida, criaria todas as atividades que a estendessem. Infelizmente, isso não é o caso, porque existem várias classes base. Mas colocar as mesmas partes do código em várias classes intermediárias não é um caminho a percorrer, imho.

Outra abordagem poderia ser criar um objeto auxiliar e delegar todas as chamadas dos eventos acima mencionados ao auxiliar. Mas isso requer que o objeto auxiliar seja incluído e que todos os manipuladores sejam redefinidos em todas as classes intermediárias. Portanto, não há muita diferença para a primeira abordagem aqui - ainda muitas duplicatas de código.

Se uma situação semelhante ocorreu no Windows, eu subclasse a classe base (algo que "corresponde" à classe Activity no Android) e interceptar as mensagens apropriadas lá (em um único lugar).

O que pode ser feito em Java / Android para isso? Eu sei que existem ferramentas interessantes, como instrumentação Java ( com alguns exemplos reais ), mas eu não sou um guru de Java, e não tenho certeza se vale a pena tentar neste caso específico.

Se eu perdi algumas outras soluções decentes, por favor, mencione-as.

ATUALIZAÇÃO:

Para aqueles que podem estar interessados em resolver o mesmo problema no Android, encontrei uma solução simples. Existe a classe Aplicação , que fornece, entre outras coisas, a interface ActivityLifecycleCallbacks . Ele faz exatamente o que eu preciso, permitindo-nos interceptar e adicionar algum valor em eventos importantes para todas as atividades. A única desvantagem desse método é que ele está disponível a partir do nível de API 14, o que não é suficiente em muitos casos (o suporte para o nível de API 10 é um requisito típico hoje).

    
por Stan 23.12.2012 / 13:50
fonte

5 respostas

6

Eu temo que você não possa implementar seu sistema de classes sem a duplicação de código no android / java.

No entanto, você pode minimizar a duplicação de código se combinar classe derivada intermediária especial com objeto auxiliar composto . Isso é chamado de 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 ....
    }

para cada classe base, você cria uma baseclass intermediária que delega suas chamadas ao auxiliar. As classes de bases intermediárias são idênticas, exceto a base de onde elas herdam.

    
por 23.12.2012 / 16:38
fonte
6

Acho que você está tentando evitar o tipo errado de duplicação de código. Eu acredito que Michael Feathers escreveu um artigo sobre isso, mas infelizmente não consigo encontrá-lo. A maneira como ele descreve é que você pode pensar em seu código como tendo duas partes como uma laranja: a casca e a polpa. A casca é o material como declarações de método, declarações de campo, declarações de classe, etc. A polpa é o material dentro desses métodos; a implementação.

Quando se trata de DRY, você quer evitar duplicar pulp . Mas muitas vezes no processo você cria mais casca. E tudo bem.

Veja um exemplo:

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

Isso pode ser refatorado para isso:

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

Adicionamos muita casca nessa refatoração. Mas, o segundo exemplo tem um muito mais limpo method() com menos violações de DRY.

Você disse que gostaria de evitar o padrão de decorador porque isso leva à duplicação de código. Se você olhar a imagem nesse link, verá que ela apenas duplicará a assinatura operation() (ou seja, rind). A implementação operation() (pulp) deve ser diferente para cada classe. Acho que seu código acabará sendo mais limpo e terá menos duplicação de celulose.

    
por 23.12.2012 / 21:18
fonte
3

Você deve preferir composição sobre herança. Um bom exemplo é o IExtension " padrão " na estrutura do .NET WCF. Baiscally você tem 3 interfaces, IExtension, IExtensibleObject e IExtensionCollection. Você pode, então, compor os diferentes comportamentos em> com um objeto IExtensibleObject por addig IExtension para sua propriedade Extension IExtensionCollection. Em java, ele deve ser parecido com algo assim, mas não é necessário criar sua própria implementação de IExtensioncollection que chama os métodos attach e desanexar quando os itens estão sendo adicionados / removidos. Observe também que cabe a você definir os pontos de extensão em sua classe extensível. O exemplo usa um mecanismo de retorno de chamada semelhante a um 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]);
    }
}

Essa abordagem tem o benefício de reutilização de extensão se você conseguir extrair pontos de extensão comuns entre suas classes.

    
por 23.12.2012 / 17:52
fonte
0

A partir do Android 3.0, pode ser possível resolvê-lo elegantemente usando um Fragmento . Os fragmentos têm seus próprios retornos de ciclo de vida e podem ser colocados dentro de uma Activity. Não tenho certeza se isso funcionará para todos os eventos.

Outra opção que eu também não tenho certeza (falta de profundo conhecimento sobre Android) pode ser usar um Decorator da maneira oposta que o k3b sugere: criar um ActivityWrapper cujos métodos de retorno de chamada contenham seu código comum e então encaminhar para um objeto Activity empacotado (suas classes de implementação reais) e, em seguida, faça com que o Android inicie esse wrapper.

    
por 23.12.2012 / 17:51
fonte
-3

É verdade que o Java não permite herança múltipla, mas você pode simulá-lo mais ou menos, fazendo com que cada uma de suas subclasses de SomeActivity estenda a classe de atividade original.

Você terá algo como:

public class TabActivity extends Activity {
    .
    .
    .
}
    
por 23.12.2012 / 15:08
fonte