Solución adecuada para la herencia múltiple en Java (Android)

15

Tengo un problema conceptual con una implementación correcta de código que parece requerir herencia múltiple, eso no sería un problema en muchos idiomas OO, pero como el proyecto es para Android, no existe tal cosa como extends .

Tengo un montón de actividades, derivadas de diferentes clases básicas, como Activity , TabActivity , ListActivity , ExpandableListActivity , etc. También tengo algunos fragmentos de código que necesito colocar en onStart , onStop , onSaveInstanceState , onRestoreInstanceState y otros controladores de eventos estándar en todas las actividades.

Si tengo una sola clase base para todas las actividades, ubicaría el código en una clase derivada intermedia especial y luego crearía todas las actividades extendiéndola. Desafortunadamente, este no es el caso, porque hay varias clases base. Pero colocar las mismas porciones de código en varias clases intermedias no es una manera de hacerlo, imho.

Otro enfoque podría ser crear un objeto de ayuda y delegar todas las llamadas de los eventos mencionados anteriormente a la ayuda. Pero esto requiere que se incluya el objeto auxiliar y que todos los controladores se redefinan en todas las clases intermedias. Por lo tanto, no hay mucha diferencia en el primer enfoque aquí, todavía hay muchos códigos duplicados.

Si ocurriera una situación similar en Windows, la subclase de clase base (algo que "corresponde" a la clase Activity en Android) y capturaría los mensajes apropiados allí (en un solo lugar).

¿Qué se puede hacer en Java / Android para esto? Sé que hay herramientas interesantes como instrumentación de Java ( con algunos ejemplos reales ), pero no soy un gurú de Java, y no estoy seguro de si vale la pena intentarlo en este caso específico.

Si me perdí otras soluciones decentes, por favor, mencionelas.

ACTUALIZAR :

Para aquellos que estén interesados en resolver el mismo problema en Android, he encontrado una solución simple. Existe la clase Aplicación , que proporciona, entre otras cosas, la interfaz ActivityLifecycleCallbacks . Hace exactamente lo que necesito, lo que nos permite interceptar y agregar algo de valor a eventos importantes para todas las actividades. El único inconveniente de este método es que está disponible a partir del nivel 14 del API, lo que no es suficiente en muchos casos (el soporte para el nivel 10 del API es un requisito típico en la actualidad).

    
pregunta Stan 23.12.2012 - 13:50

5 respuestas

6

Me temo que no puedes implementar tu sistema de clases sin duplicación codificada en Android / java.

Sin embargo, puede minimizar la duplicación codificada si combina clase especial intermedia derivada con objeto auxiliar de ayuda . Esto se llama 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 que cada clase base cree una clase de base intermedia que delegue sus llamadas al ayudante. Las clases de base intermedias son idénticas, excepto la base de la que se heredan.

    
respondido por el k3b 23.12.2012 - 16:38
6

Creo que estás intentando evitar el tipo incorrecto de duplicación de código. Creo que Michael Feathers escribió un artículo sobre esto, pero desafortunadamente no lo puedo encontrar. La forma en que lo describe es que puede pensar que su código tiene dos partes como una naranja: la cáscara y la pulpa. La corteza es la materia como declaraciones de método, declaraciones de campo, declaraciones de clase, etc. La pulpa es la materia dentro de estos métodos; la implementación.

Cuando se trata de DRY, debes evitar la duplicación de pulp . Pero a menudo en el proceso se crea más corteza. Y eso está bien.

Aquí hay un ejemplo:

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

Esto se puede refactorizar en esto:

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

Agregamos mucha cáscara en esta refactorización. Pero, el segundo ejemplo tiene un method() mucho más limpio con menos violaciones de DRY.

Dijiste que te gustaría evitar el patrón decorador porque conduce a la duplicación de código. Si miras la imagen en ese enlace, verás que solo duplicará la firma operation() (es decir, rind). La implementación de operation() (pulpa) debe ser diferente para cada clase. Creo que su código terminará más limpio como resultado y tendrá menos duplicación de celulosa.

    
respondido por el Daniel Kaplan 23.12.2012 - 21:18
3

Deberías preferir la composición sobre la herencia. Un buen ejemplo es la extensión de IE " pattern " en el marco .NET WCF. Básicamente tienes 3 interfaces, IExtension, IExtensibleObject y IExtensionCollection. Luego puede componer los diferentes comportamientos con un objeto IExtensibleObject al agregar instancias de IExtension a su propiedad Extension IExtensionCollection. En java, debería tener un aspecto similar, no obstante, debe crear su propia implementación de IExtensioncollection que llame a los métodos de conexión y separación cuando se agreguen o eliminen elementos. También tenga en cuenta que depende de usted definir los puntos de extensión en su clase extensible. El ejemplo utiliza un mecanismo de devolución de llamada similar a 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]);
    }
}

Este enfoque tiene el beneficio de la reutilización de la extensión si logras extraer puntos de extensión comunes entre tus clases.

    
respondido por el m0sa 23.12.2012 - 17:52
0

A partir de Android 3.0, puede ser posible resolver esto elegantemente usando un Fragmento . Los fragmentos tienen sus propias devoluciones de llamada de ciclo de vida y se pueden colocar dentro de una Actividad. Aunque no estoy seguro de que esto funcione para todos los eventos.

Otra opción de la que tampoco estoy seguro (a falta de un profundo conocimiento de Android) podría ser usar un Decorator de la manera opuesta que sugiere k3b: crear un ActivityWrapper cuyos métodos de devolución de llamada contengan su código común y luego enviar a un objeto Activity envuelto (sus clases de implementación reales), y luego haga que Android inicie ese contenedor.

    
respondido por el Michael Borgwardt 23.12.2012 - 17:51
-3

Es cierto que Java no permite la herencia múltiple, pero puedes simularlo más o menos haciendo que cada una de tus subclases de SomeActivity extienda la clase de actividad original.

Tendrás algo como:

public class TabActivity extends Activity {
    .
    .
    .
}
    
respondido por el Samer 23.12.2012 - 15:08

Lea otras preguntas en las etiquetas

Comentarios Recientes

Suponemos que un usuario tiene varias cadenas. El nombre es lo que necesitamos: name.appendString ("My name", []) memberFunction = [member1: length member2: length member3: comment (), member4: quotedBlank (), member5: thrownBlank (), member6: nil] en estas adiciones, se puede llamar al ayudante de clonación para obtener el miembro y no al mismo tiempo. var me = SimpleList (["name", "index"]) Superbug: el paso anidado no funciona si existen diferentes clases en el mismo ámbito. Esta solución parece coincidir con... Lee mas