Cách giải quyết phù hợp cho nhiều kế thừa trong Java (Android)


15

Tôi có một vấn đề về khái niệm với việc triển khai mã đúng, có vẻ như cần nhiều kế thừa, đó sẽ không phải là vấn đề trong nhiều ngôn ngữ OO, nhưng vì dự án dành cho Android, không có thứ gì giống như nhiều extends.

Tôi có một loạt các hoạt động, xuất phát từ các lớp cơ sở khác nhau, chẳng hạn như đơn giản Activity, TabActivity, ListActivity, ExpandableListActivity, vv Ngoài ra tôi có một số đoạn mã mà tôi cần phải ra vào onStart, onStop, onSaveInstanceState, onRestoreInstanceStatevà xử lý sự kiện tiêu chuẩn khác trong tất cả các hoạt động này.

Nếu tôi có một lớp cơ sở duy nhất cho tất cả các hoạt động, tôi sẽ đặt mã vào một lớp dẫn xuất trung gian đặc biệt, sau đó tạo tất cả các hoạt động mở rộng nó. Thật không may, đây không phải là trường hợp, bởi vì có nhiều lớp cơ sở. Nhưng việc đặt cùng một phần mã vào một số lớp trung gian không phải là một cách để đi, imho.

Một cách tiếp cận khác có thể là tạo ra một đối tượng trợ giúp và ủy thác tất cả các cuộc gọi của các sự kiện nêu trên cho người trợ giúp. Nhưng điều này đòi hỏi phải bao gồm đối tượng trợ giúp và tất cả các trình xử lý được xác định lại trong tất cả các lớp trung gian. Vì vậy, không có nhiều khác biệt so với cách tiếp cận đầu tiên ở đây - vẫn còn rất nhiều mã trùng lặp.

Nếu một tình huống tương tự xảy ra trong Windows, tôi sẽ là lớp cơ sở của lớp con (thứ gì đó "tương ứng" với Activitylớp trong Android) và bẫy các tin nhắn thích hợp ở đó (ở một nơi duy nhất).

Có thể làm gì trong Java / Android cho việc này? Tôi biết có các công cụ thú vị như thiết bị Java ( với một số ví dụ thực tế ), nhưng tôi không phải là một chuyên gia về Java và không chắc nó có đáng để thử trong trường hợp cụ thể này không.

Nếu tôi bỏ lỡ một số giải pháp tốt khác, xin vui lòng, đề cập đến chúng.

CẬP NHẬT:

Đối với những người có thể quan tâm đến việc giải quyết vấn đề tương tự trong Android, tôi đã tìm thấy một cách giải quyết đơn giản. Có tồn tại lớp Ứng dụng , cung cấp, trong số những thứ khác, giao diện ActivityLifecycleCallbacks . Nó thực hiện chính xác những gì tôi cần cho phép chúng tôi chặn và thêm một số giá trị vào các sự kiện quan trọng cho tất cả các hoạt động. Hạn chế duy nhất của phương pháp này là nó có sẵn bắt đầu từ API cấp 14, điều này không đủ trong nhiều trường hợp (hỗ trợ cho API cấp 10 là một yêu cầu điển hình hiện nay).

Câu trả lời:


6

Tôi e rằng bạn không thể thực hiện hệ thống lớp của mình mà không được mã hóa trong android / java.

Tuy nhiên, bạn có thể giảm thiểu trùng lặp mã hóa nếu bạn kết hợp lớp dẫn xuất trung gian đặc biệt với đối tượng trợ giúp tổng hợp . Điều này được gọi là Decorator_potype :

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

vì vậy mỗi lớp cơ sở bạn tạo ra một lớp cơ sở trung gian ủy nhiệm các cuộc gọi của nó cho người trợ giúp. Các lớp cơ sở trung gian giống hệt nhau ngoại trừ baseclase nơi chúng kế thừa từ đó.


1
Vâng, cảm ơn. Tôi biết decordator pattern. Đây là giải pháp cuối cùng, mặc dù thực sự chứng minh điều tôi muốn tránh - sao chép mã. Tôi sẽ chấp nhận câu trả lời của bạn, nếu không có ứng dụng ý tưởng nào khác. Tôi có thể sử dụng thuốc generic để khái quát mã của "trung gian" không?
Stan

6

Tôi nghĩ rằng bạn đang cố gắng tránh loại trùng lặp mã sai. Tôi tin rằng Michael Feathers đã viết một bài báo về điều này nhưng tiếc là tôi không thể tìm thấy nó. Theo cách anh ấy mô tả, bạn có thể nghĩ mã của mình có hai phần giống như một quả cam: Vỏ và bột giấy. Vỏ là những thứ như khai báo phương thức, khai báo trường, khai báo lớp, v.v ... Bột giấy là thứ bên trong các phương thức này; việc thực hiện.

Khi nói đến DRY, bạn muốn tránh trùng lặp bột giấy . Nhưng thường trong quá trình bạn tạo ra nhiều vỏ. Và thế là ổn.

Đây là một ví dụ:

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

Điều này có thể được tái cấu trúc vào đây:

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

Chúng tôi đã thêm rất nhiều vỏ trong tái cấu trúc này. Nhưng, ví dụ thứ hai có trình dọn dẹp sạch sẽ hơn method()với ít vi phạm DRY hơn.

Bạn nói rằng bạn muốn tránh mẫu trang trí bởi vì nó dẫn đến sao chép mã. Nếu bạn nhìn vào hình ảnh trong liên kết đó, bạn sẽ thấy nó sẽ chỉ nhân đôi operation()chữ ký (ví dụ: rind). Việc operation()thực hiện (bột giấy) nên khác nhau cho mỗi lớp. Tôi nghĩ rằng mã của bạn sẽ kết thúc sạch hơn và có ít sự sao chép bột giấy hơn.


3

Bạn nên thích thành phần hơn thừa kế. Một ví dụ điển hình là " mẫu " IExtension trong khung .NET WCF. Baiscally bạn có 3 giao diện, IExtension, IExtensibleObject và IExtensionCollection. Sau đó bạn có thể soạn khác nhau hành vi với một đối tượng IExtensibleObject bởi addig trường IExtension để sở hữu mở rộng IExtensionCollection nó. Trong java, nó sẽ trông giống như vậy, tuy nhiên bạn phải tạo ra triển khai IExtensioncollection của riêng mình, gọi các phương thức đính kèm và tách ra khi các mục được thêm / xóa. Cũng lưu ý rằng tùy thuộc vào bạn để xác định các điểm mở rộng trong lớp mở rộng của bạn Ví dụ này sử dụng cơ chế gọi lại giống như sự kiện:

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

Cách tiếp cận này có lợi ích của việc tái sử dụng tiện ích mở rộng nếu bạn quản lý để trích xuất các điểm mở rộng chung giữa các lớp của mình.


1
Có thể đó là vì tôi không sử dụng .NET, nhưng tôi thấy câu trả lời này rất khó hiểu. Bạn có thể thêm một ví dụ về việc sử dụng ba giao diện này?
Daniel Kaplan

Cảm ơn bạn, rất thú vị. Nhưng sẽ đơn giản hơn nhiều nếu chỉ đăng ký các cuộc gọi lại của tiện ích mở rộng trong đối tượng mở rộng? Một cái gì đó giống như processor.addHandler(console)được cung cấp ConsoleProcessorsẽ thực hiện giao diện gọi lại chính nó. Mẫu "phần mở rộng" trông giống như một hỗn hợp visitordecorator, nhưng nó có cần thiết trong trường hợp này không?
Stan

Nếu bạn đăng ký tiện ích mở rộng trực tiếp trong Bộ xử lý (== ExtensibleObject), bạn có khả năng tách biệt chặt chẽ. Ý tưởng ở đây là có các phần mở rộng có thể được sử dụng lại giữa các đối tượng có thể mở rộng với cùng các điểm mở rộng. Trong thực tế, mô hình là mora giống như một mô phỏng mô hình mixin .
m0sa

Hừm, tôi không chắc ràng ràng buộc bởi giao diện là khớp nối chặt chẽ. Điều thú vị nhất là ngay cả bởi mẫu mở rộng cả đối tượng mở rộng và tiện ích mở rộng đều phụ thuộc vào cùng một giao diện worker ("gọi lại"), do đó khớp nối vẫn giữ nguyên, imho. Nói cách khác, tôi không thể cắm tiện ích mở rộng hiện có vào một đối tượng có thể mở rộng mới mà không có mã hóa để hỗ trợ giao diện worker trong "bộ xử lý". Nếu "điểm mở rộng" thực sự có nghĩa là giao diện worker, thì tôi không thấy sự khác biệt.
Stan

0

Bắt đầu từ Android 3.0, có thể giải quyết vấn đề này một cách thanh lịch bằng cách sử dụng Fragment . Các mảnh có các cuộc gọi lại vòng đời của riêng chúng và có thể được đặt bên trong một Hoạt động. Tôi không chắc chắn điều này sẽ làm việc cho tất cả các sự kiện mặc dù.

Một tùy chọn khác tôi cũng không chắc chắn (thiếu bí quyết Android chuyên sâu) có thể là sử dụng Công cụ trang trí theo cách ngược lại k3b gợi ý: tạo một ActivityWrapperphương thức gọi lại có mã chung của bạn và sau đó chuyển tiếp đến một Activityđối tượng được bao bọc (của bạn các lớp triển khai thực tế), và sau đó Android bắt đầu trình bao bọc đó.


-3

Đúng là Java không cho phép đa kế thừa, nhưng bạn có thể mô phỏng nó nhiều hơn hoặc ít hơn bằng cách làm cho mỗi một trong các lớp con Một số hoạt động của bạn mở rộng lớp Hoạt động ban đầu.

Bạn sẽ có một cái gì đó như:

public class TabActivity extends Activity {
    .
    .
    .
}

2
Đây là những gì các lớp đã làm, nhưng lớp cơ sở Activity là một phần của API Android và không thể được các nhà phát triển thay đổi.
Michael Borgwardt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.