Mehrfachvererbung in Java

Im allgemeinen ist wahrscheinlich so, dass das Unterbinden von Mehrfachvererbung eher ein Segen als ein Fluch ist. Doch wie immer gibt es auch hier Fälle, wo dies unumgänglich ist. Dabei müchte ich nicht auf die Gründe eingehen. Es sei lediglich der Hinweis erlaubt: Es ist in jedem Fall weniger aufwändig, ohne Mehrfachvererbung aus zukommen. Somit ist der Aspekt ob Mehrfachvererbung tatsächlich notwendig ist zu überprüfen. Wie der Umstand von Single Inheritance umgangen werden kann soll hier am Beispiel der Programmiersprache Java erläutert werden.

  1. Beide Klassen, die Abzuleiten sind liegen im eigenen Namensraum, d.h. beide Klassen gehören zum selben Projekt wie die zu erstellende Ableitung. Hier wählt man eine Klasse aus von der man ableitet. Von der anderen Klasse erstellt man ein Interface, das man implementiert. Dieses vorgehen legt nahe, dass man für das Interface die Klasse mit weniger Implementationsaufwand verwendet. Wenn möglich sollte hier auch die erwartete API Stabilität einfliessen. D.h. eine Klasse deren API, oder zu einem geringeren Grad die Implementation, noch ändern kann sollte wo möglich nicht als Interface verwendet werden, da hier aufgrund der Anpassung der Klasse, das Interface und dann die „abgeleitete“ Klasse angepasst werden muss.
  2. Ähnlicher Fall wie oben, nur dass hier die eine Klasse ausserhalb des Projekts liegt. Liegt diese Klasse jedoch noch innerhalb des eigenen Einflussbereichs, z.B. anderes Projekt des Teams, so kann selbes vorgehen wie oben angewandt werden; andernfalls ist die externe Klasse prädestiniert für ein Interface zumal hier davon ausgegangen werden kann, dass die API stabil ist.
  3. Beide Klassen liegen Ausserhalb des Einflussbereichs. Eine der beiden Klassen implementiert ein Interface, das die essentiellen Methoden enthält. In diesem Fall kann dieses Interface implementiert werden und die andere Klasse implementiert werden.
  4. Trifft keiner der obigen Punkte zu so muss eine komplexere Lösung gefunden werden. Komplexer ist sie weil sie mehr Aufwand bedeutet, aber auch weil sie einer echten Mehrfachvererbung am nächsten kommt: Man wählt den Ansatz, dass man eine der beiden Klassen ableitet und von der anderen eine Reimplementation vornimmt. Handelt es sich hierbei um eine Klasse von Sun, so kann sie dem src.zip entnommen werden. Zum Beispiel:
    public class ObservableThread extends Thread {
        private boolean changed = false;
        private Vector obs;
        public ObservableThread() {
    	obs = new Vector();
        }
        public synchronized void addObserver(Observer o) {
            if (o == null)
                throw new NullPointerException();
    	if (!obs.contains(o)) {
    	    obs.addElement(o);
    	}
        }
        public synchronized void deleteObserver(Observer o) {
            obs.removeElement(o);
        }
        public void notifyObservers() {
    	notifyObservers(null);
        }
       public void notifyObservers(Object arg) {
            Object[] arrLocal;
    
    	synchronized (this) {
    	    if (!changed)
                    return;
                arrLocal = obs.toArray();
                clearChanged();
            }
    
            for (int i = arrLocal.length-1; i>=0; i--)
                ((Observer)arrLocal[i]).update(new HiddenObservable(this), arg);
        }
        public synchronized void deleteObservers() {
    	obs.removeAllElements();
        }
        protected synchronized void setChanged() {
    	changed = true;
        }
        protected synchronized void clearChanged() {
    	changed = false;
        }
        public synchronized boolean hasChanged() {
    	return changed;
        }
        public synchronized int countObservers() {
    	return obs.size();
        }
    

    Hierbei handelt es sich jedoch noch nicht um eine echte Mehrfachvererbung. In vielen Fällen sollte dies jedoch schon genügen. Wo nicht kann der Mehrfachvererbung mehr Authentizität verliehen werden indem von der ursprünglichen Klasse die reimplementiert wird ein Interface gebildet wird und dieses implementiert wird. Nun hat die Klasse beide Typen (Thread und Observable). Eine andere oder weitere Möglichkeit – insbesondere wenn this semantisch mit der ursprünglichen Klasse verwendet wird – besteht darin eine versteckte Inner Class zu bilden, die die reimplementierte Klasse ableitet und alle Methoden, die von dieser Klasse geerbt werden, überschreibt. Die Implementation der Methoden reicht die Ausführung an die selbe Methode in der äusseren Klasse weiter:

        private class HiddenObservable extends Observable{
        	        private ObservableThread ot;
         	        public HiddenObservable(ObservableThread obersevable){
        		       ot = obersevable;
        	        }
    		public synchronized void addObserver(Observer o) {
    			ot.addObserver(o);
    		}
    		protected synchronized void clearChanged() {
    			ot.clearChanged();
    		}
    		public synchronized int countObservers() {
    			return ot.countObservers();
    		}
    		public synchronized void deleteObserver(Observer o) {
    			ot.deleteObserver(o);
    		}
    		public synchronized void deleteObservers() {
    			ot.deleteObservers();
    		}
    		public synchronized boolean hasChanged() {
    			return ot.hasChanged();
    		}
    		public void notifyObservers() {
    			ot.notifyObservers();
    		}
    		public void notifyObservers(Object arg) {
    			ot.notifyObservers(arg);
    		}
    		protected synchronized void setChanged() {
    			ot.setChanged();
    		}
        }
    

    Zu beachten: Dies ist nötig, da im ersten Code-Teil (bereits angepasst) ein Aufruf mit this gemacht wird und this nun mal vom Typ Observable und nicht vom Typ Thread oder ObservableThread ist.
    Einziger Nachteil dieser Lösung: instanceof Observable wird immer false zurückgeben. Solange diese Abfrage jedoch im eigenen Code ist, spielt dies keine weitere Rolle.

Schreibe einen Kommentar