web-dev-qa-db-ja.com

Java(Android)の多重継承の適切な回避策

多重継承が必要と思われるコードの適切な実装に関する概念的な問題があります。これは多くのOO言語では問題にならないでしょうが、プロジェクトはAndroid用であるため、そのようなことはありません複数のextendsのように。

単純なActivityTabActivityListActivityExpandableListActivityなど、さまざまな基本クラスから派生した多数のアクティビティがあります。また、いくつかのすべてのアクティビティでonStartonStoponSaveInstanceStateonRestoreInstanceStateおよびその他の標準イベントハンドラーに配置する必要があるコードフラグメント。

すべてのアクティビティに単一の基本クラスがある場合、コードを特別な中間派生クラスに配置し、それを拡張するすべてのアクティビティを作成します。残念ながら、複数の基本クラスがあるため、これは当てはまりません。しかし、コードの同じ部分をいくつかの中間クラスに配置することは、進むべき道ではありません、imho。

別のアプローチは、ヘルパーオブジェクトを作成し、上記のイベントのすべての呼び出しをヘルパーに委任することです。ただし、これにはヘルパーオブジェクトを含める必要があり、すべてのハンドラーをすべての中間クラスで再定義する必要があります。したがって、ここでの最初のアプローチに大きな違いはありません。それでも多くのコードが重複しています。

Windowsで同様の状況が発生した場合は、基本クラス(AndroidのActivityクラスに「対応する」もの)をサブクラス化し、適切なメッセージをそこ(1か所)にトラップします。

このためにJava/Androidで何ができますか? Javaインストルメンテーション実際の例をいくつか )などの興味深いツールがあることは知っていますが、Javaの第一人者ではありません。この特定のケースで試してみる価値があるかどうかはわかりません。

他のきちんとした解決策を見逃した場合は、それらについて言及してください。

UPDATE:

Androidで同じ問題を解決することに興味がある可能性のある人のために、私は簡単な回避策を見つけました。とりわけ、 Application クラスが存在します。インターフェース ActivityLifecycleCallbacks 。これは、すべてのアクティビティの重要なイベントをインターセプトしていくつかの値を追加するために必要なことを正確に実行します。このメソッドの唯一の欠点は、APIレベル14から使用できることです。多くの場合、十分ではありません(APIレベル10のサポートは、今日の一般的な要件です)。

16
Stan

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 ....
    }

したがって、すべての基本クラスを作成して、その呼び出しをヘルパーに委任する中間基本クラスを作成します。中間ベースクラスは、継承元のベースクラスを除いて同一です。

6
k3b

間違ったタイプのコードの重複を避けようとしていると思います。 Michael Feathersがこれに関する記事を書いたと思いますが、残念ながら見つかりません。彼の説明によると、コードはオレンジのような2つの部分(皮と果肉)を持つと考えることができます。外皮はメソッド宣言、フィールド宣言、クラス宣言などのようなものです。パルプはこれらのメソッド内のものです。実装。

DRYに関しては、pulpの重複を避けたいです。しかし、多くの場合、その過程でより多くの皮を作成します。そして、それは大丈夫です。

次に例を示します。

_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
_

このリファクタリングで多くの皮を追加しました。しかし、2番目の例には、より明確なmethod()があり、DRY違反は少なくなっています。

decorator pattern はコードの重複につながるため、避けたいと述べました。そのリンクの画像を見ると、operation()署名(つまり、rind)のみが複製されていることがわかります。 operation()実装(pulp)は、クラスごとに異なる必要があります。結果として、コードはよりクリーンになり、パルプの重複が少なくなると思います。

6
Daniel Kaplan

継承よりも構成を優先する必要があります。良い例は、.NET WCFフレームワークのIExtension " pattern "です。基本的に、IExtension、IExtensibleObject、IExtensionCollectionの3つのインターフェイスがあります。次に、IExtensibleObjectオブジェクトを使用して compose 異なるbehaviorsを、Extension IExtensionCollectionプロパティにIExtensionインスタンスを追加します。 Javaのように見えるはずですが、アイテムが追加/削除されているときにattachメソッドとdetachメソッドを呼び出す独自の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]);
    }
}

この方法には、クラス間で共通の拡張ポイントを抽出できた場合、拡張を再利用できるという利点があります。

3
m0sa

Android 3.0以降、 Fragment を使用してエレガントに解決できる場合があります。フラグメントには独自のライフサイクルコールバックがあり、アクティビティ内に配置できます。私はmこれがすべてのイベントで機能するかどうかはわかりません。

(詳細なAndroidノウハウがない)もわからない)別のオプションは、k3bが提案する反対の方法でデコレーターを使用することかもしれません:ActivityWrapperを作成しますコールバックメソッドには一般的なコードが含まれ、ラップされたActivityオブジェクト(実際の実装クラス)に転送され、Androidがそのラッパーを開始します。

0