|
Dit artikel is gepubliceerd op zondag 31 juli 2011 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 2010 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 PropertyEnd ClassModule 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 SubEnd ModuleDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application 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 2010 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 SubEnd ClassModule 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 SubEnd ModuleModule Client3 Sub Main() Dim person1 As Person = New Person person1.Name = "John" Dim persons1 As Persons = New Persons persons1.Add(person1) End SubEnd ModuleDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application 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.8.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. boven
9.8.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. boven
9.8.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 2010 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 FunctionEnd ClassClass 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 FunctionEnd ClassModule 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 SubEnd ModuleDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application 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 2010 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 SubEnd ModuleClass 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 FunctionEnd ClassDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True boven
9.8.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 2010 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 ClassEnd NamespaceDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
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 2010 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 ClassEnd NamespaceDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
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 zondag 31 juli 2011 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.
|