Solusi yang tepat untuk multi inheritence di Java (Android)

15

Saya memiliki masalah konseptual dengan implementasi kode yang tampaknya membutuhkan banyak pewarisan, yang tidak akan menjadi masalah dalam banyak bahasa OO, tetapi karena proyek ini untuk Android, tidak ada yang namanya multiple extends.

Saya memiliki banyak kegiatan, yang berasal dari kelas dasar yang berbeda, seperti sederhana Activity, TabActivity, ListActivity, ExpandableListActivity, dll Juga saya memiliki beberapa fragmen kode yang saya perlu tempat ke onStart, onStop, onSaveInstanceState, onRestoreInstanceStatedan event handler standar lainnya dalam semua kegiatan.

Jika saya memiliki kelas basis tunggal untuk semua kegiatan, saya akan menempatkan kode ke kelas turunan menengah khusus, dan kemudian membuat semua kegiatan memperluasnya. Sayangnya, ini bukan masalahnya, karena ada beberapa kelas dasar. Tetapi menempatkan bagian kode yang sama ke beberapa kelas menengah bukanlah cara yang bagus, imho.

Pendekatan lain bisa dengan membuat objek helper dan mendelegasikan semua panggilan peristiwa yang disebutkan di atas kepada helper. Tetapi ini membutuhkan objek helper untuk dimasukkan, dan semua penangan harus didefinisikan ulang di semua kelas menengah. Jadi tidak ada banyak perbedaan dengan pendekatan pertama di sini - masih banyak duplikat kode.

Jika situasi serupa terjadi di bawah Windows, saya akan subkelas kelas dasar (sesuatu yang "sesuai" dengan Activitykelas di Android) dan menjebak pesan yang sesuai di sana (di satu tempat).

Apa yang bisa dilakukan di Java / Android untuk ini? Saya tahu ada alat yang menarik seperti instrumentasi Java ( dengan beberapa contoh nyata ), tapi saya bukan guru Java, dan tidak yakin apakah perlu dicoba dalam kasus khusus ini.

Jika saya melewatkan beberapa solusi layak lainnya, tolong sebutkan.

MEMPERBARUI:

Bagi mereka yang mungkin tertarik untuk menyelesaikan masalah yang sama di Android, saya telah menemukan solusi sederhana. Ada kelas Aplikasi , yang menyediakan, antara lain, antarmuka ActivityLifecycleCallbacks . Itu melakukan persis apa yang saya butuhkan memungkinkan kita untuk mencegat dan menambahkan beberapa nilai ke dalam peristiwa penting untuk semua kegiatan. Satu-satunya kelemahan dari metode ini adalah bahwa itu tersedia mulai dari API level 14, yang tidak cukup dalam banyak kasus (dukungan untuk API level 10 adalah persyaratan umum hari ini).

Stan
sumber

Jawaban:

6

Saya khawatir Anda tidak dapat mengimplementasikan sistem class Anda tanpa codeduplication di android / java.

Namun Anda dapat meminimalkan codeduplication jika Anda menggabungkan kelas turunan menengah khusus dengan objek pembantu komposit . Ini disebut 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 ....
    }

jadi setiap kelas dasar Anda membuat baseclass perantara yang mendelegasikan panggilannya ke helper. Kelas dasar menengah identik kecuali baseclase dari mana mereka mewarisi.

k3b
sumber
1
Ya terima kasih. Saya tahu decordator pattern. Ini adalah pilihan terakhir, yang meskipun sebenarnya menunjukkan apa yang saya inginkan untuk dihindari - duplikasi kode. Saya akan menerima jawaban Anda, jika tidak ada ide instersting lain yang muncul. Bisakah saya menggunakan obat generik untuk menggeneralisasi kode "perantara"?
Stan
6

Saya pikir Anda mencoba menghindari jenis duplikasi kode yang salah. Saya percaya Michael Feathers menulis artikel tentang ini, tetapi sayangnya saya tidak dapat menemukannya. Cara dia menggambarkannya adalah Anda dapat menganggap kode Anda memiliki dua bagian seperti jeruk: Kulit buah dan bubur kertas. Kulitnya adalah hal-hal seperti deklarasi metode, deklarasi lapangan, deklarasi kelas, dll. Pulpa adalah hal-hal di dalam metode ini; pelaksanaan.

Ketika datang ke KERING, Anda ingin menghindari duplikasi bubur . Namun seringkali dalam proses Anda membuat lebih banyak kulit. Dan itu tidak masalah.

Ini sebuah contoh:

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

Ini dapat direactored menjadi ini:

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

Kami menambahkan banyak kulit dalam refactoring ini. Tapi, contoh kedua memiliki jauh lebih bersihmethod() sedikit dengan pelanggaran KERING.

Anda bilang ingin menghindari pola dekorator karena mengarah ke duplikasi kode. Jika Anda melihat gambar di tautan itu, Anda akan melihatnya hanya akan menggandakan operation()tanda tangan (yaitu: kulit). The operation()pelaksanaan (bubur kertas) harus berbeda untuk masing-masing kelas. Saya pikir kode Anda akan berakhir lebih bersih dan duplikasi bubur lebih sedikit.

Daniel Kaplan
sumber
3

Anda harus lebih suka komposisi daripada warisan. Contoh yang bagus adalah " pola " IExtension dalam kerangka .NET WCF. Secara umum Anda memiliki 3 antarmuka, IExtension, IExtensibleObject dan IExtensionCollection. Anda kemudian dapat menyusun perilaku yang berbeda dengan objek IExtensibleObject dengan menambahkan contoh IExtension ke properti Extension IExtensionCollection itu. Di java seharusnya terlihat seperti itu, namun Anda tidak harus membuat implementasi IExtensioncollection Anda sendiri yang memanggil metode melampirkan dan melepaskan ketika item ditambahkan / dihapus. Perhatikan juga bahwa itu tergantung pada Anda untuk menentukan titik ekstensi di kelas extensible Anda. Contoh ini menggunakan mekanisme panggilan balik seperti peristiwa:

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

Pendekatan ini memiliki manfaat penggunaan kembali ekstensi jika Anda berhasil mengekstrak poin ekstensi umum antara kelas Anda.

m0sa
sumber
1
Mungkin karena saya tidak menggunakan .NET, tetapi saya menemukan jawaban ini sangat sulit untuk dipahami. Bisakah Anda menambahkan contoh menggunakan tiga antarmuka ini?
Daniel Kaplan
Terima kasih, sangat menarik. Tetapi bukankah akan jauh lebih sederhana hanya dengan mendaftarkan callback ekstensi di objek yang dapat diperluas? Sesuatu seperti processor.addHandler(console)asalkan ConsoleProcessorakan mengimplementasikan antarmuka panggilan balik itu sendiri. Pola "ekstensi" terlihat seperti campuran dari visitordan decorator, tetapi apakah perlu dalam kasus ini?
Stan
Jika Anda mendaftarkan ekstensi secara langsung di Prosesor (== ExtensibleObject), Anda mengalami cupling ketat. Idenya di sini adalah untuk memiliki ekstensi yang dapat digunakan kembali antara objek yang dapat diperluas dengan titik ekstensi yang sama. Faktanya polanya seperti simulasi pola mixin .
m0sa
Hm, saya tidak yakin ikatan dengan antarmuka adalah kopling ketat. Hal yang paling menarik adalah bahwa bahkan dengan pola ekstensi baik objek yang dapat diperluas maupun ekstensi bergantung pada antarmuka pekerja yang sama ("callback"), sehingga sambungan tetap sama, imho. Dengan kata lain, saya tidak bisa menyambungkan ekstensi yang ada ke objek extensible baru tanpa pengkodean untuk dukungan antarmuka pekerja di "prosesor". Jika "ekstensi" berarti sebenarnya antarmuka pekerja, maka saya tidak melihat perbedaan.
Stan
0

Mulai di Android 3.0, dimungkinkan untuk menyelesaikan ini secara elegan dengan menggunakan Fragmen . Fragmen memiliki callback daur hidup mereka sendiri dan dapat ditempatkan di dalam suatu Kegiatan. Saya tidak yakin ini akan bekerja untuk semua acara.

Pilihan lain yang saya juga tidak yakin tentang (kekurangan pengetahuan Android yang mendalam) mungkin menggunakan Dekorator dengan cara yang berlawanan k3b menyarankan: membuat ActivityWrappermetode panggilan balik yang berisi kode umum Anda dan kemudian meneruskan ke Activityobjek yang dibungkus (Anda kelas implementasi aktual), dan kemudian Android memulai pembungkus itu.

Michael Borgwardt
sumber
-3

Memang benar bahwa Java tidak mengizinkan multiple-inheritance, tetapi Anda dapat mensimulasikannya lebih atau kurang dengan membuat masing-masing subkelas SomeActivity Anda memperpanjang kelas Aktivitas asli.

Anda akan memiliki sesuatu seperti:

public class TabActivity extends Activity {
    .
    .
    .
}
Samer
sumber
2
Ini adalah apa yang sudah dilakukan oleh kelas-kelas tersebut, tetapi kelas dasar Aktivitas adalah bagian dari API Android dan tidak dapat diubah oleh pengembang.
Michael Borgwardt