Programma's bestaan binnen het .net Framework steeds uit een aantal klassen. Het .net Framework bevat reeds een heleboel klassen waar we als programmeur gebruik kunnen van maken. Om programma's te maken zal je echter steeds zelf nieuwe klassen aanmaken.
In een klasse plaatsen we instantie- en statische variabelen, constructoren, methoden en eigenschappen.
Stel dat je een klasse Persoon aanmaakt met de instantievariabelen naam en geslacht. Je kan nu dus verschillende Persoon-objecten definiëren en elke Persoon een naam en geslacht toekennen.
Je maakt dan een klasse Ouder: een klasse voor Personen met kinderen. In deze nieuwe klasse kan je opieuw de instantievariabelen naam en geslacht declareren, met daarbij een instantievariabele kinderen. De instantievariabele kinderen kan bestaan uit meerdere objecten van de klasse Persoon, deze instantievariabele wordt een Array van Persoon-objecten.
In plaats van de instantievariabelen naam en geslacht opnieuw aan te maken in de klasse Ouder kan je beter gebruik maken van een andere techniek: overerving of inheritance. Je kan er binnen het .net Framework voor zorgen dat een klasse (de subklasse of afgeleide klasse) alle variabelen, methoden, ... van een andere klasse (superklasse of basisklasse) kan gebruiken. Je geeft aan dat de klasse Ouder overerft van de klasse Persoon.
using System;
namespace Personen
{
public class Persoon
{
// enumeratie voor het geslacht van een persoon
// op deze manier moet het geslacht op een van deze waarden
// worden ingesteld
public enum Geslacht { Mannelijk, Vrouwelijk }
// instantievariabelen
protected string naam;
protected Geslacht geslacht;
// constructor
public Persoon(string naam, Geslacht geslacht)
{
this.naam = naam;
this.geslacht = geslacht;
}
// elke klasse is afgeleid van de klasse Object,
// we geven de methode ToString een eigen invulling
public override string ToString()
{
return naam +" " +geslacht;
}
}
}
In deze klasse definiëren we eerst een enumeratie, dit is een gegevenstype dat gebaseerd is op een primair datatype (standaard int), en waarbij je slechts uit bepaalde waarden kan kiezen. Deze waarden kan je ook een duidelijke naam geven. In dit geval bestaat de enumeratie Geslacht uit twee elementen: 0 en 1. We kunnen ze op een gebruiksvriendelijke manier aanduiden met Geslacht.Mannelijk en Geslacht.Vrouwelijk. Op deze manier kan niemand een geslacht toekennen dat niet bestaat.
Een Persoon beschikt over twee instantievariabelen: naam en geslacht. De toegangsaanduider protected geeft aan dat deze variabelen toegankelijk zijn vanuit deze klasse en vanuit klassen die overerven van deze klasse. Private variabelen zijn niet toegankelijk vanuit subklassen. Nog een andere toegangsaanduider is internal. Deze toegangsaanduider geeft aan dat de variabelen toegankelijk zijn vanuit objecten in dezelfde assembly.
Een constructor zorgt ervoor dat we bij aanmaak van instanties van de klasse Persoon meteen een naam en geslacht kunnen aangeven.
Je merkt dat we ook een methode hebben voorzien: ToString. Deze methode zorgt voor een string die het huidige Object representeert. Deze methode maakt deel uit van de klasse System.Object. Elke klasse die je maakt in het .net FrameWork erft in feite over van de klasse Object. Je hebt overerving dus eigenlijk al vele malen gebruikt zonder erbij stil te staan. Aangezien wij een eigen implementatie willen geven aan de methode ToString moeten we de methode ToString uit de superklasse Object overschrijven. Dit doen we met het sleutelwoord override. Deze methode kunnen we overschrijven doordat in het .net Framework de methode ToString gemerkt is als virtual: enkel virtual methodes kunnen overriden worden.
using System;
namespace Personen
{
public class Ouder : Persoon
{
//Een ouder kan meerdere kinderen hebben
protected Persoon[] kinderen;
//De constructor ontvangt de argumenten naam, geslacht en een parameterlijst van kinderen
public Ouder(string naam,
Geslacht geslacht,
params Persoon[] kinderen): base(naam, geslacht)
{
this.kinderen = kinderen;
}
public Persoon GetKind(int i)
{
return kinderen[i];
}
public override string ToString()
{
string uit = naam +" " +geslacht;
foreach(Persoon kind in kinderen)
{
uit += "\n -" +kind;
}
return uit;
}
}
}
De klasse Ouder erft over van de klasse Persoon, dit werd als volgt aangeduid:
public class Ouder : Persoon
Een klasse kan in het .net Framework slecht overerven van één basisklasse !
Op deze manier beschikt de klasse Ouder meteen over de leden (members) van de klasse Persoon: de instantievariabelen naam en geslacht en de methode ToString (ook de enumeratie kan worden gebruikt in de klasse Ouder).
De klasse Ouder beschikt over een nieuwe instantievariabele: kinderen. kinderen is een Array van Persoon-objecten. Dit is geen aspect van overerving maar is een voorbeeld van compositie, zoals je in het vorige hoofdstuk hebt geleerd.
protected Persoon[] kinderen;
De klasse Ouder bevat ook een eigen constructor. Als je een Ouder-object wenst te maken geef je een naam, een geslacht, en een lijst met de kinderen op. Het sleutelwoord params kan je in een methode gebruiken wanneer een lijst verwacht wordt van dezelfde objecten. Je mag params enkel gebruiken voor het laatste argument in de argumentenlijst van de methode (of constructor).
We zorgen ervoor dat de constructor van de basisklasse Persoon wordt aangeroepen voor het vullen van de instantievariabele naam en geslacht, we hoeven deze opdrachten dan hier niet te hernemen.
Wanneer de constuctor parameters heeft zoals in dit voorbeeld, dan wordt niet automatisch de constructor van de superklasse uitgevoerd, bij een parameterloze constructor is dit wel zo.
public Ouder(string naam,
Geslacht geslacht,
params Persoon[] kinderen): base(naam, geslacht)
{
this.kinderen = kinderen;
}
De klasse Ouder bevat een methode GetKind. Deze methode ontvangt een integer en retourneert een Persoon. Deze Persoon maakt deel uit van de Array kinderen van de huidige Ouder.
public Persoon GetKind(int i)
{
return kinderen[i];
}
De klasse Ouder bevat ook een methode ToString: we overriden de methode ToString uit de klasse Persoon.
public override string ToString()
{
string uit = naam +" " +geslacht;
foreach(Persoon kind in kinderen)
{
uit += "\n -" +kind;
}
return uit;
}
using System;
namespace Personen
{
class PersoonTest
{
static void Main(string[] args)
{
Persoon p1 = new Persoon("Jan",Persoon.Geslacht.Mannelijk);
Persoon p2 = new Persoon("Sofie",Persoon.Geslacht.Vrouwelijk);
Ouder o1 = new Ouder("Eric",Ouder.Geslacht.Mannelijk,p1,p2);
Console.WriteLine(o1);
Console.WriteLine(o1.GetKind(1));
}
}
}
Opmerking: het is nu ook mogelijk een Persoon te maken met de constructor van de klasse Ouder:
Persoon p = new Ouder(...);
Je hebt geleerd dat een klasse slechts van één klasse kan erven. Een klasse kan echter ook nog interfaces implementeren. Wanneer een klasse een interface implementeert sluit de klasse een contract met de compiler dat de klasse zich zal gedragen volgens de interface. Concreet betekent dit dat in de klasse alle eigenschappen (properties) en methoden van de interface moet implementeren. Een interface bevat dus eigenlijk enkel een lijst van eigenschappen en methoden die nog geen concrete invulling hebben.
Binnen het .net Framework worden interfaces intensief gebruikt, het is essentiëel een goed begrip te hebben van wat een interface is, en hoe je deze in je toepassingen kan gebruiken.
In de volgende Case-Study maak je een toepassing waarin je zelf een interface maakt en gebruikt.
In de volgende toepassing maken we een klein tekenprogrammaatje waarmee je een cirkel, een rechthoek en een vierkant kan tekenen. De toepassing maakt gebruik van overerving, interfaces en een GIU-omgeving.
Ter voorbereiding van de toepassing maken we een structure Punt. Een structure kan je zien als gelijkend op een klasse. Een structure wordt gebruikt voor elementen die een beperkt aantal instantievariabelen hebben. Ze vormen een manier om een eigen datatype te maken.
Meer over structures bij MSDN.
Wij maken een structure Punt, dit eigen datatype geven we instantievariabelen x en y. Een punt kunnen we voortaan vastleggen met de structure Punt, we hoeven niet zelf telkens twee variabelen te declareren.
public struct Punt
{
public int x;
public int y;
public Punt(int x, int y)
{
this.x = x;
this.y = y;
}
}
De interface ITekenBaar bevat 1 methode: Teken. Elke klasse die deze interface implementeert zal een implementatie moeten geven aan de methode Teken. De methode ontvangt een instantie van de klasse Graphics: deze klasse wordt gebruikt om echt te kunnen tekenen op een formulier.
using System;
using System.Drawing;
namespace GUIVormen
{
public abstract class GrafischObject: ITekenBaar
{
protected Punt startPunt;
protected Color kleur;
public GrafischObject(Punt s, Color c)
{
startPunt = s;
kleur = c;
}
public abstract void Teken(Graphics g);
}
}
Deze klasse id gemerkt als abstract: van deze klasse kunnen geen instanties worden gemaakt. Het is voor ons niet de bedoeling dat de gebruiker een object maakt van de klasse GrafischObject. De klasse GrafischObject zal enkel gebruikt worden als superklasse voor de eigenlijke klassen voor de verschillende vormen: een cirkel, een rechthoek en een vierkant.
Elke vorm zal straks echter beschikken over een startPunt en een kleur, vandaar dat deze instantievariabelen werden opgenomen in de abstracte klasse.
Een constructor werd voorzien om de instantievariabelen te vullen.
De klasse implementeert de interface ITekenBaar en moet dus de methode Teken implementeren. We kunnen hier nog niet concreet opgeven hoe de vorm moet worden getekend, daar dit afhangt van het soort vorm dat we moeten tekenen. De methode Teken definiëren we hier dus als abstract: klassen die overerven van GrafischObject moeten de methode Teken implementeren.
Abstracte methoden zijn per definitie virtual (virtual is een sleutelwoord waarmee we aangeven dat een methode mag overriden worden - een niet virtuele methode kan niet overriden worden).
using System;
using System.Drawing;
namespace GUIVormen
{
public class Cirkel: GrafischObject
{
protected int straal;
public Cirkel(Punt p, Color c, int straal):base(p,c)
{
this.straal = straal;
}
public override void Teken(Graphics g)
{
g.DrawEllipse(new Pen(kleur),startPunt.x, startPunt.y,straal,straal);
}
}
}
private void btnCirkel_Click(object sender, System.EventArgs e)
{
Graphics g = panel1.CreateGraphics();
Cirkel c = new Cirkel(new Punt(40,40),Color.Red,30);
c.Teken(g);
}
private void btnWis_Click(object sender, System.EventArgs e)
{
Graphics g = panel1.CreateGraphics();
g.Clear(Color.White);
}
De methode CreateGraphics uit de klasse Panel (overgeërfd van Control) retourneert een referentie naar het Graphics-object van het gebruikte Panel.
Een Graphics-object (System.Drawing.Graphics) bevat eigenschappen en methoden om objecten te tekenen.
| Meer tutorials: |
| leer ook: | html | | xhtml | | css | | asp | | asp.net | | c# | | ado.net | | linq | | ajax | | java | | javascript |