Java'da çoklu kalıtım için uygun geçici çözüm (Android)


15

Birçok OO dilinde bir sorun olmaz, çoklu miras gerektiren kod düzgün bir uygulama ile kavramsal bir sorun var, ama proje Android için olduğu gibi, çoklu gibi bir şey yok extends.

Böyle basit gibi farklı temel sınıflar, türetilmiş faaliyetlerin bir demet var Activity, TabActivity, ListActivity, ExpandableListActivity, vb Ayrıca ben içine yerden gereken bazı kod parçaları var onStart, onStop, onSaveInstanceState, onRestoreInstanceStatetüm faaliyetlerinde ve diğer standart olay işleyicileri.

Tüm etkinlikler için tek bir temel sınıf varsa, kodu özel bir ara türetilmiş sınıfa yerleştirmek ve sonra onu genişleten tüm faaliyetleri oluşturmak. Ne yazık ki, durum böyle değil, çünkü birden fazla temel sınıf var. Ancak aynı kod parçalarını birkaç ara sınıfa yerleştirmek bir yol değildir, imho.

Başka bir yaklaşım, bir yardımcı nesne oluşturmak ve yukarıda belirtilen olayların tüm çağrılarını yardımcıya devretmek olabilir. Ancak bu, yardımcı nesnenin dahil edilmesini ve tüm işleyicilerin tüm ara sınıflarda yeniden tanımlanmasını gerektirir. Yani burada ilk yaklaşımda pek bir fark yok - yine de çok sayıda kod kopyası.

Windows altında benzer bir durum ortaya çıkarsa, ben sınıfın alt sınıfını ( ActivityAndroid'de sınıfa karşılık gelen bir şey) alt gruplara alırdım ve orada uygun iletileri (tek bir yerde) yakalayabilirim.

Bunun için Java / Android'de neler yapılabilir? Java enstrümantasyonu ( bazı gerçek örneklerle ) gibi ilginç araçlar olduğunu biliyorum , ancak bir Java guru değilim ve bu özel durumda denemeye değer olup olmadığından emin değilim.

Başka iyi çözümleri de kaçırırsam, lütfen onlardan bahsedin.

GÜNCELLEME:

Android'de aynı sorunu çözmek isteyenler için basit bir çözüm buldum. Diğer şeylerin yanı sıra, ActivityLifecycleCallbacks arabirimini sağlayan Application sınıfı vardır . Tüm aktiviteler için önemli olaylara müdahale etmemize ve bir miktar değer katmamıza izin vermem gereken şeyi yapıyor. Bu yöntemin tek dezavantajı, birçok durumda yeterli olmayan API düzey 14'ten başlayarak kullanılabilir olmasıdır (API düzey 10 desteği bugün tipik bir gereksinimdir).

Yanıtlar:


6

Korkarım Android / Java'da kodlama yapmadan sınıf sisteminizi uygulayamazsınız.

Birleştirmek ancak sen codeduplication en aza indirmek özel ara türetilmiş bir sınıf ile birleşik yardımcı nesnesi . Buna Decorator_pattern denir :

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

böylece her temel sınıf, yardımcıya çağrılarını devreten bir ara taban sınıfı oluşturur. Ara taban sınıfları, miras aldıkları baseklaz hariç özdeştir.


1
Evet teşekkür ederim. Biliyorum decordator pattern. Bu aslında ne kaçınmak tercih ederim gösterir - kod çoğaltma son çare. Eğer başka bir fikir ortaya çıkmazsa cevabınızı kabul edeceğim. "Ara ürünlerin" kodunu genelleştirmek için jenerik ilaçları kullanabilir miyim?
Stan

6

Sanırım yanlış kod yinelemesinden kaçınmaya çalışıyorsunuz. Michael Feathers'ın bu konuda bir makale yazdığına inanıyorum ama maalesef bulamıyorum. Bunu tarif ettiği gibi, kodunuzu portakal gibi iki parçaya sahip olarak düşünebilirsiniz: Kabuk ve hamur. Kabuk, yöntem bildirimleri, alan bildirimleri, sınıf bildirimleri vb. Şeylerdir. Kağıt hamuru, bu yöntemlerin içindeki şeylerdir; hayata geçirme.

KURU söz konusu olduğunda, hamuru çoğaltmaktan kaçınmak istersiniz . Ama genellikle süreçte daha fazla kabuk yaratırsınız. Ve sorun değil.

İşte bir örnek:

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

Bu yeniden düzenlenebilir:

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

Bu yeniden düzenlemeye çok fazla kabuk ekledik. Ancak, ikinci örneğin method()daha az KURU ihlali olan çok daha temiz bir örneği var .

Dekoratör deseninden kaçınmak istediğinizi söylediniz çünkü kod çoğaltmasına neden oluyor. Bu bağlantıdaki resme bakarsanız, görüntünün yalnızca operation()imzayı çoğaltacağını görürsünüz (örn: kabuk). operation()Uygulaması (hamur) her sınıf için farklı olmalıdır. Kodunuzun sonuç olarak daha temiz sonuçlanacağını ve daha az pulpa çoğaltmasına sahip olacağını düşünüyorum.


3

Kompozisyonu kalıtım yerine tercih etmelisiniz. Bunun güzel bir örneği, .NET WCF çerçevesindeki IExtension " kalıbı " dır . Baiscally, IExtension, IExtensibleObject ve IExtensionCollection olmak üzere 3 arayüzünüz var. Daha sonra olabilir oluşturmak farklı davranışlar 's Uzatma IExtensionCollection özelliğine IExtension örneklerini addig tarafından IExtensibleObject nesneyle. Java'da böyle bir şey görünmelidir, ancak öğeler eklenirken / kaldırılırken ekleme ve ayırma yöntemlerini çağıran kendi IExtensioncollection uygulamanızı oluşturmanız gerekmez. Ayrıca, genişletilebilir sınıfınızdaki uzantı noktalarını tanımlamanın size bağlı olduğuna dikkat edin. Örnek, olay benzeri bir geri arama mekanizması kullanır:

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

Bu yaklaşım, sınıflarınız arasında ortak uzantı noktaları çıkarmayı başarırsanız uzantıyı yeniden kullanma avantajına sahiptir.


1
Belki .NET kullanmıyorum, ama bu cevabı anlamakta zorlandım. Bu üç arayüzü kullanmaya örnek verebilir misiniz?
Daniel Kaplan

Teşekkür ederim, çok ilginç. Ancak, uzantının geri aramalarını genişletilebilir nesneye kaydetmek daha kolay olmaz mıydı? Böyle bir şey processor.addHandler(console)sağlanan ConsoleProcessorkendisi arayüzü geri arama uygulamaya koyacak. "Uzatma" paterni bir karışım gibi görünür visitorve decoratorbu durumda gerekli midir?
Stan

Uzantıları doğrudan İşlemci'ye (== ExtensibleObject) kaydederseniz, sıkıca kaplanırsınız. Buradaki fikir, aynı uzantı noktalarına sahip genişletilebilir nesneler arasında yeniden kullanılabilecek uzantılara sahip olmaktır. Aslında desen, bir mixin desen simülasyonu gibi moradır .
m0sa

Hm, arayüzle bağlamanın sıkı bir bağlantı olduğundan emin değilim. En ilginç şey, uzatma modeli ile bile hem genişletilebilir nesnenin hem de uzantının aynı işçi arayüzüne ("geri arama") dayanması, bu nedenle kuplajın aynı kalması imho. Başka bir deyişle, "işlemci" alt arabirimini desteklemek için bir kodlama olmadan varolan uzantıyı yeni bir genişletilebilir nesneye takamıyorum. "Uzatma noktası" aslında çalışan arayüzü anlamına geliyorsa, bir fark görmüyorum.
Stan

0

Android 3.0'dan başlayarak, bunu bir Parça kullanarak zarif bir şekilde çözmek mümkün olabilir . Parçaların kendi yaşam döngüsü geri çağrıları vardır ve bir Faaliyetin içine yerleştirilebilirler. Bunun tüm etkinlikler için işe yarayacağından emin değilim.

Ayrıca emin değilim başka bir seçenek (derinlemesine Android know-how eksik) k3b ters yönde bir Dekoratör kullanmak olabilir: ActivityWrappergeri arama yöntemleri ortak kodunuzu içeren bir oluşturmak ve sonra sarılmış bir Activitynesneye (sizin gerçek uygulama sınıfları) ve ardından Android'in bu sarmalayıcıyı başlatmasını sağlayın.


-3

Java'nın çoklu kalıtıma izin vermediği doğrudur, ancak SomeActivity alt sınıflarınızın her birini orijinal Activity sınıfını genişleterek aşağı yukarı simüle edebilirsiniz.

Gibi bir şey olacak:

public class TabActivity extends Activity {
    .
    .
    .
}

2
Sınıfların zaten yaptığı budur, ancak Etkinlik tabanı sınıfı Android API'nin bir parçasıdır ve geliştiriciler tarafından değiştirilemez.
Michael Borgwardt
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.