Introductie OOP met Visual Basic .NET
Het overstappen van Visual Basic 6 naar Visual Basic .NET valt nog niet mee. Met Visual Basic 6 bent u in staat om objecten te bouwen, maar volledig object georiënteerd programmeren (OOP) is niet mogelijk. Met de komst van Visual Studio .NET kunt u dit echter wel. Het complete Microsoft .NET Framework, met al haar tools, is volgens objectgeoriënteerde principes ontwikkeld. Als u deze principes beheerst, kunt u Visual Basic .NET optimaal gebruiken.
In het verleden heb ik met enige regelmaat geschreven hoe u binnen Visual Basic 6 zelf objecten kunt bouwen en deze kan toepassen binnen uw eigen applicaties of beschikbaar kan stellen aan andere toepassingen. Hoewel Visual Basic 6 al enige OOP kenmerken had, waren een aantal principes niet mogelijk, zoals volledige overerving en polymorfisme.
Met Visual Basic .NET hebt u wel de beschikking over alle faciliteiten en mogelijkheden om volledig objectgeoriënteerd te programmeren. Wat houdt dit objectgeoriënteerd programmeren nu in en welke voordelen heeft het ten opzicht van meer serieel programmeren? Om deze vraag te kunnen beantwoorden leg ik u eerst een aantal concepten uit van objectgeoriënteerd programmeren. In de basis bestaat OOP uit een viertal pilaren:
- Abstractie (Abstraction);
- Insluiting (Encapsulation);
- Overerving (Inheritance);
- Polymorfisme (Polymorphism).
Abstractie
Abstractie betekent de scheiding van de totale implementatie van de applicatie in logische, feitelijke codeblokken. U onderscheidt een aantal losse, logische entiteiten en definieert deze. Een klassiek voorbeeld wat altijd in de literatuur weer opduikt is de auto. Men kan aan een auto een aantal zelfstandige onderdelen onderscheiden. Een goed voorbeeld is het 'Wiel'. Een 'wiel' heeft een aantal eigenschappen, bijvoorbeeld wielmaat, type velg en merk band. Door de interface van een 'wiel' vast te leggen in een object, kan men die eenvoudig hergebruiken. De meeste auto's hebben immers vier wielen.
Binnen Visual Basic .NET, maar ook binnen Visual Basic 6 kunt u dit bewerkstelligen door gebruik te maken van classes/objecten en bijvoorbeeld een collectie van die classes.
| Classes versus Objecten |
| De termen class en object worden vaak naast - en door elkaar gebruikt. Toch zit er een wezenlijk verschil tussen beiden. De class is de blauwdruk van het object. Het object is de instantie van de class. Binnen uw applicatie kunnen er meerdere objecten zijn van één type class. U maakt object van een class met gebruik te maken van het keyword New() |
Interface
Onder 'interface' verstaan we hoe de class er voor de buitenwereld uit ziet. Dus welke publieke eigenschappen en methoden bevat het. Voorbeelden van eigenschappen (properties) zijn: .Wielmaat, .MerkBand, .Positie en .TypeVelg. Het zijn dus kenmerken die het object beschrijven en die invloed kunnen hebben op het gedrag van het object. Methoden (methods) kunnen worden uitgevoerd. Hierbij kunt u denken aan bijvoorbeeld: RondDraaien().
Een interface bevat alleen de declaratie. Classes kunnen zowel de declaratie als de definitie, dus 'inhoudelijke' code bevatten. Het verschil zit hem met name in het feit dat classes de implementatie van zijn eigenschappen en methoden bevatten en interfaces niet. Zowel classes als interfaces ondersteunen overerving, waarover later meer. Binnen Visual Basic .NET definieert u een interface met het keyword Interface en een class met het keyword Class.
Insluiting
Met insluiting of encapsulation bedoelen we dat alle benodigde informatie en functionaliteit zich in de class bevindt. In veel gevallen weet u in wat voor een object uw class gebruikt gaat worden, maar soms ook niet. U mag dus geen vooronderstellingen maken over de context waarin uw class gebruikt gaat worden. De toegang tot die informatie mag enkel en alleen geschieden door het instellen van eigenschappen; de toegang moet dus beperkt zijn tot de properties en methods die de interface beschikbaar stelt. Het gevaar schuilt er in dat Visual Basic .NET u het wel mogelijk maakt om hiervan af te wijken. Publieke variabelen in modules zijn beschikbaar binnen een class, waardoor u toch een soort afhankelijkheid ten opzichte van de context kunt creëren. Verwijs dus nooit naar dit soort variabelen, maar maak deze beschikbaar als een eigenschap op uw object.
Zorg dat uw interface compleet is, maar overdrijf hierin niet. Zolang uw interface vrij algemeen blijft kan men de code hergebruiken. Zodra u eigenschappen of methodes gaat toevoegen die alleen betrekking hebben op de applicatie waarin het oorspronkelijk voor ontwikkeld is, zal de class niet snel hergebruikt worden. En dit is nu juist een van de belangrijke redenen om te kiezen voor insluiting.
Een voorbeeld: U maakt een class waarmee het eenvoudig wordt om informatie weg te schrijven naar een .INI bestand. De class bevat eigenschappen zoals: .Filename, .Section, .KeyName en .KeyValue. Tevens bevat het de volgende methoden: Save(), Load() , Delete() en DeleteAll(). De interface is duidelijk en kan eenvoudig in een andere applicatie worden hergebruikt. Nu voegt u, om het aantal regels in aanroepende applicatie te verminderen, de methode: .SaveUserName(Byval strUserName) toe. Deze methode maakt of past de sectie 'UserInfo' aan en schrijft in de sleutel 'Username' de meegeven variabele weg. U bespaart een aantal handelingen, maar maakt uw class minder eenduidig en lastiger te hergebruiken. Niet doen dus. Zorg dat uw class logisch is opgebouwd en voorspelbaar reageert. Daarnaast heeft insluiting het voordeel dat de code zich bevindt op maar één plaats en op de plaats waar het eigenlijk ook hoort.
Overerving
De bovenstaande technieken zijn ook toe te passen met behulp van Visual Basic 6. Visual Basic .NET biedt u echter de mogelijkheid om naast classes en interfaces ook visuele elementen over te erven. Met de reeds beschreven concepten is er een sprake van een zogenaamde heeft-een-relatie (of has-a-relatie). De class CAuto heeft-een-relatie met de class CWiel. Of een Form heeft-een Textbox. Deze heeft-een relaties zijn een belangrijk kenmerk van OOP. U kunt namelijk toepassingen bouwen door bestaande objecten te combineren. Dit concept wordt ook wel aangeduid als compositie.
Overerving of inheritance maakt daarentegen gebruik van een zogenaamde is-een- relatie. Een 'Textbox' is een 'Control', een 'Bus' is een 'Voertuig' en een 'Haai' is een 'Vis'.
Wanneer u code heeft geschreven die een 'Voertuig' beschrijft, dan is het handig dat wanneer u een 'Bus' wilt implementeren u de code van de 'Voertuig' kunt hergebruiken en eventueel kan aanpassen. Dit kan plaats vinden door overerving: het maken, aanpassen en eventueel uitbreiden van nieuwe classes op basis van een bestaande classes. U maakt een afgeleide of derived class op basis van een zogenaamde basisclass of baseclass of superclass. Het hele .NET Framework is op dit concept gebouwd, met als moeder van alle objecten: Object. Alle hiervan afgeleide classes bevatten een uitgebreidere interface, maar ook dè interface van deze class. Bekijkt u de code eens in Listing 1
Public Class Form1
Inherits System.Windows.Forms.Form
End Class |
| Codevoorbeeld 1. Overerving |
Elke Form binnen een Windows applicatie is afgeleid van de basisclass 'Form', die gedefinieerd is in System.Windows.Forms. Binnen het hele .NET Framework komt dit principe aan het licht. Elke Button op een Form is afgeleid van System.Windows.Forms.Button. Sterker nog: u kunt een Form aanpassen en deze weer gebruiken als basis voor een nieuw Form. We spreken in dit geval van Inherited Forms of – controls. Hierdoor kunt u de code in schermen die veel op elkaar lijken tot een minimum reduceren.
Public MustInherit Class CVoertuig ' Interne variabelen van Public Properties Private mlngAantalZitPlaatsen As Long Private mclrKleur As Color Private mlngTopSnelheid As Long
' Constructor Public Sub New() ' Stel default waarden in Me.AantalZitPlaatsen = 1 Me.Kleur = Color.White Me.TopSnelheid = 40 End Sub
Public Property AantalZitPlaatsen() As Long Get Return mlngAantalZitPlaatsen End Get Set(ByVal lngAantalZitPlaatsen As Long) mlngAantalZitPlaatsen = lngAantalZitPlaatsen End Set End Property
Public Property Kleur() As Color Get Return mclrKleur End Get Set(ByVal clrKleur As Color) mclrKleur = clrKleur End Set End Property
Public Property TopSnelheid() As Long Get Return mlngTopSnelheid End Get Set(ByVal lngTopSnelheid As Long) mlngTopSnelheid = lngTopSnelheid End Set End Property
End Class |
| Codevoorbeeld 2. CVoertuig |
Een voorbeeld van een eigen overervinghiërarchie. Men zou een basisclass 'CVoertuig' kunnen maken. Deze class bevat reeds een aantal eigenschappen die voor alle voertuigen gelden, zoals bijvoorbeeld: .Kleur, .AantalZitplaatsen of .TopSnelheid.

Afbeelding 1. Een overervinghiërarchie
Op basis van deze basisclass zou men een aantal andere classes kunnen afleiden. Aan die afgeleide classes zou men vervolgens eigenschappen kunnen toevoegen die specifiek zijn voor deze class, bijvoorbeeld aan de afgeleide 'CLandVoertuig' de eigenschap: .AantalWielen.
In Listing 2 ziet u hoe u eenvoudig een class 'CVoertuig' kunt definiëren.. Met de code in de constructor, kunt u het object voorzien van standaardwaarden. Op basis van bovenstaande code kunt u de class 'CLandVoertuig' en op basis daarvan class 'CBus' afleiden. Deze laatste class bevat de eigenschappen van zowel 'CVoertuig' als 'CLandVoertuig'. Het heeft al deze eigenschappen 'geërfd'. Zie voor deze implementatie Listing 3.
Public MustInherit Class CLandVoertuig
Inherits CVoertuig
' Interne variabelen Private mintAantalWielen As Int32
Public Property AantalWielen() As Int32 Get Return mintAantalWielen End Get Set(ByVal intAantalWielen As Int32) mintAantalWielen = intAantalWielen End Set End Property
Public Sub New() Me.AantalWielen = 1 End Sub
End Class
Public Class CBus
Inherits CLandVoertuig
' Constructor Public Sub New() Me.AantalWielen = 6 Me.AantalZitPlaatsen = 70 End Sub
End Class |
| Codevoorbeeld 3. CLandVoertuig en CBus |
Doordat CVoertuig en CLandVoertuig zogenaamde abstracte classes zijn kunnen ze niet als zelfstandige objecten worden geïnstantieerd. U maakt een abstracte class met behulp van het MustInherit keyword. We noemen deze classes ook wel 'virtual abstract classes'. Methods en properties die als MustOverride gemarkeerd zijn, noemen we ook wel 'virtual abstract methods'.
Met het keyword Inherits geeft u aan op basis van welke class het object afgeleid moet worden. Deze keywords maken het misschien op het eerste gezicht niet makkelijker, maar u kunt er wel betere code mee schrijven. Het is zeker aan te raden om per class(hiërarchie) te bepalen of u deze keywords wilt gebruiken. U kunt dit proces min of meer vergelijken met de denkwijzes die u hanteert bij het gebruik van woorden die de scope van variabelen bepalen. Hierbij kunt u denken aan Public, Private en bijvoorbeeld Friend. Per property of method bekijkt u of u het beschikbaar wil maken voor 'de buitenwereld' of dat het een interne property of method is.
De keywords als MustInherit, MustOverride, Overrideable, NotInheritable en alle andere maken het u mogelijk om betere code te schrijven. In ieder geval kunnen uw classes en objecten eenduidiger in het gebruik worden, waardoor u, als programmeur van de baseclasses, niet voor vervelende problemen komt te staan. U voorkomt dat andere programmeurs uw object op een geheel andere en ongewenste wijze kunnen implementeren.
Dim objBus As New CBus Dim strCrLf As String = System.Environment.NewLine
With objBus ' Stel specifieke eigenschappen in .AantalWielen = 6 .Kleur = Color.Yellow ' Toon messagebox met waarden MessageBox.Show("Aant. zitplaatsen: " & _ .AantalZitPlaatsen.ToString & strCrLf & _ "Topsnelheid: " & _ .TopSnelheid.ToString & strCrLf & _ "Aant. wielen: " & _ .AantalWielen.ToString & strCrLf & _ "Kleur: " & .Kleur.ToString) End With |
| Codevoorbeeld 4. Gebruik classes vanuit client |

Afbeelding 2. Gebruik van de class CBus
De implementatie van de classes kunt u vrij simpel toepassen. U declareert een variabele van het type CBus en u kunt op dit object de eigenschappen specifiek voor het type CBus benaderen, maar ook eigenschappen die geërfd zijn van CVoertuig. Wanneer u de code van Listing 3 uitvoert, wordt er een messagebox getoond zoals afbeelding 2.
Wanneer u wilt dat een class niet meer 'gederived' kan worden of met andere woorden dat er geen andere classes meer van kan worden afgeleid, dan kunt u een zogenaamde 'final class' definiëren. Dit type class is gemarkeerd als NotInheritable.
Public NotInheritable Class CDubbelDekker
Inherits CBus ' ... End Class |
| Codevoorbeeld 5. NotInheritable class CDubbelDekker |
Polymorfisme
Waarschijnlijk het lastigste concept van OOP is polymorfisme of te wel veelvormigheid. Met polymorfisme wordt het gedrag beschreven van classes die van een gemeenschappelijke baseclass zijn afgeleidt. Visual Basic .NET biedt u de mogelijkheid om polymorfisme toe te passen en optimaal te gebruiken. Met polymorfisme worden methods met dezelfde naam op verschillende wijze geïmplementeerd. Stelt u zich eens voor dat we een class definiëren van het type CVliegtuig. Nu kan men zowel een eenmotorig- als tweemotorig vliegtuig onderscheiden. Voor beide vliegtuigtypes moeten we de motor kunnen starten; dit implementeren we als method: StartMotor().
Doordat we tijdens het aanmaken van het object aangeven met welk type vliegtuig we te maken hebben, kunnen we met behulp van een Select Case ... bepalen wat er feitelijk moet gebeuren. Bekijkt u onderstaande code zorgvuldig, hier wordt dit programmeerprobleem opgelost zonder gebruik te maken van polymorfisme.
Namespace ZonderPolymorfisme
Public Enum VliegtuigTypeEnum EenMotorig = 0 TweeMotorig = 1 End Enum
Public Class CVliegtuig
Private menmVliegtuigType As VliegtuigTypeEnum
Public Sub New(ByVal enmVliegtuigType As VliegtuigTypeEnum) menmVliegtuigType = enmVliegtuigType End Sub
Public Sub StartMotor() Select Case menmVliegtuigType Case VliegtuigTypeEnum.EenMotorig MessageBox.Show("Start motor 1") Case VliegtuigTypeEnum.TweeMotorig MessageBox.Show("Start motor 1") MessageBox.Show("Start motor 2") End Select End Sub
End Class
End Namespace |
| Codevoorbeeld 6. Zonder polymorfisme |
Afhankelijk van hoe het object is aangemaakt worden er of één of twee messageboxen getoond. Hoewel deze code er op zich best goed uitziet, is het niet de beste implementatie. Een eenmotorig vliegtuig heeft geen twee motoren die we kunnen starten en tweemotorige vliegtuigen hebben altijd twee motoren die we moeten starten. Vanuit het oogpunt van OOP hoort de code niet gemixt te zijn; feitelijk hebben we te maken met twee classes.
Namespace MetPolymorfisme
Public MustInherit Class CVliegtuig Public MustOverride Sub StartMotor() End Class
Public NotInheritable Class EenMotorigVliegtuig Inherits CVliegtuig Public Overrides Sub StartMotor() MessageBox.Show("Start motor 1") End Sub End Class Public NotInheritable Class TweeMotorigVliegtuig Inherits CVliegtuig Public Overrides Sub StartMotor() MessageBox.Show("Start motor 1") MessageBox.Show("Start motor 2") End Sub End Class
End Namespace
|
| Codevoorbeeld 7. Met polymorfisme |
Wanneer u de twee oplossingen met elkaar vergelijkt zult u het met mij eens zijn dat de versie met polymorfisme er eenvoudiger uitziet en zeker makkelijker te onderhouden is. Wanneer u besluit om een viermotorig vliegtuig te implementeren kunt u de code eenvoudig uitbreiden, zonder de bestaande implementatie aan te passen. Dit is een veel veiligere manier van werken, dan dat u de bestaande code moet uitbreiden met nieuwe 'Case..'.
Tevens ziet u hier het gebruik van namespaces. Dit houdt concreet in dat we in de applicatie meerdere classes van het type CVliegtuig kunnen benoemen. Door te verwijzen naar de namespace, weet de compiler welke class gebruikt moet worden.
Private Sub btnZonder1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnZonder1.Click Dim objVliegtuig As New ZonderPolymorfisme.CVliegtuig( _ ZonderPolymorfisme.VliegtuigTypeEnum.EenMotorig) objVliegtuig.StartMotor()
End Sub
Private Sub btnZonder2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnZonder2.Click Dim objVliegtuig As New ZonderPolymorfisme.CVliegtuig( _ ZonderPolymorfisme.VliegtuigTypeEnum.TweeMotorig) objVliegtuig.StartMotor()
End Sub
' MET POLYMORFISME Private Sub btnMet1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnMet1.Click
Dim objVliegtuig As New MetPolymorfisme.EenMotorigVliegtuig objVliegtuig.StartMotor()
End Sub
Private Sub btnMet2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnMet2.Click
Dim objVliegtuig As New MetPolymorfisme.TweeMotorigVliegtuig
objVliegtuig.StartMotor()
End Sub
|
| Codevoorbeeld 8. Gebruik polymorfisme |
Er zijn drie vormen van polymorfisme.
Interface polymorfisme
Deze vorm van polymorfisme is in principe ook mogelijk met Visual Basic 6. Feitelijk houdt het in dat meerdere classes een interface kunnen implementeren, maar ook dat één class meerdere interfaces kan implementeren.
Zoals al eerder is beschreven bevat de interface geen 'echte' code en het gedrag wordt bepaald door de code die er in het object aan wordt toegevoegd.
Inheritance polymorfisme
Inheritance polymorfisme ondersteunt meerdere afgeleide classes van een baseclass. Elke afgeleide class erft alles, inclusief de implementatiecode en kan worden uitgebreid met eigen methods en properties. Wanneer u wilt dat een bepaalde methode binnen de afgeleide class moet worden overschreven, gebruikt u het Overrides keyword. Een voorwaarde is echter wel dat in de base class de desbetreffende methode is aangemerkt als Overridable. Wanneer u wilt dat de oorspronkelijke code in de baseclass ook wordt uitgevoerd dan kunt u deze aanroepen door:
MyBase.MethodeNaam.
Public Class CHond
Public Overridable Sub Blaffen() MessageBox.Show("Waf Waf") End Sub
End Class
Public Class CKeffertje
Inherits CHond
Public Overrides Sub Blaffen() MyBase.Blaffen() ' Hierdoor zegt het eerst Waf Waf! MessageBox.Show("Woef Woef") End Sub
End Class
|
| Codevoorbeeld 9. Gebruik MyBase |
Het aanroepen van de code resulteert in de volgende messageboxen. U ziet dat ook de code die in CHond is geïmplementeerd wordt uitgevoerd.

Afbeelding 3.Gebruik MyBase
Abstract polymorfisme
Abstracte classes moeten geïmplementeerd worden door een afgeleide class, want zijzelf kunnen niet worden geïnstantieerd. Aangezien abstracte classes geen implementatiecode bevatten, zult u dat voor elke method en property zelf moeten schrijven. In het gebruik lijkt abstract polymorfisme erg op interface polymorfisme.
| Me, MyBase en MyClass |
In de bovenstaande codevoorbeelden bent u al een aantal keer de keywords Me en MyBase tegengekomen. U gebruikt MyBase wanneer u wilt verwijzen naar properties of methods op het 'parentobject'. U gebruikt Me, wanneer u wilt verwijzen naar variabelen, properties of methods. Het keyword MyClass is echter wat lastiger te begrijpen. Het lijkt erg veel op Me, met dien verstande dat het niet kijkt naar de huidige instantie van de class. Wanneer u een niet geïmplementeerde overridable methode aanroept binnen de afgeleide class, bestaat het gevaar dat die methode niet volledig wordt uitgevoerd in de context van de baseclass, waardoor het resultaat erg onvoorspelbaar wordt. Het zou kunnen zijn dat u een methode wel hebt geïmplementeerd en dat de baseclass die methode gebruikt. Gebruik daarom het MyClass keyword om ervoor te zorgen dat een method in de baseclass altijd de methods en properties in die baseclass gebruikt (in tegenstelling tot de 'overridden' versie in de afgeleide class). |
Tot slot
De mogelijkheid om volledig object georiënteerd te programmeren met VB.NET is een grote vooruitgang. Wilt u optimaal gebruik maken van deze features, dan moet u zich de tijd gunnen om alle principes in de vingers te krijgen; maar het is die tijdsinvestering dubbel en dwars waard.
Een aantal zaken met betrekking tot overerving en polymorfisme, zoals bijvoorbeeld 'shadowing' of 'multiple inheritance' zijn niet besproken. Deze termen kunnen als beginpunt dienen om deze materie verder te kunnen uitdiepen.
Bronnen
- Programming Microsoft Visual Basic .NET Francesco Balena (0-7356-1375-3)
- Programmeercursus OOP met VB.NET en C# Robin A. Reynolds-Haertle (90-395-1960-9)
- http://www.informit.com