Java (Android)에서 다중 상속에 대한 적절한 해결 방법


15

다중 상속이 필요한 것처럼 보이는 적절한 코드 구현에 대한 개념적 문제가 있습니다. 많은 OO 언어에서는 문제가되지 않지만 프로젝트는 Android 용이므로 다중과 같은 것은 없습니다 extends.

나는 그런 간단한 같은 다른 기본 클래스에서 파생 된 활동의 무리를 가지고 Activity, TabActivity, ListActivity, ExpandableListActivity, 등 또한 나는에 배치해야합니다 몇 가지 코드 조각이 onStart, onStop, onSaveInstanceState, onRestoreInstanceState모든 활동 및 기타 표준 이벤트 핸들러.

모든 활동에 대해 단일 기본 클래스가있는 경우 코드를 특수 중간 파생 클래스에 배치 한 다음이를 확장하는 모든 활동을 작성합니다. 불행히도 여러 기본 클래스가 있기 때문에 그렇지 않습니다. 그러나 동일한 코드 부분을 여러 중간 클래스에 배치하는 것은 좋은 방법이 아닙니다.

또 다른 방법은 도우미 개체를 만들고 위에서 언급 한 이벤트의 모든 호출을 도우미에 위임하는 것입니다. 그러나이를 위해서는 도우미 개체가 포함되어야하고 모든 처리기는 모든 중간 클래스에서 재정의되어야합니다. 따라서 첫 번째 접근 방식과 큰 차이는 없습니다. 여전히 많은 코드 복제입니다.

Windows에서 비슷한 상황이 발생하면 하위 클래스 기본 클래스 ( ActivityAndroid 의 클래스에 "대응" )를하고 적절한 메시지를 한곳에서 트랩합니다.

이를 위해 Java / Android에서 무엇을 할 수 있습니까? Java 계측 ( 일부 실제 예제 포함 ) 같은 흥미로운 도구가 있다는 것을 알고 있지만 Java 전문가가 아니며이 특정 사례에서 시도해 볼 가치가 있는지 확실하지 않습니다.

다른 적절한 해결책을 놓친 경우 언급하십시오.

최신 정보:

안드로이드에서 동일한 문제를 해결하는 데 관심이있는 사람들을 위해 간단한 해결 방법을 찾았습니다. ActivityLifecycleCallbacks 인터페이스를 제공 하는 Application 클래스 가 있습니다 . 그것은 우리가 모든 활동의 중요한 사건에 가치를 더하고 가로 채기 위해 필요한 것을 정확하게 수행합니다. 이 방법의 유일한 단점은 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 ....
    }

따라서 모든 기본 클래스는 호출을 도우미에게 위임하는 중간 기본 클래스를 만듭니다. 중간 basesclass는 그들이 상속 한 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

이것은 이것으로 리팩토링 될 수 있습니다 :

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

상속보다 구성을 선호해야합니다. 좋은 예는 .NET WCF 프레임 워크 의 IExtension " 패턴 "입니다. 기본적으로 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을 사용하지 않기 때문일 수 있지만이 답변을 이해하기가 매우 어렵다는 것을 알았습니다. 이 세 가지 인터페이스를 사용하는 예를 추가 할 수 있습니까?
Daniel Kaplan

정말 고마워요 그러나 확장 가능한 콜백을 확장 가능한 객체에 등록하는 것만 큼 간단하지 않습니까? 콜백 인터페이스 자체를 구현 processor.addHandler(console)하는 것과 같은 것이 제공 ConsoleProcessor됩니다. 은 "확장"패턴 외모의 믹스 인을 좋아 visitor하고 decorator있지만,이 경우에 필요하다?
Stan

프로세서에 확장을 직접 등록하면 (== ExtensibleObject) 컵이 ​​단단히 고정됩니다. 여기서 아이디어는 확장 점이 동일한 확장 가능한 객체간에 재사용 할 수있는 확장을 갖는 것입니다. 실제로 패턴은 믹스 인 패턴 시뮬레이션 과 같은 모라 입니다.
m0sa

흠, 인터페이스에 의한 바인딩이 단단한 커플 링인지 확실하지 않습니다. 가장 흥미로운 점은 확장 패턴에 의해서도 확장 가능한 객체와 확장 모두 동일한 작업자 인터페이스 ( "콜백")에 의존하므로 커플 링이 동일하게 유지된다는 것입니다. 다시 말해, "프로세서"에서 작업자 인터페이스를 지원하기위한 코딩 없이는 기존 확장을 새로운 확장 가능한 개체에 연결할 수 없습니다. "확장 점"이 실제로 작업자 인터페이스를 의미하는 경우 차이점이 없습니다.
Stan

0

Android 3.0부터는 Fragment 를 사용 하여이 문제를 우아하게 해결할 수 있습니다 . 프래그먼트에는 자체 라이프 사이클 콜백이 있으며 활동 내에 배치 할 수 있습니다. 그래도 이것이 모든 이벤트에 적용되는지는 확실하지 않습니다.

나는 확실하지에 대한도있어 또 다른 옵션은 (심층 안드로이드 부족한 노하우) 제안 반대의 방법 K3B에서 데코레이터를 사용하는 수 있습니다 : 만들고 ActivityWrapper그 콜백 방법 싸서 앞으로 다음 공통 코드를 포함하고 Activity오브젝트 (당신의 실제 구현 클래스)를 확인한 다음 Android에서 해당 래퍼를 시작하도록합니다.


-3

Java가 다중 상속을 허용하지 않는 것은 사실이지만 SomeActivity 서브 클래스 각각이 원래 Activity 클래스를 확장하도록하여이를 다소 시뮬레이션 할 수 있습니다.

당신은 다음과 같은 것을 가질 것입니다 :

public class TabActivity extends Activity {
    .
    .
    .
}

2
이것은 클래스가 이미하는 일이지만 Activity 기본 클래스는 Android API의 일부이며 개발자가 변경할 수 없습니다.
Michael Borgwardt
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.