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


15

複数の継承を必要とするコードの適切な実装に概念的な問題がありますが、これは多くのオブジェクト指向言語では問題になりませんが、プロジェクトはAndroid用であるため、multipleのようなものはありませんextends

私はそのような単純な異なる基底クラスから派生し、活動の束を持ってActivityTabActivityListActivityExpandableListActivity、などまた、私は私がに配置する必要があり、いくつかのコードの断片を持っているonStartonStoponSaveInstanceStateonRestoreInstanceStateおよびすべての活動で他の標準のイベントハンドラ。

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

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

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

このためにJava / Androidで何ができますか?Javaインストルメンテーション実際の例もあります)などの興味深いツールがあることは知っていますが、私はJavaの第一人者ではありません。

他の適切な解決策を見逃した場合は、それらに言及してください。

更新:

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

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

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

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

コードの重複を招くため、デコレータパターンを避けたいと言いました。そのリンクの画像を見ると、operation()署名を複製するだけです(つまり、rind)。operation()実装(パルプ)は、クラスごとに異なるべきです。結果として、コードはよりクリーンになり、パルプの重複が少なくなると思います。


3

継承よりも合成を優先する必要があります。良い例は、.NET WCFフレームワークのIExtension " パターン "です。Baiscallyには、IExtension、IExtensibleObject、IExtensionCollectionの3つのインターフェイスがあります。その後、IExtensionインスタンスをExtension IExtensionCollectionプロパティに追加することにより、IExtensibleObjectオブジェクトを使用てさまざまな動作構成できます。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]);
    }
}

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


1
たぶん、.NETを使用していないからかもしれませんが、この答えを理解するのは非常に難しいと感じました。これら3つのインターフェースの使用例を追加できますか?
ダニエルカプラン

ありがとう、とても興味深い。しかし、拡張機能のコールバックを拡張可能なオブジェクトに登録するだけで、はるかに簡単になりませんか?コールバックインターフェイス自体を実装するようなものがprocessor.addHandler(console)提供さConsoleProcessorれます。「拡張子」パターンのミックスインのようなルックスvisitordecorator、それは、この場合には必要なのか?
スタン

プロセッサにエクステンションを直接登録すると(== ExtensibleObject)、タイトなカップリングができます。ここでの考え方は、同じ拡張ポイントを持つ拡張可能なオブジェクト間で再利用できる拡張機能を用意することです。実際、パターンはミックスインパターンシミュレーションのようなモーラです。
m0sa

うーん、インターフェイスによるバインディングが密結合かどうかはわかりません。最も興味深いのは、拡張パターンによっても、拡張可能なオブジェクトと拡張機能の両方が同じワーカーインターフェイス(「コールバック」)に依存しているため、結合が同じままであるということです。つまり、「プロセッサ」のワーカーインターフェイスをサポートするためのコーディングなしでは、既存の拡張機能を新しい拡張可能なオブジェクトにプラグインできません。「拡張ポイント」が実際にワーカーインターフェイスを意味する場合、違いは見当たりません。
スタン

0

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

私もないよ別のオプションは、必ず(Androidのノウハウで、深欠く)については、K3Bを示唆して反対の方法でデコレータを使用することがあります作成しActivityWrapper、そのコールバックメソッド前方に包まに、あなたの共通のコードが含まれており、Activityオブジェクト(あなたを実際の実装クラス)、Androidにそのラッパーを開始させます。


-3

Javaが多重継承を許可していないことは事実ですが、SomeActivityサブクラスのそれぞれが元のActivityクラスを拡張することにより、多かれ少なかれそれをシミュレートできます。

次のようなものがあります。

public class TabActivity extends Activity {
    .
    .
    .
}

2
これはクラスがすでに行っていることですが、アクティビティの基本クラスはAndroid APIの一部であり、開発者が変更することはできません。
マイケルボルグワード
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.