|
Dit artikel is gepubliceerd op woensdag 28 april 2010 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.
Regelmatig komt het voor dat men in applicaties wil werken met een verzameling ( een collectie ) van objecten. Net zoals we arrays kunnen creëren van Integers of Strings ( of eender welk type ) kunnen we ook een array aanmaken van een user-defined referencetype. Hieronder zien we een voorbeeld van een array met elementen van het type Person. Visual Basic Broncode Class Person
Private m_Name As String
Public Property Name() As String
Get
Name = m_Name
End Get
Set(ByVal value As String)
m_Name = value
End Set
End Property
End Class
Module Client1
Dim persons As Person()
Dim personsCount As Integer
Sub Main()
Dim person1 As Person = New Person
person1.Name = "John"
Dim person2 As Person = New Person
person2.Name = "Jane"
Console.WriteLine(personsCount)
AddPerson(person1)
AddPerson(person2)
Console.WriteLine(personsCount)
Console.WriteLine(persons(1).Name)
Console.ReadLine()
End Sub
Sub AddPerson(ByVal item As Person)
ReDim Preserve persons(personsCount)
persons(personsCount) = item
personsCount += 1
End Sub
End ModuleDownload Visual Basic Broncode Bekijk deze Broncode in Visual C#
Visual Basic Output 0
2
Jane De client is hier zelf verantwoordelijk voor het opstellen en definiëren van de logica omtrent het beheer van deze collectie van Person objecten. Zo heeft de client hier bijvoorbeeld zelf moeten bepalen ( implementeren ) wat er juist dient te gebeuren wanneer een Person object wordt toegevoegd aan de verzameling.
De mogelijkheid bestaat ook deze logica weg te abstraheren voor de clients, dit bijvoorbeeld door collectie objecten te creëren. Met onder andere als bedoeling ervoor te zorgen dat de clients zich hierbij niet meer hoeven bezig te houden.
Collectie objecten ( gedefinieerd door collectietypen ) kunnen ook beschouwd worden als een toepassing van containment. Deze collectietypen kunnen het eenvoudiger maken voor clients met een collectie van objecten om te gaan. Visual Basic Broncode Class Persons
Private m_Items As Person()
Default Public ReadOnly Property Item(ByVal index As Integer) As Person
Get
Item = m_Items(index)
End Get
End Property
Private m_Count As Integer
Public ReadOnly Property Count() As Integer
Get
Count = m_Count
End Get
End Property
Public Sub Add(ByVal item As Person)
ReDim Preserve m_Items(Count)
m_Items(Count) = item
m_Count += 1
End Sub
End Class
Module Client2
Sub Main()
Dim person1 As Person = New Person
person1.Name = "John"
Dim person2 As Person = New Person
person2.Name = "Jane"
Dim persons1 As Persons = New Persons
Console.WriteLine(persons1.Count)
persons1.Add(person1)
persons1.Add(person2)
Console.WriteLine(persons1.Count)
Console.WriteLine(persons1(1).Name)
Console.WriteLine(persons1.Item(1).Name)
Console.ReadLine()
End Sub
End Module
Module Client3
Sub Main()
Dim person1 As Person = New Person
person1.Name = "John"
Dim persons1 As Persons = New Persons
persons1.Add(person1)
End Sub
End ModuleDownload Visual Basic Broncode Bekijk deze Broncode in Visual C#
Visual Basic Output 0
2
Jane
Jane Meerdere clients ( zoals bovenstaande Client2 en Client3 ) kunnen nu deze collectielogica herbruiken, zonder dat die steeds opnieuw herhaald wordt.
Bemerk hoe de identifier van het collectietype ( PersonS ) duidelijk aangeeft dat een collect object van dat type meerdere Person objecten kan bevatten ( meervoud gebruikt voor de identifier ). 9.6.1. Indexer PropertyRegel (1) geef de getter aan van wat men noemt een "indexer". Dit is een typische member voor collectietypen waarvan de elementen via een index benaderd kunnen worden. Deze indexer levert een Person object op ( vandaar de verwijzing naar het type Person in de As clausule van die membersignatuur. Meteen is hier ook aangegeven dat een Property net zoals procedures en functies argumentvariabelen kan gebruiken. Alle mogelijkheden omtrent argumenten ( meerdere argumenten, doorgeven van argumentwaarden by reference of by value, optionele argumenten, parameterarrays, ... ) zijn hier ook beschikbaar. Hier werd ervoor ge-opteerd de indexer readonly te maken, dit is echter niet noodzakelijk, afhankelijk van de vereisten kan men deze property ook writeable maken. Terug naar boven 9.6.2. Default PropertyHet keyword Default ( hier gebruikt voor de indexer ) zorgt ervoor dat men in de client niet expliciet meer hoeft te verwijzen naar de identifier van de property ( zoals in regels (2) en (3) wordt geïllustreerd ). Meteen na de objectexpressie ( gevormd door de identifier van de objectvariabele ) kan men de argumentenlijst plaatsen. Default kan enkele gebruikt worden bij properties met argumenten. Dat ( niet optionele ) argumenten noodzakelijk zijn is eigenlijk logisch : in het geval dat men naar een argumentloze property zou kunnen verwijzen zonder de identifier op te geven, bekomt men gewoon de identifier van de objectvariabele, waarbij de compiler niet meer zou weten of het nu een verwijzing is naar het object of naar de default eigenschap.
In bovenstaand voorbeeld gaat het om een erg beperkt ( met beperkte logica en mogelijkheden ) collectietype gedefinieerd. Veel meer logica kan nog worden opgenomen. Zo is het soms nuttig bij het toevoegen van een element ( hier Person object ) na te gaan of de collectie niet reeds dat element bevat, enz... . Het is ook aan te bevelen het "symmetrieprincipe" na te leven bij het uitkiezen van de voor de clients beschikbare members. Hiermee wordt in dit geval bedoeld dat als er een Add method aanwezig is, een Remove method ook wel van nut zal zijn. Maar dit blijft natuurlijk volledig naar de keuze van de auteur.
Verderop wordt behandeld welke voorgedefinieerde collectiestructuren men allemaal kan gebruiken, en hoe deze eventueel aan te passen. Terug naar boven
9.6.3. OefeningenOpgave 1 :
Maak een klasse om voorstellingen te maken van rechthoeken. Een rechthoek heeft als toestand een bepaalde hoogte en breedte ( instelbaar en opvraagbaar ). Van rechthoeken moet men ook de oppervlakte kunnen bepalen.
Creëer ook een collectietype om een verzameling van rechthoeken te beheren. De elementen in dergelijke collectie moet men via een index kunnen aanspreken. Aan de collectie moet men rechthoeken kunnen toevoegen, en rechthoeken op een bepaalde index kunnen verwijderen. De som van alle oppervlaktes van alle rechthoek elementen moet men kunnen opvragen van de collectie.
Schrijf zelf een client om bovenstaande klassen te testen. Oplossing 1 : Visual Basic Broncode Class Rectangle
Private m_Height As Integer
Public Property Height() As Integer
Get
Height = m_Height
End Get
Set(ByVal value As Integer)
m_Height = value
End Set
End Property
Private m_Width As Integer
Public Property Width() As Integer
Get
Width = m_Width
End Get
Set(ByVal value As Integer)
m_Width = value
End Set
End Property
Public Function GetArea() As Integer
GetArea = Height * Width
End Function
End Class
Class Rectangles
Private m_Items As Rectangle()
Default Public ReadOnly Property Item(ByVal index As Integer) As Rectangle
Get
Item = m_Items(index)
End Get
End Property
Private m_Count As Integer
Public ReadOnly Property Count() As Integer
Get
Count = m_Count
End Get
End Property
Public Sub Add(ByVal rectangle As Rectangle)
ReDim Preserve m_Items(Count)
m_Items(Count) = rectangle
m_Count += 1
End Sub
Public Sub RemoveAt(ByVal index As Integer)
For itemIndex As Integer = index To Count - 2
m_Items(itemIndex) = m_Items(itemIndex + 1)
Next
ReDim Preserve m_Items(Count - 2)
m_Count -= 1
End Sub
Public Function GetTotalArea() As Integer
For Each item As Rectangle In m_Items
GetTotalArea += item.GetArea()
Next
End Function
End Class
Module Exercise1Solution
Sub Main()
Dim rectangle1 As Rectangle = New Rectangle
rectangle1.Height = 1
rectangle1.Width = 2
Console.WriteLine(rectangle1.GetArea())
Dim rectangle2 As Rectangle = New Rectangle
rectangle2.Height = 3
rectangle2.Width = 4
Console.WriteLine(rectangle2.GetArea())
Dim rectangles1 As Rectangles = New Rectangles
rectangles1.Add(rectangle1)
rectangles1.Add(rectangle2)
For index As Integer = 0 To rectangles1.Count - 1
Console.Write(rectangles1(index).GetArea() & " ")
Next
Console.WriteLine()
Console.WriteLine(rectangles1.GetTotalArea())
rectangles1.RemoveAt(1)
For index As Integer = 0 To rectangles1.Count - 1
Console.Write(rectangles1(index).GetArea() & " ")
Next
Console.WriteLine()
Console.WriteLine(rectangles1.GetTotalArea())
Console.ReadLine()
End Sub
End ModuleDownload Visual Basic Broncode Bekijk deze Broncode in Visual C#
Visual Basic Output 2
12
2 12
14
2
2 Opgave 2 : Maak een klasse waarvan objecten een voorstelling zijn van een zin. Een zin is een ge-ordende lijst van woorden ( Strings ).
Het moet mogelijk zijn : - een woord aan de zin toe te voegen ( achteraan toevoegen ) - het aantal woorden van de zin op te vragen - de positie ( index ) van een woord in de zin op te vragen - te vragen of een woord zich al dan niet in de zin bevindt - een woord op een bepaalde positie ( index ) op te vragen Oplossing 2 : Visual Basic Broncode Module SentenceTestFixture
Sub Main()
Dim sentence1 As Sentence = New Sentence
Console.WriteLine(sentence1.Count = 0)
Console.WriteLine(sentence1.IndexOf("Hello") = -1)
Console.WriteLine(sentence1.Contains("Hello") = False)
sentence1.Add("Hello")
Console.WriteLine(sentence1.Count = 1)
Console.WriteLine(sentence1.Item(0) = "Hello")
Console.WriteLine(sentence1.IndexOf("Hello") = 0)
Console.WriteLine(sentence1.Contains("Hello") = True)
Console.WriteLine(sentence1.IndexOf("World") = -1)
Console.WriteLine(sentence1.Contains("World") = False)
sentence1.Add("World")
Console.WriteLine(sentence1.Count = 2)
Console.WriteLine(sentence1.Item(0) = "Hello")
Console.WriteLine(sentence1.Item(1) = "World")
Console.WriteLine(sentence1.IndexOf("Hello") = 0)
Console.WriteLine(sentence1.Contains("Hello") = True)
Console.WriteLine(sentence1.IndexOf("World") = 1)
Console.WriteLine(sentence1.Contains("World") = True)
Console.ReadLine()
End Sub
End Module
Class Sentence
Private m_Words As String() = New String() {}
Public ReadOnly Property Count() As Integer
Get
Count = m_Words.Length
End Get
End Property
Default Public ReadOnly Property Item(ByVal index As Integer) As String
Get
If index >= 0 AndAlso index < Count Then Item = m_Words(index)
End Get
End Property
Public Sub Add(ByVal word As String)
ReDim Preserve m_Words(Count)
m_Words(Count - 1) = word
End Sub
Public Function IndexOf(ByVal word As String) As Integer
IndexOf = -1
If Count > 0 Then
Dim found As Boolean
Do
IndexOf += 1
found = (Item(IndexOf) = word)
Loop Until found OrElse IndexOf = Count - 1
If Not found Then IndexOf = -1
End If
End Function
Public Function Contains(ByVal word As String) As Boolean
Contains = (IndexOf(word) <> -1)
End Function
End ClassDownload Visual Basic Broncode Bekijk deze Broncode in Visual C#
Visual Basic Output True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True Terug naar boven
9.6.4. Alternatieve ImplementatiesBovenstaande implementatie voor Sentence gebruikt intern een String array om de bevatte woorden in te beheren. Dit is maar één mogelijkheid, er zijn echter tal van mogelijke implementaties.
Onderstaande variatie gaat bijvoorbeeld de zware ReDim Preserve operaties zo vaak mogelijk vermijden door gebruik te maken van een bepaalde capaciteit, die verdubbeld wordt indien deze te klein wordt : Visual Basic Broncode Namespace Alternative1
Class Sentence
Private m_Count As Integer
Private m_Capacity As Integer = 16
Private m_Words(m_Capacity - 1) As String
Public ReadOnly Property Count() As Integer
Get
Count = m_Count
End Get
End Property
Default Public ReadOnly Property Item(ByVal index As Integer) As String
Get
Item = m_Words(index)
End Get
End Property
Public Sub Add(ByVal word As String)
m_Count += 1
If Count > m_Capacity Then
m_Capacity *= 2
ReDim Preserve m_Words(m_Capacity - 1)
End If
m_Words(Count - 1) = word
End Sub
Public Function IndexOf(ByVal word As String) As Integer
IndexOf = -1
If Count > 0 Then
Dim found As Boolean
Do
IndexOf += 1
found = (Item(IndexOf) = word)
Loop Until found OrElse IndexOf = Count - 1
If Not found Then IndexOf = -1
End If
End Function
Public Function Contains(ByVal word As String) As Boolean
Contains = (IndexOf(word) <> -1)
End Function
End Class
End NamespaceDownload Visual Basic Broncode Bekijk deze Broncode in Visual C#
Voor meer details over het intelligent vergroten van arrays bekijk je best nog eens het topic over het dimensioneren van tabellen.
Onderstaande variatie maakt intern helemaal geen gebruik van een array, en is hier opgenomen om te illustreren hoe de implementatie totaal anders kan zijn zonder dat dit enige impact heeft voor - op zijn minst de code van - de client.
Er wordt in onderstaand voorbeeld gebruik gemaakt van een gelinkte lijst van woorden : Visual Basic Broncode Namespace Alternative2
Class Sentence
Private m_StartWord As Word = New Word With {.Value = "sentinel"}
Public ReadOnly Property Count() As Integer
Get
Dim element As Word = m_StartWord
Do Until element.NextWord Is Nothing
element = element.NextWord
Count += 1
Loop
End Get
End Property
Default Public ReadOnly Property Item(ByVal index As Integer) As String
Get
If index >= 0 AndAlso index < Count Then
Dim elementIndex As Integer = -1
Dim element As Word = m_StartWord
Do Until elementIndex = index OrElse element.NextWord Is Nothing
elementIndex += 1
element = element.NextWord
Loop
If elementIndex = index Then Item = element.Value
End If
End Get
End Property
Public Sub Add(ByVal word As String)
Dim lastWord As Word = m_StartWord
Do Until lastWord.NextWord Is Nothing
lastWord = lastWord.NextWord
Loop
lastWord.NextWord = New Word With {.Value = word}
End Sub
Public Function IndexOf(ByVal word As String) As Integer
Dim elementIndex As Integer = -1
Dim element As Word = m_StartWord.NextWord
Dim found As Boolean
Do Until element Is Nothing OrElse found
elementIndex += 1
found = (element.Value = word)
element = element.NextWord
Loop
If found Then
IndexOf = elementIndex
Else
IndexOf = -1
End If
End Function
Public Function Contains(ByVal word As String) As Boolean
Contains = (IndexOf(word) <> -1)
End Function
End Class
Class Word
Private m_Value As String
Public Property Value() As String
Get
Value = m_Value
End Get
Set(ByVal value As String)
m_Value = value
End Set
End Property
Private m_NextWord As Word
Public Property NextWord() As Word
Get
NextWord = m_NextWord
End Get
Set(ByVal value As Word)
m_NextWord = value
End Set
End Property
End Class
End NamespaceDownload Visual Basic Broncode Bekijk deze Broncode in Visual C#
Bekijk het topic over LinkedList voor meer details over en andere voorbeelden van gelinkte lijsten.
Voor je de in bovenstaande oefening opgestelde testcode uit op deze implementatie van Sentence dan zie je hoe nog steeds alle testen slagen. Wat illustreert dat de manier waarop klassen geïmplementeerd worden vrij kunnen variëren ( eventueel in een latere fase ), zonder dat de client zich daarvan bewust hoeft te zijn. De client intresseert zich immers ook niet in hoe iets ( een type ) geïmplementeerd wordt. Het enigste wat een gebruiker van het type Sentence intresseert is dat hij woorden kan toevoegen aan dergelijke zin, en naderhand bijvoorbeeld de positie van een woord in deze zin kan opvragen. Hoe deze woorden worden bewaard, en wat er bijvoorbeeld juist gebeurt als de gebruiker een positie van een woord opvraagd, of over het algemeen : hoe het type zijn verantwoordelijkheden verwezelijkt, kan hem echter niet schelen.
Dit artikel is gepubliceerd op woensdag 28 april 2010 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.
Visual Basic 2008 & 2010 Boeken
Berichten
|