Advanced   Java   Services Interfaces Back Next Up Home

Von der abstrakten Klasse zum Interface

Ein Interface kann man auffassen als Spezialfall einer abstrakten Klasse. Treibt man das Konzept abstrakte Klasse auf die Spitze, so landet man bei einer Klasse, die nur noch abstrakte Methoden enthält.

public abstract class SehrAbstrakt
{
   private int a ;  // nicht sinnvoll
   public int b ;

   public abstract void abstractMethod1();
   public abstract int abstractMethod2(String s);
   public abstract String abstractMethod3(int a);
   // ...
   // ...

}

In der obigen Situation gibt es für die Variable a keine sinnvolle Verwendung. Wer kann sie benützen ? In der Klasse gibt es keine Methoden, die sie verwenden, außerhalb der Klasse ist sie nicht ansprechbar. Objekte einer Ableitung von SehrAbstrakt können sie auch nicht verwenden, weil sie auf den ererbten privaten Datenteil nicht direkt zugreifen können. In dieser Situation machen nur noch public Variable einen Sinn. Die Initialisierung von b oder der Zugriff auf b ist auch nicht direkt möglich, weil man ja ein Objekt braucht, um b zu benützen. Immerhin kann ein Objekt einer abgeleiteten Klasse darauf zugreifen. Das widerspricht jedoch dem Prinzip der Datenkapselung. Sinnvoller wäre an dieser Stelle eine statische Variable. Die läßt sich natürlich auch bei einer abstrakten Klasse über den Namen der Klasse ansprechen. Damit sind wir schon fast bei einem Interface.

public abstract class FastEinInterface
{
   public static int a ;  // statische Variable
   public static const int b=17 ;
   // statische Konstante, muß sofort initialisiert werden

   public abstract void abstractMethod1();
   public abstract int abstractMethod2(String s);
   public abstract String abstractMethod3(int a);
   // ...
   // ...

}

Von der Klasse zum Interface

public interface IMyFirstInterface
{
   void abstractMethod();       // implizit abstrakt
   int publicMethod(String s);  // implizit public
   void notStaticMethod(int a); // nicht statisch
   // ...
   // ...
}

Definition

Ein Interface ist eine Sammlung von abstrakten Methoden. Die Methoden eines Interfaces sind implizit public, abstract und nicht statisch. Die Modifier dürfen auch nicht verwendet werden, der Compiler reagiert darauf mit einer Fehlermeldung. Ebensowenig darf der Modifier virtual verwendet werden. Im Gegensatz zu Java erlaubt das Interfacekonzept von C# keine statischen Konstanten in einem Interface. Das vieldiskutierte Constant Interface Anti pattern ist somit nicht nicht realisierbar. Siehe etwa
resources.mdbitz.com/2010/03/the-constant-interface-antipattern-how-not-to-define-constants
oder
stackoverflow.com/questions/2659593/what-is-the-use-of-interface-constants

Zweck des Begriffes ist es, eine abgeschwächte Form der Mehrfachvererbung zuzulassen. So kann eine Klasse nur einmal von einer anderen Klasse, der Elternklasse erben, aber beliebig viele Interfaces implementieren:

public interface IFace1
{
   //..
}

public interface IFace2
{
   //..
}

public interface IFace3
{
   //..
}


class ImplementAll : IFace1, IFace2, IFace3
{
   //..
}

Implementieren eines Interfaces

Implementierung durch eine reale Kindklasse

Eine Möglichkeit

public interface IFace
{
   void method();
}

public class Implementor : IFace
{
   public void Method()   // not virtual
   {
      Console.WriteLine("Method in Implementor");
   }
}

public class Child : Implementor
{
   public void Method() // Warning:  Method hides inherited member. Use the new keyword if hiding was intended.
   {
      Console.WriteLine("Method in Child");
   }
}

Die Implementierung ist korrekt, jedoch wird in Implementor die Methode nicht virtuell angelegt, was bei der Weitervererbung zu dem zitierten Warning führt.

Eine andere Möglichkeit ist dann natürlich, die Implementierung virtuell anzulegen.

public interface IFace
{
   void method();
}

public class Implementor : IFace
{
   public virtual void Method() // virtual
   {
      Console.WriteLine("Method in Implementor");
   }
}

public class Child : Implementor
{
   public override void Method() // oder auch mit new
   {
      Console.WriteLine("Method in Child");
   }
}


public static void Main(string[] args)
{
   Implementor impl = new Implementor();
   impl.Method();
}

Man sieht hier, daß eine Methode in einem Interface zwar abstrakt ist, aber nicht implizit virtual, sonst würde man in Implementor ja override verwenden. Zum Vergleich die analoge Situation mit einer abstrakten Klasse.

public abstract class AbsClass
{
   public abstract void Method();  // implizit virtual
}

class RealClass : AbsClass
{
   public override void Method()  // override notwendig wegen abstract  (oder new)
   {
      Console.WriteLine("Method in AbsClass");
   }
}

class Child2 : RealClass
{
   public override void Method()  // oder new
   {
      Console.WriteLine("Method in RealClass");
   }
}

Implementierung durch eine abstrakte Kindklasse

Im Falle einer abstrakten Kindklasse gibt es eine Besonderheit bei C#. Der Compiler erzwingt bei einer abstrakten Kindklasse eine Wiederholung der Deklaration der vom Interface geerbten Methode (im Unterschied zu etwa Java).

public interface IFace
{
   void Method();
}

public abstract class AbstrakteKlasse : IFace
{
   // AbstrakteKlasse does not implement interface member Method1()
}

Durch die erneute Deklaration wird die Methode implizit virtuell.

public interface IFace
{
   void Method();
}

public abstract class AbstrakteKlasse : IFace
{
   public abstract void Method();  // jetzt implizit virtual
}

Typverwandtschaft durch Ableiten eines Interfaces

Wir wissen bereits, daß eine abgeleitete Klasse typverwandt zu ihrer Elternklasse ist.

class Parent
{}

class Child : Parent
{}

static void Main(string[] args)
{
   Child ch = new Child();
   Parent p = ch; // OK
}

Nun gibt es eine wichtige Ergänzung. Obwohl man von einem Interface keine Objekte anlegen kann, stellt jedes Interface einen eigenen Datentyp dar. Ein Objekt zu diesem Typ existiert jedoch erst dann, wenn es eine Klasse gibt, die dieses Interface implementiert und die abstrakten Methoden realisiert. Nehmwn wir an, daß eine Klasse Impl alle Methoden eines Interfaces Face implementiert. Dann kann man folgendes schreiben.

interface IFace
{}

class Impl : IFace
{}

static void Main(string[] args)
{
   Impl im = new Impl(); //1
   IFace f = im; //2 OK
}

Man sieht, daß man die Referenz vom Typ eines Interfaces genauso einsetzt wie eine Basisreferenz. Der Konstruktoraufruf mit new erfordert eine reale Klasse (//1), bei der nachfolgenden Zuweisung spielt die Referenz f die zuvor die Referenz p(//2). Es sind auch Aufrufe von Methoden möglich, der Interfacetyp kennt jedoch nur die Methoden, die im Interface vereinbart hat.

Diese Situation stellt sich in einem UML-Diagramm graphisch wie folgt dar.

implement2.jpg

Fazit:


Verwendung von Interfaces

Der Name Interface kommt natürlich nicht von ungefähr. Eine Klasse, die ein Interface implementiert, garantiert die Existenz der im Interface vereinbarten Methoden und bietet damit eine Schnittstelle an. Eine dritte Klasse kann sich dann darauf verlassen, daß diese Methoden aufrufbar sind. Eine weitere Standardsituation ist folgende: Eine Methode hat in der Parameterliste den Typ eines Interfaces stehen. Jedes Objekt, dessen zugehörige Klasse dieses Interface implementiert, kann dann dieser Methode als Parameter übergeben werden. Die Klassen brauchen in keiner Weise über Vererbung verwandt zu sein. Innerhalb der Methode erscheinen sie als Objekte vom Typ des Interfaces und der Programmierer kann alle Methoden aufrufen, die der Interfacetyp vereinbart hat. Welchen Klassentyp sie haben, bleibt im verborgen. Er muß es auch nicht wissen, denn alle Objekte bieten ihm die gleiche Schnittstelle an.


Vererbung bei Interfaces

Auch für Interfaces gibt es Vererbung. Die wichtige Einschränkung ist, daß ein Interface nur von einem Interface erben kann, nie von einer Klasse. Durch diese (sinnvolle und notwendige) Einschränkung wird die Vererbung bei Interfaces eine ziemlich einfache Angelegenheit. Wie in Diagrammen üblich bedeutet ein durchgezogener Pfeil extends und ein gestrichelter Pfeil implements.

implement4.jpg

Im obigen Diagramm haben wir zwei Interfaces Face1 und Face2 und zwei Klassen Parent und RealChild. Der Name der letzteren soll natürlich bedeuten, daß RealChild keine abstrakte Klasse ist. Diese spezielle Variante der Mehrfachvererbung vermeidet die Probleme der allgemeinen Mehrfachvererbung bei Klassen. Es folgt ein Beispiel einer möglichen Realisierung der obigen Hierarchie.

class Program
{
   static void Main(string[] args)
   {
   IFace1 f1;  IFace1 f2;
   Parent pa = new Parent();
   RealChild ch = new RealChild();
   f1 = pa;
   f2 = ch;
   f1.FaceMethod1();
   f2.FaceMethod1();

   }
}

interface IFace1
{
   void FaceMethod1();
}
interface IFace2 : IFace1
{
   void FaceMethod2();
}

class Parent : IFace1
{
   public virtual void FaceMethod1()
   {
      Console.WriteLine("FaceMethod1 von Parent") ;
   }
}

class RealChild : Parent, IFace2
{
   public override void FaceMethod1()
   {
      Console.WriteLine("FaceMethod1 von RealChild") ;
   }
   public void FaceMethod2()
   {
      Console.WriteLine("FaceMethod2") ;
   }
}

Überlegen sie sich, daß es nicht zu Widersprüchen kommt. Es ist egal, auf welchem Weg sie von IFace1 zu RealChild kommen.


Zusammenfassung

Da eine abstrakte Methode in einer abstrakten Klasse implizit virtuell ist, die Methoden eines Interfaces aber nicht virtuell sind wird hier dieser Zustand quasiabstrakt genannt.

Auf der nächsten Seite beschäftigen wir uns mit der konkreten Implementierung des wichtigen Interfaces IComparable.

Valid XHTML 1.0 Strict top Back Next Up Home