Objekt Orienterad Programmering med C# Del 5: Interfaces
Introduktion
I denna den sista delen i objekt orienterad programmering med C# ska vi gå igenom ett annat koncept inom programutveckling nämligen vad är och hur använder och varför ska vi använda Interface vid programutveckling.
Vad är ett Interface?
Om vi tänker tillbaka till föregående genomgång då pratade vi i slutet om abstrakta klasser. Alltså klasser utan implementering utan bara en lista av metoder och egenskaper. Vilket vi då lärde oss var att det är klassens interface(publika gränssnitt).
Detta introducerar ett väldigt användbart sätt att se på objekt. Inte vad de är utan vad de kan göra
Detta kallas även för identifiering via beteende, inte via typ.
Det kan också beskrivas som en datastruktur som är liknande en klass, synnerhet gällande syntaxen men är fundamentalt olik en klass.
Ett interface saknar implementering, ett interface är enbart en lista av metod och egenskaps definitioner.
Vänta lite, är inte detta väldigt likt en abstrakt klass? Precis det är till sitt beteende exakt som en abstrakt klass med abstrakta metoder och egenskaper.
Vad är då skillnaden mellan interface och abstrakta klasser?
Skillnaden är att som vi har lärt oss att i C# och .NET kan vi bara ärva ifrån en klass. Vi har inte "multiple inheritance" i C# som finns i C++.
Däremot kan vi realisera(använda) flera interface i våra klasser. Detta är den viktigaste skillnaden och innebär ofta att istället för abstrakta klasser så använder vi interface istället.
Det är av största vikt att förstå att interface INTE är lösningen på avsaknaden av multipla arv i .NET och C#. Tyvärr finns det dokumentation och läromaterial som påstår detta.
I dokumentation framförallt engelsk så kommer ni att stöta på begreppet realization of an interface. Betyder helt enkelt använd ett interface och implementera alla de metoder och egenskaper som är definierade i ett interface.
Det är dock viktigt att förstå interface inte kan jämföras med klasser och arv. De har helt olika syften. Klasser och arv är till för att kunna återanvända kod och utöka funktionalitet genom skapa nya specialiserade klasser samt att kunna åsidosätta implementering från basklasser i barnklasser. Interface är för att identifiera ett tvingande beteende som behövs i flera klasser utan att definiera implementeringen.
Ett annat sätt att beskriva ett interface på, är att se det som ett kontrakt mellan interface och realiserande klasser. Kontraktet innebär att när väl ett interface är publicerat så får det inte ändras. Å andra sidan så innebär kontraktet att om en klass realiserar ett interface så garanterar klassen att alla metoder och eventuella egenskaper kommer att implementeras.
Samma regler gäller för interface som för abstrakta klasser, om vi väljer att realisera ett interface MÅSTE vi också implementera alla metoder och egenskaper.
UML representation av Interface
Notationen är väldigt lik den vid arv, skillnaden är den streckade linjen.
Observera interface namnet, det börjar på ett stort I(Interface) och sedan Shape. Detta är namngivnings konventionen för Interface.
Hur skapar vi interface?
Vi skapar ett nytt interface genom att istället för nyckelordet class använda nyckelordet interface. Så låt titta på hur vi kan göra om vår abstrakta Shape klass ifrån föregående genomgång till att istället vara ett interface.
public interface IShape
{
void CalculateArea();
}
Att tänka på är att vi använder public som åtkomst skydd samt nyckelordet interface och namnger vårt interface till IShape. Nästa är att vi INTE använder åtkomst skydd för våra metoder eller egenskaper. I ett interface förväntas de att vara publika.
Det är allt vi behöver kunna för att skapa ett interface. Om ni funderar på när, var, hur och framförallt varför ska vi använda interface. Så kommer vi strax att gå igenom detta.
Hur använder vi Interface?
Vi använder ett interface ungefär på samma sätt som vi definierar ett arv på en klass, det vill säga att vi efter klassnamnet anger ett kolon(:) och sedan interface namnet.
public class Rectangle : IShape{}
Vad vi nu måste göra är att implementera alla metoder och egenskaper som är definierade i vårt interface.
public void CalculateArea()
{
Console.WriteLine("Beräknar arean på en rektangel");
}
Observera åtkomst skyddet som MÅSTE vara public vid implementering, om inte kommer vi att få ett fel vid kompilering.
Så låt oss nu justera alla våra klasser att istället för att ärva ifrån Shape klassen realisera vårt IShape interface.
Interface IShape
namespace interfaces;
public interface IShape
{
void CalculateArea();
}
Klassen Rectangle
namespace interfaces;
public class Rectangle : IShape
{
public void CalculateArea()
{
Console.WriteLine("Beräknar arean på en rektangel");
}
}
Klassen Circle
namespace interfaces;
public class Circle : IShape
{
public void CalculateArea()
{
Console.WriteLine("Beräknar arean på en cirkeln");
}
}
Klassen Triangle
namespace interfaces;
public class Triangle : IShape
{
public void CalculateArea()
{
Console.WriteLine("Beräknar arean för en triangel");
}
}
Klassen Octagon
namespace interfaces;
public sealed class Octagon : IShape
{
public void CalculateArea()
{
Console.WriteLine("Beräknar arean av en oktagon");
}
}
Interface och Polymorfism
Som vi såg i förra modulen så kunde vi använda en referens variabel av en basklass och sedan uppnå ett polymorfistiskt beteende. Det vill säga att en basklass vet vilken implementering som ska exekveras beroende på vilken barnklass som refereras. Kan vi uppnå samma sak med interface, givetvis. Låt oss skriva om vår Program klass så att den hanterar interface istället.
amespace interfaces;
internal class Program
{
private static void Main(string[] args)
{
// Iställer för klassen Shape använder vi
// IShape interface som typ i vår generiska lista...
var shapes = new List<IShape>();
// Nedanstående kod är exakt som i exemplet i modulen om arv
// och polymorfism...
shapes.Add(new Rectangle());
shapes.Add(new Circle());
shapes.Add(new Rectangle());
shapes.Add(new Triangle());
shapes.Add(new Circle());
shapes.Add(new Triangle());
shapes.Add(new Octagon());
foreach (var shape in shapes)
{
shape.CalculateArea();
}
}
}
Om vi nu kör applikationen så får vi följande resultat.
Beräknar arean på en rektangel
Beräknar arean på en cirkeln
Beräknar arean på en rektangel
Beräknar arean för en triangel
Beräknar arean på en cirkeln
Beräknar arean för en triangel
Beräknar arean av en oktagon
Interface har samma möjlighet till polymorfism som arv
Varför Interface?
Det största argumentet för att använda interface är att vi kan skapa loosely coupled(mjukt kopplade) klasser som inte är beroende av en specifik implementering. Så att vi vid behov kan göra justeringar och byta implementering utan att det påverkar applikationen.