วิธีหลีกเลี่ยงปัญหาที่เหมาะสมสำหรับการสืบทอดหลาย ๆ อย่างใน Java (Android)


15

ฉันมีปัญหาด้านแนวคิดเกี่ยวกับการติดตั้งโค้ดอย่างเหมาะสมซึ่งดูเหมือนว่าต้องใช้การสืบทอดหลายแบบซึ่งจะไม่เป็นปัญหาในหลายภาษา OO แต่เนื่องจากโปรเจ็กต์สำหรับ Android ไม่มีสิ่งเช่นหลายextendsอย่าง

ฉันมีพวงของกิจกรรมที่ได้มาจากฐานเรียนที่แตกต่างกันเช่นที่เรียบง่ายActivity, TabActivity, ListActivity, ExpandableListActivityฯลฯ นอกจากนี้ฉันมีเศษรหัสบางอย่างที่ฉันต้องไปยังสถานที่ลงในonStart, onStop,onSaveInstanceState , onRestoreInstanceStateและอื่น ๆ ที่จัดการเหตุการณ์มาตรฐานในทุกกิจกรรม

ถ้าฉันมีคลาสฐานเดียวสำหรับทุกกิจกรรมฉันจะวางโค้ดลงในคลาสที่ได้รับมาจากระดับกลางพิเศษจากนั้นสร้างกิจกรรมทั้งหมดที่ขยายออกมา น่าเสียดายที่นี่ไม่ใช่กรณีเนื่องจากมีหลายคลาสพื้นฐาน แต่การวางส่วนของรหัสเดียวกันลงในคลาสกลางหลาย ๆ คลาสนั้นไม่ใช่วิธีที่จะทำได้

อีกวิธีหนึ่งคือการสร้างวัตถุผู้ช่วยและมอบหมายการโทรเหตุการณ์ทั้งหมดที่กล่าวไว้ข้างต้นให้กับผู้ช่วย แต่สิ่งนี้ต้องการวัตถุผู้ช่วยที่จะรวมและตัวจัดการทั้งหมดจะถูกกำหนดใหม่ในชั้นกลางทั้งหมด ดังนั้นจึงไม่มีความแตกต่างกับวิธีแรกที่นี่ - ยังคงมีการทำสำเนารหัสจำนวนมาก

หากมีสถานการณ์คล้ายกันเกิดขึ้นใน Windows ฉันต้องการคลาสย่อยฐานรอง (สิ่งที่ "สอดคล้อง" กับ Activityคลาสใน Android) และดักจับข้อความที่เหมาะสมที่นั่น (ในที่เดียว)

Java / Android สามารถทำอะไรได้บ้าง? ฉันรู้ว่ามีเครื่องมือที่น่าสนใจเช่นเครื่องมือJava ( พร้อมตัวอย่างจริง ) แต่ฉันไม่ใช่กูรู Java และไม่แน่ใจว่ามันคุ้มค่าที่จะลองในกรณีเฉพาะนี้หรือไม่

หากฉันพลาดวิธีแก้ปัญหาที่เหมาะสมอื่น ๆ โปรดพูดถึงพวกเขา

UPDATE:

สำหรับผู้ที่อาจมีความสนใจในการแก้ปัญหาเดียวกันใน Android ฉันพบวิธีแก้ปัญหาง่ายๆ มีอยู่แอพลิเคชันระดับซึ่งมีในสิ่งอื่น ๆ อินเตอร์เฟซActivityLifecycleCallbacks มันเป็นสิ่งที่ฉันต้องการทำให้เราสามารถดักและเพิ่มคุณค่าบางอย่างในเหตุการณ์สำคัญสำหรับทุกกิจกรรม ข้อเสียเปรียบเพียงอย่างเดียวของวิธีนี้คือมีให้ตั้งแต่เริ่มระดับ API 14 ซึ่งไม่เพียงพอในหลายกรณี (การรองรับ API ระดับ 10 เป็นข้อกำหนดทั่วไปในปัจจุบัน)

คำตอบ:


6

ฉันเกรงว่าคุณจะไม่สามารถใช้ระบบ classs ของคุณได้โดยไม่ต้องใช้รหัสใน Android / Java

อย่างไรก็ตามคุณสามารถลด codeduplication เมื่อคุณจองพิเศษเรียนมากลางกับ วัตถุตัวช่วยคอมโพสิต สิ่งนี้เรียกว่า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 ....
    }

ดังนั้นทุกคลาสพื้นฐานที่คุณสร้าง baseclass ขั้นกลางที่มอบหมายการเรียกไปยังผู้ช่วย ระดับกลางพื้นฐานเหมือนกันยกเว้น baseclase ที่พวกเขาสืบทอด


1
ใช่ขอบคุณ. decordator patternฉันรู้ว่า นี่เป็นทางเลือกสุดท้ายซึ่งจริง ๆ แล้วแสดงให้เห็นถึงสิ่งที่ฉันต้องการหลีกเลี่ยง - การทำสำเนารหัส ฉันจะยอมรับคำตอบของคุณหากไม่มีความคิดที่น่าประหลาดใจ ฉันสามารถใช้ยาสามัญเพื่อสรุปรหัสของ "คนกลาง" ได้หรือไม่?
Stan

6

ฉันคิดว่าคุณกำลังพยายามหลีกเลี่ยงการทำสำเนารหัสผิดประเภท ฉันเชื่อว่า Michael Feathers เขียนบทความเกี่ยวกับเรื่องนี้ แต่น่าเสียดายที่ฉันไม่พบมัน วิธีที่เขาอธิบายมันคือคุณสามารถนึกถึงโค้ดของคุณว่ามีสองส่วนเช่นส้ม: เปลือกและเยื่อกระดาษ เปลือกเป็นสิ่งที่ต้องการประกาศวิธีการประกาศเขตประกาศระดับ ฯลฯ เยื่อกระดาษเป็นสิ่งที่อยู่ภายในวิธีการเหล่านี้ การดำเนินการ

เมื่อพูดถึง 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

สิ่งนี้สามารถ refactored เป็นนี้:

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

เราเพิ่มเปลือกจำนวนมากในการรีแฟคเตอร์นี้ แต่ตัวอย่างที่สองมีความสะอาดกว่ามากmethod()กว่าโดยมีการละเมิด DRY น้อยกว่า

คุณบอกว่าคุณต้องการหลีกเลี่ยงรูปแบบมัณฑนากรเพราะมันจะนำไปสู่การทำสำเนารหัส หากคุณดูภาพในลิงค์นั้นคุณจะเห็นว่ามันจะทำซ้ำoperation()ลายเซ็นเท่านั้น(เช่น: เปลือก) การoperation()ใช้งาน (เยื่อกระดาษ) ควรแตกต่างกันสำหรับแต่ละชั้นเรียน ฉันคิดว่าโค้ดของคุณจะจบลงด้วยความสะอาดยิ่งขึ้นและมีการทำซ้ำของเยื่อกระดาษน้อยลง


3

คุณควรจะชอบองค์ประกอบมากกว่ามรดก ตัวอย่างที่ดีคือ IExtension " pattern " ใน. NET WCF framework โดยทั่วไปคุณมี 3 อินเตอร์เฟส IExtension, IExtensibleObject และ IExtensionCollection จากนั้นคุณสามารถเขียนพฤติกรรมที่แตกต่างกับวัตถุ IExtensibleObject โดย addig IExtension อินสแตนซ์ของมันเป็นคุณสมบัติส่วนขยายของ IExtensionCollection ใน java มันควรมีลักษณะเช่นนั้นไม่ใช่ว่าคุณต้องสร้างการใช้งาน IExtensioncollection ของคุณเองที่เรียกเมธอด attach และ detach เมื่อไอเท็มถูกเพิ่ม / ลบ นอกจากนี้โปรดทราบว่ามันขึ้นอยู่กับคุณที่จะกำหนดจุดส่วนขยายในคลาสที่ขยายได้ของคุณตัวอย่างใช้กลไกการเรียกกลับที่คล้ายเหตุการณ์:

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 แต่ฉันพบคำตอบนี้ยากที่จะเข้าใจ คุณสามารถเพิ่มตัวอย่างการใช้สามอินเตอร์เฟสเหล่านี้ได้หรือไม่
Daniel Kaplan

ขอบคุณที่น่าสนใจมาก แต่จะง่ายกว่าหรือไม่เพียงลงทะเบียน callbacks ของส่วนขยายในวัตถุที่ขยายได้ บางอย่างเช่นที่processor.addHandler(console)มีให้ซึ่งConsoleProcessorจะใช้ส่วนติดต่อกลับ รูปแบบ "ส่วนขยาย" ดูเหมือนมิกซ์อินvisitorและdecoratorมันจำเป็นไหมในกรณีนี้?
สแตน

หากคุณลงทะเบียนส่วนขยายโดยตรงในตัวประมวลผล (== ExtensibleObject) คุณมีถ้วยแน่น แนวคิดในที่นี้คือการมีส่วนขยายที่สามารถนำกลับมาใช้ใหม่ระหว่างวัตถุที่ขยายได้ด้วยจุดส่วนขยายเดียวกัน ในความเป็นจริงรูปแบบเป็นโมราเหมือนการเลียนแบบรูปแบบมิกซ์
m0sa

อืมฉันไม่แน่ใจว่าการเชื่อมต่อโดยส่วนต่อประสานนั้นเป็นข้อต่อที่แน่นหนา สิ่งที่น่าสนใจที่สุดคือแม้โดยรูปแบบการขยายทั้งวัตถุที่ขยายได้และส่วนขยายนั้นยังต้องอาศัยอินเทอร์เฟซผู้ปฏิบัติงานเดียวกัน ("โทรกลับ") ดังนั้นการเชื่อมต่อยังคงเหมือนเดิม imho กล่าวอีกนัยหนึ่งฉันไม่สามารถเสียบส่วนขยายที่มีอยู่ลงในวัตถุที่ขยายได้ใหม่โดยไม่ต้องเข้ารหัสเพื่อสนับสนุนส่วนต่อประสานผู้ปฏิบัติงานใน "ตัวประมวลผล" หาก "จุดส่วนขยาย" หมายถึงส่วนต่อประสานกับผู้ปฏิบัติงานจริงฉันก็ไม่เห็นความแตกต่าง
สแตน

0

การเริ่มต้นใน Android 3.0 อาจเป็นไปได้ที่จะแก้ปัญหานี้ได้อย่างสวยงามโดยใช้Fragmentเศษแฟรกเมนต์มีการโทรกลับตลอดวงจรของตนเองและสามารถวางไว้ในกิจกรรมได้ ฉันไม่แน่ใจว่าสิ่งนี้จะใช้ได้กับกิจกรรมทั้งหมดหรือไม่

ตัวเลือกอื่นที่ฉันยังไม่แน่ใจเกี่ยวกับ (ขาดความรู้ในเชิงลึกของ Android) อาจใช้มัณฑนากรในทางตรงกันข้าม k3b แนะนำ: สร้างActivityWrapperวิธีการโทรกลับที่มีรหัสทั่วไปของคุณแล้วส่งต่อไปยังActivityวัตถุที่ถูกห่อชั้นเรียนการใช้งานจริง) แล้วให้ Android เริ่ม wrapper นั้น


-3

เป็นความจริงที่ว่า Java ไม่อนุญาตให้มีการสืบทอดหลาย ๆ อย่าง แต่คุณสามารถจำลองมันได้มากหรือน้อยโดยการทำให้คลาสย่อยย่อย SomeActivity แต่ละอันของคุณขยายคลาสกิจกรรมเดิม

คุณจะมีสิ่งที่ชอบ:

public class TabActivity extends Activity {
    .
    .
    .
}

2
นี่คือสิ่งที่คลาสทำอยู่แล้ว แต่คลาสฐานกิจกรรมเป็นส่วนหนึ่งของ Android API และไม่สามารถเปลี่ยนแปลงได้โดยผู้พัฒนา
Michael Borgwardt
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.