3. Klasy, struktury, interfejsy
3.1. Obiekt na przykładzie
3.1.1. Informacje ogólne
Teraz zajmiemy się programowaniem obiektowym na przykładach. Obiekt to jednostka zawierająca dane określające jego stan (nazywane właściwościami) oraz funkcje (nazywane metodami). Obiekt jest tworzony na podstawie wzorca zwanego klasą:
Public Class c1
' atrybuty
Private p1 As type1
Private p2 As type2
....
' metoda
Public Sub m1(....)
...
End Sub
' metoda
Public Function m2(...)
....
End Function
End Class
Na podstawie poprzedniej klasy C1 można utworzyć wiele obiektów O1, O2,… Wszystkie będą miały właściwości p1, p2,… oraz metody m3, m4,… Jednak będą miały różne wartości dla swoich właściwości pi, dzięki czemu każda z nich będzie miała swój własny stan. Analogicznie deklaracja
tworzy dwa obiekty (termin ten jest tu niepoprawny) typu (klasy) Integer. Ich jedyną właściwością jest ich wartość. Jeśli O1 jest obiektem typu C1, O1.p1 oznacza właściwość p1 obiektu O1, a O1.m1 oznacza metodę m1 obiektu O1. Rozważmy pierwszy model obiektu: klasę personne.
3.1.2. Definicja klasy „osoba”
Definicja klasy personne będzie wyglądać następująco:
Public Class personne
' atrybuty
Private prenom As String
Private nom As String
Private age As Integer
' metoda
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' metoda
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Mamy tu do czynienia z definicją klasy, a więc typu danych. Tworząc zmienne tego typu, będziemy nazywać je obiektami lub instancjami klas. Klasa jest zatem szablonem, na podstawie którego tworzone są obiekty. Elementy składowe lub pola klasy mogą być danymi (atrybutami), metodami (funkcjami) lub właściwościami. Właściwości to specjalne metody służące do sprawdzania lub ustalania wartości atrybutów obiektu. Pola te mogą być opatrzone jednym z trzech następujących słów kluczowych:
Pole prywatne (private) jest dostępne wyłącznie dla wewnętrznych metod klasy | |
Pole publiczne (public) jest dostępne dla każdej funkcji, niezależnie od tego, czy została zdefiniowana w ramach klasy, czy nie | |
Pole chronione (protected) jest dostępne wyłącznie za pośrednictwem wewnętrznych metod klasy lub obiektu pochodnego (patrz dalsza część dotycząca dziedziczenia). |
Zazwyczaj dane klasy są deklarowane jako prywatne, podczas gdy jej metody i właściwości są deklarowane jako publiczne. Oznacza to, że użytkownik obiektu (programista):
- nie będzie miał bezpośredniego dostępu do danych prywatnych obiektu
- będzie mógł wywoływać publiczne metody obiektu, a w szczególności te, które zapewniają dostęp do jego danych prywatnych.
Składnia deklaracji klasy jest następująca:
public class classe
private donnée ou méthode ou propriété privée
public donnée ou méthode ou propriété publique
protected donnée ou méthode ou propriété protégée
end class
Kolejność deklaracji atrybutów private, protected i public jest dowolna.
3.1.3. Metoda inicjuje
Wróćmy do naszej klasy [personne] zadeklarowanej jako:
Public Class personne
' atrybuty
Private prenom As String
Private nom As String
Private age As Integer
' metoda
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' metoda
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Jaka jest rola metody initialise? Ponieważ nom, prenom i age są danymi prywatnymi klasy personne, instrukcje:
są niedozwolone. Musimy zainicjować obiekt typu personne za pomocą metody publicznej. Do tego służy metoda initialise. Napiszemy:
Zapis p1.initialise jest poprawny, ponieważ initialise jest publicznie dostępny.
3.1.4. Operator new
Sekwencja instrukcji
jest nieprawidłowa. Instrukcja
deklaruje p1 jako odwołanie do obiektu typu personne. Obiekt ten jeszcze nie istnieje, a zatem p1 nie jest zainicjowany. To tak, jakbyśmy napisali:
gdzie za pomocą słowa kluczowego nothing wyraźnie wskazujemy, że zmienna p1 nie odwołuje się jeszcze do żadnego obiektu. Gdy następnie zapisujemy
wywołujemy metodę initialise obiektu, do którego odwołuje się zmienna p1. Jednak ten obiekt jeszcze nie istnieje, więc kompilator zgłosi błąd. Aby zmienna p1 odwoływała się do obiektu, należy zapisać:
Spowoduje to utworzenie jeszcze niezinicjalizowanego obiektu typu personne: atrybuty nom i prenom, które są odwołaniami do obiektów typu String, będą miały wartość nothing, a age – wartość 0. Ma więc miejsce inicjalizacja domyślna. Teraz, gdy p1 odwołuje się do obiektu, instrukcja inicjalizacji tego obiektu
jest poprawna.
3.1.5. Słowo kluczowe Me
Przyjrzyjmy się kodowi metody initialise:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Instrukcja Me.prenom=P oznacza, że atrybut prenom bieżącego obiektu (Me) otrzymuje wartość P. Słowo kluczowe Me określa obiekt bieżący: ten, w którym znajduje się wykonywana metoda. Skąd to wiemy? Przyjrzyjmy się, jak przebiega inicjalizacja obiektu, do którego odwołuje się p1 w programie wywołującym:
Wywoływana jest metoda initialise obiektu p1. Gdy w tej metodzie odwołuje się do obiektu Me, w rzeczywistości odwołuje się do obiektu p1. Metoda initialise mogłaby również zostać zapisana w następujący sposób:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
prenom = P
nom = N
Me.age = age
End Sub
Gdy metoda obiektu odwołuje się do atrybutu A tego obiektu, zapis Me.A jest domyślny. Należy go użyć jawnie w przypadku konfliktu identyfikatorów. Dotyczy to następującej instrukcji:
Me.age=age;
gdzie age oznacza atrybut bieżącego obiektu, a także parametr age przekazany do metody. Należy wówczas usunąć niejednoznaczność, określając atrybut age jako Me.age.
3.1.6. Program testowy
Oto krótki program testowy:
Public Class personne
' atrybuty
Private prenom As String
Private nom As String
Private age As Integer
' metoda
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' metoda
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
oraz uzyskane wyniki:
dos>vbc personne1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
3.1.7. Korzystanie z pliku skompilowanych klas (assembly)
Należy zauważyć, że w poprzednim przykładzie w naszym programie testowym występują dwie klasy: klasy personne i test1. Istnieje inny sposób postępowania:
-
kompilujemy klasę „osoba” do osobnego pliku zwanego zestawem (assembly). Plik ten ma rozszerzenie .dll
-
kompilujemy klasę „test1”, odwołując się do zestawu zawierającego klasę „osoba”.
Oba pliki źródłowe wyglądają następująco:
test.vb | |
personne2.vb | |
Klasa personne jest kompilowana za pomocą następującej instrukcji:
dos>vbc /t:library personne2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
W wyniku kompilacji powstał plik personne2.dll. To właśnie opcja kompilacji /t:library powoduje utworzenie pliku „assembly”. Teraz skompilujmy plik test.vb:
dos>vbc /r:personne2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
24/02/2004 16:51 3 072 test.exe
Opcja kompilacji /r:personne2.dll informuje kompilator, że pewne klasy znajdują się w pliku personne2.dll. Gdy w pliku źródłowym test.vb kompilator napotka odwołanie do klasy personne, która nie została zadeklarowana w pliku źródłowym test.vb, będzie szukał klasy personne w plikach .dll wskazanych przez opcję /r. Znajdzie tam klasę personne w zestawie personne2.dll. W tym zestawie można było umieścić inne klasy. Aby podczas kompilacji wykorzystać kilka plików skompilowanych klas, należy wpisać:
Uruchomienie programu test1.exe daje następujące wyniki:
3.1.8. Inna metoda inicjuje
Rozważmy ponownie klasę personne i dodajmy do niej następującą metodę:
' metoda
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
Mamy teraz dwie metody o nazwie initialise: jest to dopuszczalne, o ile przyjmują one różne parametry. Tak właśnie jest w tym przypadku. Parametrem jest teraz odwołanie P do osoby. Atrybuty osoby P są następnie przypisywane do bieżącego obiektu (Me). Warto zauważyć, że metoda initialise ma bezpośredni dostęp do atrybutów obiektu P, mimo że są one typu private. Zasada ta zawsze obowiązuje: obiekt O1 klasy C zawsze ma dostęp do atrybutów obiektów tej samej klasy C. Oto test nowej klasy personne, która została skompilowana do klasy personne.dll, jak wyjaśniono wcześniej:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
Module test1
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne
p2.initialise(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
oraz jej wyniki:
3.1.9. Konstruktory klasy „osoba”
Konstruktor to procedura o nazwie New, wywoływana podczas tworzenia obiektu. Zazwyczaj służy ona do jego inicjalizacji. Jeśli klasa posiada konstruktor przyjmujący n argumentów argi, deklaracja i inicjalizacja obiektu tej klasy może przebiegać w następującej formie:
dim objet as classe =new classe(arg1,arg2, ... argn)
lub
dim objet as classe
…
objet=new classe(arg1,arg2, ... argn)
Gdy klasa posiada jeden lub więcej konstruktorów, do utworzenia obiektu tej klasy należy obowiązkowo użyć jednego z nich. Jeśli klasa C nie posiada żadnego konstruktora, ma ona domyślny konstruktor bez parametrów: public New(). Atrybuty obiektu są wówczas inicjowane wartościami domyślnymi. Tak właśnie było w poprzednich programach, w których napisaliśmy:
Stwórzmy dwa konstruktory dla naszej klasy personne:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
' klasa osoba
Public Class personne
' atrybuty
Private prenom As String
Private nom As String
Private age As Integer
' konstruktory
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
initialise(P)
End Sub
' metody inicjalizacji obiektu
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
' metoda
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Nasze dwa konstruktory ograniczają się do wywołania odpowiednich metod initialise. Przypominamy, że gdy w konstruktorze pojawia się na przykład zapis initialise(P), kompilator przekształca go na Me.initialise(P). W konstruktorze wywoływana jest zatem metoda initialise w celu przetworzenia obiektu, do którego odwołuje się Me, czyli obiektu bieżącego, który jest właśnie tworzony. Oto krótki program testowy:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
' strona testowa
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
oraz uzyskane wyniki:
3.1.10. Odwołania do obiektów
Zawsze używamy tej samej klasy personne. Program testowy wygląda następująco:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
' strona testowa
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 odwołuje się do tego samego obiektu co p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 odwołuje się do obiektu, który będzie kopią obiektu, do którego odwołuje się p1
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' zmieniamy stan obiektu, do którego odwołuje się p1
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' ponieważ p2 = p1, obiekt, do którego odwołuje się p2, musiał zmienić swój stan
Console.Out.Write("p2=")
p2.identifie()
' ponieważ p3 nie odwołuje się do tego samego obiektu co p1, obiekt, do którego odwołuje się p3, nie musiał ulec zmianie
Console.Out.Write("p3=")
p3.identifie()
End Sub
End Module
Otrzymane wyniki są następujące:
p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30
Gdy zadeklaruje się zmienną p1 za pomocą
p1 odwołuje się do obiektu personne("Jean","Dupont",30), ale nie jest samym obiektem. W języku C można by powiedzieć, że jest to wskaźnik, c.a.d, wskazujący adres utworzonego obiektu. Jeśli następnie wpiszemy:
To nie obiekt personne("Jean","Dupont",30) zostaje zmodyfikowany, lecz zmienia się wartość odwołania p1. Obiekt personne("Jean","Dupont",30) zostanie „utracony”, jeśli nie będzie odwołany przez żadną inną zmienną.
Gdy piszemy:
inicjujemy wskaźnik p2: „wskazuje” on na ten sam obiekt (oznacza ten sam obiekt), co wskaźnik p1. Zatem jeśli zmodyfikujemy obiekt, na który „wskazuje” (lub do którego odwołuje się) wskaźnik p1, zmodyfikujemy również obiekt, do którego odwołuje się wskaźnik p2.
Gdy zapisujemy:
tworzony jest nowy obiekt, będący kopią obiektu, do którego odwołuje się p1. Ten nowy obiekt będzie odwołany przez p3. Jeśli zmodyfikujemy obiekt, na który „wskazuje” (lub do którego odwołuje się) p1, nie wpłynie to w żaden sposób na obiekt, do którego odwołuje się p3. Potwierdzają to uzyskane wyniki.
3.1.11. Obiekty tymczasowe
W wyrażeniu można jawnie wywołać konstruktor obiektu: obiekt ten jest tworzony, ale nie mamy do niego dostępu (na przykład w celu jego modyfikacji). Ten obiekt tymczasowy jest tworzony na potrzeby obliczenia wyrażenia, a następnie porzucany. Zajmowana przez niego przestrzeń pamięci zostanie później automatycznie odzyskana przez program zwany „zbieraczem śmieci”, którego zadaniem jest odzyskiwanie przestrzeni pamięci zajmowanej przez obiekty, do których nie odwołują się już dane programu. Rozważmy następujący nowy program testowy:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
' strona testowa
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
i zmodyfikujmy konstruktory klasy personne tak, aby wyświetlały komunikat:
' konstruktory
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Console.Out.WriteLine("Constructeur personne(String, String, integer)")
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
Console.Out.WriteLine("Constructeur personne(personne)")
initialise(P)
End Sub
Otrzymujemy następujące wyniki:
dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30
ilustrujące kolejną budowę obu obiektów tymczasowych.
3.1.12. Metody odczytu i zapisu atrybutów prywatnych
Dodajemy do klasy personne metody niezbędne do odczytu lub modyfikacji stanu atrybutów obiektów:
Imports System
Public Class personne
' atrybuty
Private prenom As [String]
Private nom As [String]
Private age As Integer
' konstruktory
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub New(ByVal P As personne)
Me.prenom = P.prenom
Me.nom = P.nom
Me.age = P.age
End Sub
' identyfikatory
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' metody dostępu
Public Function getPrenom() As [String]
Return prenom
End Function
Public Function getNom() As [String]
Return nom
End Function
Public Function getAge() As Integer
Return age
End Function
'modyfikatory
Public Sub setPrenom(ByVal P As [String])
Me.prenom = P
End Sub
Public Sub setNom(ByVal N As [String])
Me.nom = N
End Sub
Public Sub setAge(ByVal age As Integer)
Me.age = age
End Sub
End Class
Testujemy nową klasę za pomocą następującego programu:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
' strona testowa
Public Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
P.setAge(56)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
End Sub
End Module
i uzyskujemy następujące wyniki:
3.1.13. Właściwości
Istnieje jeszcze jeden sposób uzyskania dostępu do atrybutów klasy – poprzez utworzenie właściwości. Pozwalają one na manipulowanie atrybutami prywatnymi tak, jakby były one publiczne. Rozważmy następującą klasę personne, w której poprzednie metody dostępu i modyfikacji zostały zastąpione właściwościami do odczytu i zapisu:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
' klasa osoba
Public Class personne
' atrybuty
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' producenci
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identyfikuje
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' właściwości
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' czy wiek jest prawidłowy?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Właściwość (Property) pozwala odczytać (get) lub ustawić (set) wartość atrybutu. W naszym przykładzie nazwom atrybutów dodaliśmy przedrostek _, aby nazwy właściwości odpowiadały nazwom atrybutów pierwotnych. Właściwość nie może bowiem nosić tej samej nazwy co atrybut, którym zarządza, ponieważ w takim przypadku dochodzi do konfliktu nazw w klasie. Dlatego nazwaliśmy nasze atrybuty _prenom, _nom, _age i odpowiednio zmodyfikowaliśmy konstruktory oraz metody. Następnie utworzyliśmy trzy właściwości: nom, prenom i age. Właściwość deklaruje się w następujący sposób:
Public Property nom() As Type
Get
...
End Get
Set(ByVal Value As Type)
...
End Set
End Property
gdzie Type musi być typem atrybutu zarządzanego przez właściwość. Może ona posiadać dwie metody o nazwach get i set. Metoda get zazwyczaj odpowiada za zwracanie wartości atrybutu, którym zarządza (może jednak zwracać coś innego – nic jej w tym nie przeszkadza). Metoda set przyjmuje parametr o nazwie value, który zazwyczaj przypisuje do zarządzanego przez siebie atrybutu. Może przy tej okazji sprawdzić poprawność otrzymanej wartości i ewentualnie zgłosić wyjątek, jeśli wartość okaże się nieprawidłowa. Tak właśnie postępuje się tutaj w przypadku wieku.
W jaki sposób wywoływane są metody get i set? Rozważmy następujący program testowy:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
' strona testowa
Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
P.age = 56
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
Try
P.age = -4
Catch ex As Exception
Console.Error.WriteLine(ex.Message)
End Try
End Sub
End Module
W instrukcji
próbujemy uzyskać wartości właściwości prenom, nom i age osoby P. Wywoływana jest wówczas metoda get tych właściwości, która zwraca wartość atrybutu, którym one zarządzają.
W instrukcji
chcemy ustawić wartość właściwości age. Wywoływana jest wówczas metoda set tej właściwości. Otrzyma ona wartość 56 w parametrze value.
Właściwość P klasy C, która definiuje wyłącznie metodę get, jest określana jako tylko do odczytu. Jeśli c jest obiektem klasy C, operacja c.P=wartość zostanie odrzucona przez kompilator.
Wykonanie powyższego programu testowego daje następujące wyniki:
Właściwości pozwalają nam zatem na manipulowanie atrybutami prywatnymi tak, jakby były one publiczne.
3.1.14. Metody i atrybuty klasy
Załóżmy, że chcemy policzyć liczbę obiektów [personne] utworzonych w aplikacji. Możemy samodzielnie zarządzać licznikiem, ale istnieje ryzyko, że zapomnimy o obiektach tymczasowych tworzonych tu i ówdzie. Bezpieczniejszym rozwiązaniem wydaje się umieszczenie w konstruktorach klasy [personne] instrukcji zwiększającej licznik. Problem polega na przekazaniu odwołania do tego licznika, aby konstruktor mógł go zwiększyć: należy przekazać im nowy parametr. Można również umieścić licznik w definicji klasy. Ponieważ jest to atrybut samej klasy, a nie konkretnego obiektu tej klasy, deklaruje się go inaczej, używając słowa kluczowego Shared:
Aby do niego odwołać się, piszemy personne._nbPersonnes, aby pokazać, że jest to atrybut samej klasy personne. W tym przypadku utworzyliśmy atrybut prywatny, do którego nie będziemy mieli bezpośredniego dostępu spoza klasy. Tworzymy zatem właściwość publiczną, aby zapewnić dostęp do atrybutu klasy nbPersonnes. Aby ustawić wartość nbPersonnes, metoda get tej właściwości nie wymaga konkretnego obiektu personne: w rzeczywistości _nbPersonnes nie jest atrybutem konkretnego obiektu, lecz atrybutem całej klasy. Dlatego potrzebna jest również zadeklarowana właściwość o nazwie Shared:
która z zewnątrz będzie wywoływana przy użyciu składni personne.nbPersonnes. Właściwość jest zadeklarowana jako tylko do odczytu (ReadOnly), ponieważ nie posiada metody set. Oto przykład. Klasa personne przyjmuje następujący wygląd:
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
' klasa
Public Class personne
' atrybuty klasy
Private Shared _nbPersonnes As Long = 0
' atrybuty instancji
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' konstruktory
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' jeszcze jedna osoba
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' jeszcze jedna osoba
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identyfikuje
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' właściwość klasy
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' właściwości instancji
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' czy wiek jest prawidłowy?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Z następującym programem:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
' strona testowa
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Dim p2 As New personne(p1)
Console.Out.WriteLine(("Nombre de personnes créées : " & personne.nbPersonnes))
End Sub
End Module
uzyskuje się następujące wyniki:
3.1.15. Przekazywanie obiektu do funkcji
Wspomnieliśmy już, że domyślnie funkcja VB.NET przekazuje parametry rzeczywiste do funkcji przez wartość: wartości parametrów rzeczywistych są kopiowane do parametrów formalnych. W przypadku obiektu nie należy dać się zwieść nadużyciu językowemu, które ma miejsce systematycznie, gdy mówi się o „obiekcie” zamiast o „odwołaniu do obiektu”. Obiektem można manipulować wyłącznie za pośrednictwem odwołania (wskaźnika) do niego. Do funkcji przekazywany jest zatem nie sam obiekt, lecz odwołanie do tego obiektu. W parametrze formalnym powielana jest więc wartość odwołania, a nie wartość samego obiektu: nie dochodzi do utworzenia nowego obiektu. Jeśli odwołanie do obiektu R1 zostanie przekazane do funkcji, zostanie ono skopiowane do odpowiedniego parametru formalnego R2. W związku z tym odniesienia R2 i R1 wskazują ten sam obiekt. Jeśli funkcja modyfikuje obiekt, na który wskazuje R2, to oczywiście modyfikuje również obiekt, do którego odnosi się R1, ponieważ jest to ten sam obiekt.
![]() |
Widać to na poniższym przykładzie:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
' strona testowa
Module test
Sub Main()
' osoba p1
Dim p1 As New personne("Jean", "Dupont", 30)
' wyświetlanie p1
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' modyfikacja p1
modifie(p1)
' wyświetlanie p1
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' wyświetlanie osoby P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' zmiana P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' wyświetlanie P
Console.Out.Write("Paramètre formel après modification : ")
P.identifie()
End Sub
End Module
Otrzymane wyniki są następujące:
Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52
Widać, że utworzono tylko jeden obiekt: osobę p1 z procedury Main oraz że obiekt ten został rzeczywiście zmodyfikowany przez funkcję modifie.
3.1.16. Tablica osób
Obiekt jest danymi takimi jak każde inne i w związku z tym wiele obiektów można zgromadzić w tablicy:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
' strona testowa
Module test
Sub Main()
' tabela osób
Dim amis(2) As personne
amis(0) = New personne("Jean", "Dupont", 30)
amis(1) = New personne("Sylvie", "Vartan", 52)
amis(2) = New personne("Neil", "Armstrong", 66)
' wyświetlanie
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To amis.Length - 1
amis(i).identifie()
Next i
End Sub
End Module
Instrukcja Dim amis(2) As personne tworzy tablicę składającą się z 3 elementów typu personne. Te 3 elementy są tutaj zainicjowane wartościami nothing, c.a.d, co oznacza, że nie odwołują się one do żadnego obiektu. Ponownie, w wyniku nieprecyzyjnego sformułowania, mówi się o tablicy obiektów, podczas gdy jest to jedynie tablica odwołań do obiektów. Utworzenie tablicy obiektów, która sama w sobie jest obiektem, nie powoduje utworzenia żadnego obiektu typu jej elementów: należy to zrobić później. Otrzymujemy następujące wyniki:
3.2. Dziedziczenie na przykładzie
3.2.1. Informacje ogólne
W tym miejscu omówimy pojęcie dziedziczenia. Celem dziedziczenia jest „dostosowanie” istniejącej klasy tak, aby spełniała nasze potrzeby. Załóżmy, że chcemy utworzyć klasę enseignant: nauczyciel to osoba szczególna. Posiada on atrybuty, których nie ma żadna inna osoba: na przykład przedmiot, którego naucza. Posiada jednak również atrybuty charakterystyczne dla każdej osoby: imię, nazwisko i wiek. Nauczyciel stanowi zatem pełnoprawną część klasy personne, ale posiada dodatkowe atrybuty. Zamiast tworzyć klasę enseignant od podstaw, lepiej byłoby wykorzystać istniejące elementy klasy personne i dostosować je do specyficznego charakteru nauczycieli. Umożliwia nam to koncepcja dziedziczenia. Aby wyrazić, że klasa enseignant dziedziczy właściwości klasy personne, napiszemy:
Public Class enseignant
Inherits personne
Należy zwrócić uwagę na specyficzną składnię rozłożoną na dwa wiersze. Klasa personne nazywana jest klasą nadrzędną (lub macierzystą), a klasa enseignant – klasą pochodną (lub potomną). Obiekt enseignant posiada wszystkie cechy obiektu personne: ma te same atrybuty i metody. Atrybuty i metody klasy nadrzędnej nie są powtarzane w definicji klasy podrzędnej: wystarczy wskazać atrybuty i metody dodane przez klasę podrzędną. Zakładamy, że klasa personne jest zdefiniowana w następujący sposób:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
' klasa osoba
Public Class personne
' atrybuty klasy
Private Shared _nbPersonnes As Long = 0
' atrybuty instancji
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' konstruktory
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' kolejna osoba
_nbPersonnes += 1
' konstrukcja
Me._prenom = P
Me._nom = N
Me._age = age
' monitorowanie
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' jeszcze jedna osoba
_nbPersonnes += 1
' budowa
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' monitorowanie
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' właściwość klasy
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' właściwości instancji
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' czy wiek jest prawidłowy?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
Public ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
End Class
Metoda identifie została zastąpiona przez właściwość identité, dostępną tylko do odczytu, która identyfikuje osobę. Tworzymy klasę enseignant dziedziczącą po klasie personne:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Public Class enseignant
Inherits personne
' atrybuty
Private _section As Integer
' konstruktor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' śledzenie
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' właściwość sekcji
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
End Class
Klasa enseignant rozszerza metody i atrybuty klasy personne o:
- atrybut section, który stanowi numer sekcji, do której należy nauczyciel w gronie pedagogicznym (ogólnie rzecz biorąc, jedna sekcja na jeden przedmiot)
- nowy konstruktor umożliwiający zainicjowanie wszystkich atrybutów nauczyciela
Deklaracja
Public Class enseignant
Inherits personne
wskazuje, że klasa enseignant wywodzi się z klasy personne.
3.2.2. Tworzenie obiektu nauczyciela
Konstruktor klasy enseignant wygląda następująco:
' konstruktor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' śledzenie
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
Oświadczenie
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
określa, że konstruktor otrzymuje cztery parametry: P, N, age, section. Musi przekazać trzy z nich – (P,N,age) – do swojej klasy bazowej, w tym przypadku klasy personne. Wiemy, że ta klasa posiada konstruktor *person(string, string, int)*, który pozwoli utworzyć obiekt typu Person na podstawie przekazanych parametrów (P,N,age). Klasa [enseignant] przekazuje parametry (P, N, wiek) do swojej klasy bazowej w następujący sposób:
MyBase.New(P, N, age)
Po zakończeniu tworzenia klasy bazowej tworzenie obiektu enseignant jest kontynuowane poprzez wykonanie treści konstruktora:
Me._section = section
Podsumowując, konstruktor klasy pochodnej:
- przekazuje klasie bazowej parametry niezbędne do jej utworzenia
- wykorzystuje pozostałe parametry do zainicjowania swoich własnych atrybutów
Można by było zapisać to w następujący sposób:
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
Me._section = section
' śledzenie
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
To niemożliwe. Klasa personne zadeklarowała jako prywatne (private) swoje trzy pola: _prenom, _nom i _age. Tylko obiekty tej samej klasy mają bezpośredni dostęp do tych pól. Wszystkie pozostałe obiekty, w tym obiekty potomne, jak w tym przypadku, muszą korzystać z metod publicznych, aby uzyskać do nich dostęp. Sytuacja wyglądałaby inaczej, gdyby klasa personne zadeklarowała te trzy pola jako chronione (protected): wówczas zezwalałaby klasom pochodnym na bezpośredni dostęp do tych trzech pól. W naszym przykładzie użycie konstruktora klasy nadrzędnej było zatem właściwym rozwiązaniem i jest to standardowa procedura: podczas tworzenia obiektu potomnego najpierw wywołuje się konstruktor obiektu nadrzędnego, a następnie uzupełnia się inicjalizacje właściwe dla obiektu potomnego (w naszym przykładzie section).
Skompilujmy klasy personne i enseignant w zestawach:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>vbc /r:personne.dll /t:library enseignant.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
25/02/2004 10:08 1 828 personne.vb
25/02/2004 10:11 675 enseignant.vb
25/02/2004 10:12 223 test.vb
25/02/2004 10:16 4 096 personne.dll
25/02/2004 10:16 3 584 enseignant.dll
Warto zauważyć, że aby skompilować klasę potomną enseignant, konieczne było odwołanie się do pliku personne.dll, który zawiera klasę personne. Spróbujmy napisać pierwszy program testowy:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Program ten ogranicza się do utworzenia obiektu enseignant (new) i zidentyfikowania go. Klasa enseignant nie posiada metody identité, ale jej klasa nadrzędna posiada taką metodę, która ponadto jest publiczna: staje się ona poprzez dziedziczenie publiczną metodą klasy enseignant. Uzyskane wyniki są następujące:
dos>vbc /r:personne.dll /r:enseignant.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>test
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Jean,Dupont,30)
Widać, że:
- obiekt personne został utworzony przed obiektem enseignant
- uzyskana tożsamość należy do obiektu personne
3.2.3. Przeciążenie metody lub właściwości
W poprzednim przykładzie uzyskaliśmy tożsamość części personne nauczyciela, ale brakuje pewnych informacji właściwych dla klasy enseignant (sekcja). Musimy zatem zdefiniować właściwość umożliwiającą identyfikację nauczyciela:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Public Class enseignant
Inherits personne
' atrybuty
Private _section As Integer
' konstruktor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' śledzenie
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' właściwość sekcji
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' przeładowanie właściwości tożsamości
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
Metoda identite klasy enseignant opiera się na metodzie identite swojej klasy nadrzędnej (MyBase.identite) w celu wyświetlenia części „personne”, a następnie uzupełnia ją o pole _section, które jest specyficzne dla klasy enseignant. Zwróćmy uwagę na deklarację właściwości identite:
Public Shadows ReadOnly Property identite() As String
co oznacza, że właściwość identite „maskuje” metodę o tej samej nazwie, która mogłaby istnieć w klasie nadrzędnej. Niech E będzie obiektem enseignant. Obiekt ten zawiera w sobie obiekt personne:
![]() |
Właściwość „identity” jest zdefiniowana zarówno w klasie enseignant, jak i w jej klasie nadrzędnej personne. W klasie potomnej enseignant właściwość identite musi być poprzedzona słowem kluczowym „shadows”, aby wskazać, że redefiniuje się nową właściwość identite dla klasy enseignant.
Public Shadows ReadOnly Property identite() As String
Klasa enseignant posiada teraz dwie właściwości identite:
- jedną odziedziczoną po klasie nadrzędnej personne
- własną
Jeśli E jest obiektem klasy enseignant, to E.identite oznacza metodę identite klasy enseignant. Mówi się, że właściwość identite klasy nadrzędnej jest „prze definiowana” przez właściwość identite klasy podrzędnej. Ogólnie rzecz biorąc, jeśli O jest obiektem, a M metodą, to w celu wykonania metody O.M system szuka metody M w następującej kolejności:
- w klasie obiektu O
- w jej klasie nadrzędnej, jeśli taka istnieje
- w klasie nadrzędnej klasy nadrzędnej, jeśli taka istnieje
- itd…
Dziedziczenie pozwala zatem na ponowne zdefiniowanie w klasie potomnej metod/właściwości o tej samej nazwie, co w klasie nadrzędnej. To właśnie umożliwia dostosowanie klasy potomnej do jej własnych potrzeb. W połączeniu z polimorfizmem, który omówimy nieco później, przeciążanie metod/właściwości stanowi główną zaletę dziedziczenia. Rozważmy ten sam przykład, co poprzednio:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Tym razem uzyskano następujące wyniki:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)
3.2.4. Polimorfizm
Rozważmy następującą sekwencję klas: C0 C1 C2 … Gdzie Ci Cj oznacza, że klasa Cj wywodzi się z klasy Ci. Oznacza to, że klasa Cj posiada wszystkie cechy klasy Ci oraz dodatkowe cechy. Niech Oi będą obiektami typu Ci. Dopuszczalne jest zapisanie:
W rzeczywistości, dzięki dziedziczeniu, klasa Cj posiada wszystkie cechy klasy Ci oraz dodatkowe. Zatem obiekt Oj typu Cj zawiera w sobie obiekt typu Ci. Operacja
sprawia, że Oi jest odwołaniem do obiektu typu Ci zawartego w obiekcie Oj.
Fakt, że zmienna Oi klasy Ci może w rzeczywistości odwoływać się nie tylko do obiektu klasy Ci, ale do dowolnego obiektu pochodnego od klasy Ci, nazywany jest polimorfizmem: zdolność zmiennej do odwoływania się do różnych typów obiektów. Weźmy przykład i rozważmy następującą funkcję niezależną od jakiejkolwiek klasy:
Sub affiche(ByVal p As personne)
Równie dobrze można by zapisać
jak i
W tym ostatnim przypadku parametr formalny typu personne funkcji affiche otrzyma wartość typu enseignant. Ponieważ typ enseignant wywodzi się z typu personne, jest to dopuszczalne.
3.2.5. Przedefiniowanie i polimorfizm
Uzupełnijmy naszą procedurę affiche:
Sub affiche(ByVal p As personne)
' wyświetla tożsamość p
Console.Out.WriteLine(p.identite)
End Sub
Metoda p.identite zwraca ciąg znaków identyfikujący obiekt personne.. Co dzieje się w przypadku naszego poprzedniego przykładu, gdy mamy do czynienia z obiektem enseignant:
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
Przyjrzyjmy się następującemu przykładowi:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Module test
Sub Main()
' nauczyciel
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' osoba
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' wyświetla
Sub affiche(ByVal p As personne)
' określa tożsamość p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Otrzymane wyniki są następujące:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
Analiza wykonania pokazuje, że instrukcja p.identite za każdym razem wywoływała właściwość identite obiektu personne, osobę zawartą w enseignant e, a następnie samą personne p. Nie dostosowała się ona do obiektu faktycznie przekazanego jako parametr do affiche. Wolelibyśmy uzyskać pełną tożsamość obiektu enseignant e. W tym celu notacja p.identite musiałaby odnosić się do właściwości identite obiektu, na który faktycznie wskazuje p, a nie do właściwości identite części „personne” obiektu, na który faktycznie wskazuje p. Wynik ten można uzyskać, deklarując identite jako właściwość nadpisywalną (overridable) w klasie bazowej personne:
Public Overridable ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
Słowo kluczowe „overridable” sprawia, że identite staje się właściwością nadpisywalną lub wirtualną. Słowo to można również zastosować do metod. Klasy potomne, które nadpisują właściwość lub metodę wirtualną, muszą wówczas używać słowa kluczowego overrides zamiast **shadows** do oznaczenia nadpisanej właściwości/metody. Tak więc w klasie enseignant właściwość identite jest zdefiniowana w następujący sposób:
' nadpisanie właściwości tożsamości
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
Program testowy:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Module test
Sub Main()
' nauczyciel
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' osoba
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' wyświetla
Sub affiche(ByVal p As personne)
' wyświetla tożsamość p
Console.Out.WriteLine(p.identite)
End Sub
End Module
generuje wówczas następujące wyniki:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
Tym razem udało nam się uzyskać pełną tożsamość nauczyciela. Zdefiniujmy teraz metodę zamiast właściwości. Klasa object jest klasą „nadrzędną” wszystkich klas VB.NET. Tak więc, gdy piszemy:
to domyślnie zapisujemy:
Klasa object definiuje metodę wirtualną ToString:

Metoda ToString zwraca nazwę klasy, do której należy obiekt, jak pokazano w poniższym przykładzie:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Module test2
Sub Main()
' nauczyciel
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' osoba
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
Wyniki są następujące:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne
Warto zauważyć, że chociaż nie zdefiniowaliśmy na nowo metody ToString w klasach personne i enseignant, można jednak zauważyć, że metoda ToString klasy object jest mimo to w stanie wyświetlić rzeczywistą nazwę klasy obiektu. Zdefiniujmy ponownie metodę ToString w klasach personne i enseignant:
' ToString
Public Overrides Function ToString() As String
' nadajemy właściwość tożsamości
Return identite
End Function
Definicja jest taka sama w obu klasach. Rozważmy następujący program testowy:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Module test
Sub Main()
' nauczyciel
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' osoba
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' plakat
Sub affiche(ByVal p As personne)
' wyświetla tożsamość p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Wyniki wykonania są następujące:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
3.3. Zdefiniowanie indeksatora dla klasy
Rozważmy klasę [ArrayList] zdefiniowaną w platformie .NET. Klasa ta umożliwia przechowywanie obiektów na liście. Należy ona do przestrzeni nazw [System.Collections]. W poniższym przykładzie wykorzystujemy tę klasę do przechowywania listy osób (w szerokim znaczeniu):
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Imports System.Collections
' klasa listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' dodanie osoby do listy
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' indeksator
Default Public Shadows Property Item(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' kolejny indeksator
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' szukamy osoby o imieniu N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
' toString
Public Overrides Function ToString() As String
' zwraca (element1, element2, ..., elementn)
Dim liste As String = "("
Dim i As Integer
' przeglądamy tablicę dynamiczną
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' ostatni element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Przedstawmy kilka informacji na temat niektórych właściwości i metod klasy [ArrayList]:
atrybut określający liczbę elementów listy | |
metoda umożliwiająca dodanie obiektu do listy | |
metoda zwracająca element i z listy |
Zauważamy, że aby uzyskać element nr i z listy, nie wpisaliśmy [liste.Item(i)], ale bezpośrednio [liste(i)], co na pierwszy rzut oka wydaje się błędne. Jest to jednak możliwe, ponieważ klasa [ArrayList] definiuje właściwość domyślną [Item] zgodnie ze składnią podobną do poniższej:
Default Public Property Item(ByVal i As Integer) As Object
Get
...
End Get
Set(ByVal Value As personne)
...
End Set
End Property
Gdy kompilator napotka notację [liste(i)], sprawdza, czy klasa [ArrayList] zdefiniowała właściwość o następującej sygnaturze:
Default Public Property Proc(ByVal var As Integer) As Type
W tym przypadku znajdzie procedurę [Item]. Następnie przekształci zapis [liste(i)] na [liste.Item(i)]. Właściwość o nazwie [Item] nazywamy domyślną właściwością indeksowaną klasy [ArrayList]. Wykonanie powyższego programu daje następujące wyniki:
dos>vbc /r:personne.dll /r:enseignant.dll lstpersonnes1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:34 3 584 lstpersonnes1.exe
12/03/2004 13:39 661 lstpersonnes1.vb
11/03/2004 18:26 4 096 personne.dll
dos>lstpersonnes1
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(paul,chenou,31)
personne(nicole,chenou,11)
enseignant(personne(jacques,sileau,33),61)
Utwórzmy klasę o nazwie [listeDePersonnes], która będzie listą osób, a więc listą specjalną, którą naturalnie można wyprowadzić z klasy [ArrayList]:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Imports System.Collections
' klasa listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' aby dodać osobę do listy
Public Shadows Sub Add(ByVal p As Object)
' p musi być osobą
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
' indeksator
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' toString
Public Overrides Function ToString() As String
' zwraca (element1, element2, ..., elementn)
Dim liste As String = "("
Dim i As Integer
' przeglądamy tablicę dynamiczną
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' ostatni element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Klasa posiada następujące metody i właściwości:
zwraca ciąg znaków „reprezentujący” zawartość listy | |
metoda umożliwiająca dodanie osoby do listy | |
domyślna właściwość indeksowana, zwracająca osobę o numerze i z listy |
Rozważmy nowe elementy:
Utworzono nową metodę [Add].
' aby dodać osobę do listy
Public Shadows Sub Add(ByVal p As Object)
' p musi być osobą
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
W klasie nadrzędnej [ArrayList] istnieje już procedura o tej samej sygnaturze, stąd użycie słowa kluczowego [Shadows], które wskazuje, że nowa procedura zastępuje tę z klasy nadrzędnej. Metoda [Add] klasy potomnej sprawdza, czy dodany obiekt jest rzeczywiście typu [personne] lub pochodzi od niego za pomocą funkcji [TypeOf]. Jeśli tak nie jest, generowany jest wyjątek za pomocą instrukcji [Throw]. Jeśli dodawany obiekt jest rzeczywiście typu [personne], do dodania obiektu wykorzystywana jest metoda [Add] klasy bazowej.
Tworzona jest właściwość indeksowana:
' indeksator
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
W klasie bazowej istnieje już właściwość domyślna o nazwie [Item] z tą samą sygnaturą. W związku z tym konieczne jest użycie słowa kluczowego [Shadows], aby wskazać, że nowa właściwość indeksowana [Index] „ukryje” właściwość klasy bazowej [Item]. Należy zauważyć, że ma to zastosowanie nawet wtedy, gdy obie właściwości nie mają tej samej nazwy. Właściwość [Index] pozwala odwołać się do osoby nr i z listy. Opiera się ona na właściwości [Item] klasy bazowej, aby uzyskać dostęp do elementu nr i obiektu bazowego [ArrayList]. Wprowadzono zmiany typu, aby uwzględnić fakt, że właściwość [Item] obsługuje elementy typu [Object], podczas gdy właściwość [Index] obsługuje elementy typu [personne].
Na koniec nadpisujemy (Overrides) metodę [ToString] klasy [ArrayList]:
' toString
Public Overrides Function ToString() As String
' zwraca (element1, element2, ..., elementn)
Dim liste As String = "("
Dim i As Integer
' przeglądamy tablicę dynamiczną
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' ostatni element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
Metoda ta zwraca ciąg znaków w postaci „(e1, e2, ..., en)”, gdzie e1, e2, ..., en to elementy listy. Należy zwrócić uwagę na zapis [Me(i)], który oznacza element o numerze i bieżącego obiektu [Me]. Wykorzystywana jest domyślna właściwość indeksowana. Zatem [Me(i)] jest równoważne [Me.Index(i)].
Kod klasy umieszczono w pliku [lstpersonnes2.vb] i skompilowano:
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
Tworzony jest program testowy:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
Imports System.Collections
Module test
Sub Main()
' utworzenie pustej listy osób
Dim liste As listeDePersonnes = New listeDePersonnes
' tworzenie osób
Dim p1 As personne = New personne("paul", "chenou", 31)
Dim p2 As personne = New personne("nicole", "chenou", 11)
Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
' wypełnianie listy
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' wyświetlanie listy
Console.Out.WriteLine(liste.ToString)
' dodanie obiektu innego niż osoba
Try
liste.Add(4)
Catch e As Exception
Console.Error.WriteLine(e.Message)
End Try
End Sub
End Module
Zostaje skompilowany:
dos>vbc /r:personne.dll /r:enseignant.dll /r:lstpersonnes2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
12/03/2004 15:50 3 584 test.exe
12/03/2004 15:49 623 test.vb
a następnie uruchomiono:
dos>test
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
([personne(paul,chenou,31)],[personne(nicole,chenou,11)],[enseignant(personne(jacques,sileau,33),61)])
L'objet ajouté (4) n'est pas une personne
Można by zapisać to w następujący sposób:
gdzie l miałoby postać [listeDePersonnes]. W tym przypadku chcemy indeksować listę l nie według numeru elementu, ale według nazwiska osoby. W tym celu definiujemy nową właściwość indeksowaną domyślnie:
' inny indeksator
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' wyszukiwanie osoby o imieniu N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
Pierwszy wiersz
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer
wskazuje, że ponownie tworzymy domyślną właściwość indeksowaną. Wszystkie domyślne właściwości muszą nosić tę samą nazwę, w tym przypadku [Index]. Nowa właściwość [Index] indeksuje klasę listeDePersonnes za pomocą ciągu znaków N. Wynikiem wyrażenia listeDePersonnes(N) jest liczba całkowita. Liczba ta będzie pozycją na liście osoby o nazwie N lub -1, jeśli osoba ta nie znajduje się na liście. Definiujemy jedynie właściwość get, uniemożliwiając w ten sposób zapis listeDePersonnes ("nom")=valeur, który wymagałby zdefiniowania właściwości set). Stąd słowo kluczowe [ReadOnly]. Słowo kluczowe [Shadows] jest niezbędne do ukrycia domyślnej właściwości klasy bazowej (chociaż nie ma ona tej samej sygnatury).
W treści get przeglądamy listę osób w poszukiwaniu nazwy N przekazanej jako parametr. Jeśli znajdziemy ją na pozycji i, zwracamy i, w przeciwnym razie zwracamy -1.
Nowy program testowy mógłby wyglądać następująco:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
' strona testowa
Module test
Sub Main()
' lista osób
Dim l As New listeDePersonnes
' dodawanie osób
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' wyświetlanie
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' zmiana elementu 1
l(1) = New personne("sylvie", "cachan", 5)
' wyświetlanie elementu 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' wyświetlanie listy l
Console.Out.WriteLine(("l=" + l.ToString))
' wyszukiwanie osób
Dim noms() As String = New [String]() {"cachan", "inconnu"}
Dim i As Integer
For i = 0 To noms.Length - 1
Dim inom As Integer = l(noms(i))
If inom <> -1 Then
Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString))
Else
Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas"))
End If
Next i
End Sub
End Module
Wykonanie programu daje następujące wyniki:
personne(string, string, int)
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)])
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)],[personne(jacques,tartifume,27)])
Construction personne(string, string, int)
l[1]=personne(sylvie,cachan,5)
l=([personne(jean,dumornet,10)],[personne(sylvie,cachan,5)],[personne(jacques,tartifume,27)])
personne(cachan)=personne(sylvie,cachan,5)
personne(inconnu) n'existe pas
3.4. Struktury
Struktura VB.NET wywodzi się bezpośrednio ze struktury języka C i jest bardzo zbliżona do klasy. Struktura jest zdefiniowana w następujący sposób:
Structure spersonne
' atrybuty
...
' właściwości
...
' konstruktorzy
...
' metody
End Structure
Pomimo podobieństwa w deklaracji istnieją istotne różnice między klasą a strukturą. Na przykład w strukturach nie ma pojęcia dziedziczenia. Jeśli tworzymy klasę, która nie ma być klasą pochodną, jakie różnice między strukturą a klasą pomogą nam dokonać wyboru między nimi? Posłużmy się poniższym przykładem, aby to sprawdzić:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
' struktura spersonne
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' klasa cpersonne
Class cpersonne
Public nom As String
Public age As Integer
End Class
' moduł testowy
Public Module test
Sub Main()
' osoba p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' osoba p2
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 zostaje zmodyfikowany
sp2.nom = "nicole"
sp2.age = 30
' weryfikacja sp1 i sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' osoba cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' osoba c P2
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 został zmodyfikowany
cp2.nom = "nicole"
cp2.age = 30
' weryfikacja P1 i P2
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
End Sub
End Module
Po uruchomieniu tego programu otrzymujemy następujące wyniki:
sp1=spersonne(paul,10)
sp2=spersonne(paul,10)
sp1=cpersonne(paul,10)
sp2=cpersonne(nicole,30)
cP1=cpersonne(paul,10)
cP2=cpersonne(paul,10)
cP1=cpersonne(nicole,30)
cP2=cpersonne(nicole,30)
Tam, gdzie na poprzednich stronach tego rozdziału używano klasy personne, teraz stosujemy strukturę spersonne:
' struktura osoby
Structure spersonne
Public nom As String
Public age As Integer
End Structure
Oświadczenie
Dim sp1 As spersonne
tworzy strukturę (nazwa, wiek), a wartością sp1 jest sama ta struktura.
Deklaracja
Dim cp1 As New cpersonne
tworzy obiekt [cpersonne] (w przybliżeniu odpowiadający naszej strukturze), a cp1 jest wówczas adresem (odwołaniem) tego obiektu.
Podsumowując
- w przypadku struktury wartość sp1 to sama struktura
- w przypadku klasy wartość p1 stanowi adres utworzonego obiektu
![]() |
![]() |
Gdy w programie zapisuje się
Dim sp2 As spersonne = sp1
tworzona jest nowa struktura (nazwa, wiek) i inicjowana wartością p1, czyli samą strukturą.
![]() |
Struktura z sp1 jest zatem powielana w sp2. Jest to kopiowanie wartości.
Instrukcja
Dim cp2 As cpersonne = cp1
działa inaczej. Wartość cp1 jest kopiowana do cp2, ale ponieważ ta wartość jest w rzeczywistości adresem obiektu, obiekt ten nie jest powielany. Ma po prostu dwa odwołania do siebie:
![]() |
W przypadku struktury zmiana wartości sp2 nie powoduje zmiany wartości sp1, co pokazuje program. W przypadku obiektu, jeśli zmodyfikujemy obiekt, na który wskazuje cp2, to obiekt, na który wskazuje cp1, również ulegnie zmianie, ponieważ jest to ten sam obiekt. Pokazują to również wyniki programu.
Z powyższych wyjaśnień wynika zatem, że:
- wartością zmiennej typu struktura jest sama struktura
- wartością zmiennej typu obiekt jest adres obiektu, na który wskazuje
Po zrozumieniu tej fundamentalnej różnicy struktura okazuje się bardzo zbliżona do klasy, co ilustruje poniższy przykład:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
' struktura osoby
Structure personne
' atrybuty
Private _nom As String
Private _age As Integer
' właściwości
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
_age = value
End Set
End Property
' Konstruktor
Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
_nom = NOM
_age = AGE
End Sub 'New
' TOSTRING
Public Overrides Function ToString() As String
Return "personne(" & nom & "," & age & ")"
End Function
End Structure
' moduł testowy
Module test
Sub Main()
' osoba p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' osoba p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 zostaje zmodyfikowany
p2.nom = "nicole"
p2.age = 30
' weryfikacja p1 i p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
Otrzymujemy następujące wyniki wykonania:
Jedyną zauważalną różnicą między strukturą a klasą jest to, że w przypadku klasy obiekty p1 i p2 miałyby na końcu programu tę samą wartość, czyli wartość p2.
3.5. Interfejsy
Interfejs to zbiór prototypów metod lub właściwości, które tworzą umowę. Klasa, która decyduje się na implementację interfejsu, zobowiązuje się do zapewnienia implementacji wszystkich metod zdefiniowanych w interfejsie. To kompilator weryfikuje tę implementację. Oto na przykład definicja interfejsu Istats:
Każda klasa implementująca ten interfejs zostanie zadeklarowana jako
public class C
Implements Istats
...
function moyenne() as Double Implements Istats.moyenne
...
end function
function écartType () as Double Implements Istats. écartType
...
end function
end class
Metody [moyenne] i [écartType] należy zdefiniować w klasie C. Rozważmy następujący kod:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
' struktura
Public Structure élève
Public _nom As String
Public _note As Double
' konstruktor
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' klasa notatek
Public Class notes
' atrybut
Protected _matière As String
Protected _élèves() As élève
' producent
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' rejestrowanie uczniów i przedmiotów
Me._matière = MATIERE
Me._élèves = ELEVES
End Sub
' ToString
Public Overrides Function ToString() As String
Dim valeur As String = "matière=" + _matière + ", notes=("
Dim i As Integer
' łączymy wszystkie oceny
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'ostatnia ocena
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' koniec
Return valeur
End Function
End Class
Klasa notes gromadzi oceny uczniów z danej klasy z danego przedmiotu:
Public Class notes
' atrybut
Protected _matière As String
Protected _élèves() As élève
Atrybuty są zadeklarowane jako protected, aby były dostępne z klasy pochodnej. Typ élève to struktura przechowująca imię i nazwisko ucznia oraz jego ocenę z przedmiotu:
Public Structure élève
Public _nom As String
Public _note As Double
' konstruktor
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
Postanawiamy wyprowadzić tę klasę notes z klasy notesStats, która posiadałaby dwa dodatkowe atrybuty: średnią i odchylenie standardowe ocen:
Public Class notesStats
Inherits notes
Implements Istats
' atrybuty
Private _moyenne As Double
Private _écartType As Double
Klasa notesStats implementuje następujący interfejs Istats:
Oznacza to, że klasa notesStats musi posiadać dwie metody o nazwach moyenne i écartType o sygnaturze określonej w interfejsie Istats. Klasa notesStats wygląda następująco:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' atrybuty
Private _moyenne As Double
Private _écartType As Double
' konstruktor
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' obliczanie średniej ocen
Dim somme As Double = 0
Dim i As Integer
For i = 0 To ELEVES.Length - 1
somme += ELEVES(i)._note
Next i
If ELEVES.Length <> 0 Then
_moyenne = somme / ELEVES.Length
Else
_moyenne = -1
End If
' odchylenie standardowe
Dim carrés As Double = 0
For i = 0 To ELEVES.Length - 1
carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
Next i
If ELEVES.Length <> 0 Then
_écartType = Math.Sqrt((carrés / ELEVES.Length))
Else
_écartType = -1
End If
End Sub
' ToString
Public Overrides Function ToString() As String
Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
End Function 'ToString
' metody interfejsu Istats
Public Function moyenne() As Double Implements Istats.moyenne
' zwraca średnią ocen
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' zwraca odchylenie standardowe
Return _écartType
End Function
End Class
Średnia _moyenne i odchylenie standardowe _ecartType są obliczane już podczas tworzenia obiektu. Dlatego metody moyenne i écartType muszą jedynie zwracać wartości atrybutów _moyenne i _ecartType. Obie metody zwracają -1, jeśli tablica uczniów jest pusta.
Następująca klasa testowa:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Module test
Sub Main()
' niektórzy uczniowie i oceny
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' które zapisujemy w obiekcie „oceny”
Dim anglais As New notes("anglais", ELEVES)
' i wyświetla
Console.Out.WriteLine((anglais.ToString))
' to samo z średnią i odchyleniem standardowym
anglais = New notesStats("anglais", ELEVES)
Console.Out.WriteLine((anglais.ToString))
End Sub
End Module
daje następujące wyniki:
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18])
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,écart-type=1,63299316185545
Klasa notesStats mogłaby równie dobrze zaimplementować metody moyenne i écartType dla siebie samej, nie wskazując, że implementuje interfejs Istats. Jaki jest sens stosowania interfejsów? Jest on następujący: funkcja może przyjmować jako parametr formalny wartość o typie interfejsu I. Każdy obiekt klasy C implementującej interfejs I może wówczas być parametrem rzeczywistym tej funkcji. Rozważmy następujący przykład:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
' interfejs Iexemple
Public Interface Iexemple
Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
' pierwsza klasa
Public Class classe1
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 10
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 20
End Function
End Class
'druga klasa
Public Class classe2
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 100
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 200
End Function
End Class
Interfejs Iexemple definiuje dwie metody: ajouter i soustraire. Klasy classe1 i classe2 implementują ten interfejs. Należy zauważyć, że klasy te nie wykonują żadnych innych czynności, co wynika z dążenia do uproszczenia przykładu. Rozważmy teraz następujący przykład:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
' klasa testowa
Module test
'oblicz
Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
Console.Out.WriteLine(inter.ajouter(i, j))
Console.Out.WriteLine(inter.soustraire(i, j))
End Sub
' funkcja Main
Sub Main()
' utworzenie dwóch obiektów klasy1 i klasy2
Dim c1 As New classe1
Dim c2 As New classe2
' wywołania statycznej funkcji „obliczyć”
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
Funkcja calculer przyjmuje jako parametr element typu Iexemple. Może zatem otrzymać dla tego parametru zarówno obiekt typu classe1, jak i typu classe2. Tak właśnie postępuje się w procedurze Main, uzyskując następujące wyniki:
Widać zatem, że mamy tu do czynienia z właściwością zbliżoną do polimorfizmu rozpatrywanego w przypadku klas. Jeśli zbiór klas Ci, które nie są ze sobą powiązane poprzez dziedziczenie (a zatem nie można wykorzystać polimorfizmu dziedziczenia), posiada zbiór metod o tej samej sygnaturze, interesujące może być zgrupowanie tych metod w interfejsie I, z którego dziedziczyłyby wszystkie odnośne klasy. Instancje tych klas Ci mogą być wówczas wykorzystywane jako parametry funkcji przyjmujących parametr typu I, c.a.d. Są to funkcje wykorzystujące wyłącznie metody obiektów Ci zdefiniowane w interfejsie I, a nie konkretne atrybuty i metody poszczególnych klas Ci. Na koniec należy zauważyć, że dziedziczenie interfejsów może być wielokrotne, c.a.d. Można zapisać
Public Class classe
Implements I1,I2,...
gdzie Ij to interfejsy.
3.6. Przestrzenie nazw
Aby wyświetlić wiersz na ekranie, używamy instrukcji
Jeśli przyjrzymy się definicji klasy Console
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
okazuje się, że należy ona do przestrzeni nazw System. Oznacza to, że klasa Console powinna być oznaczona jako System.Console, a w rzeczywistości należałoby napisać:
Można tego uniknąć, stosując klauzulę imports:
Mówi się, że importuje się przestrzeń nazw System za pomocą klauzuli imports. Gdy kompilator napotka nazwę klasy (w tym przypadku Console), będzie jej szukał w różnych przestrzeniach nazw zaimportowanych za pomocą klauzul imports. W tym przypadku znajdzie klasę Console w przestrzeni nazw System. Zwróćmy teraz uwagę na drugą informację powiązaną z klasą Console:
Assembly: Mscorlib (in Mscorlib.dll)
Ten wiersz wskazuje, w którym „zestawie” znajduje się definicja klasy Console. Informacja ta może okazać się przydatna, gdy kompilujemy poza Visual Studio.NET i musimy podać odniesienia do różnych plików dll zawierających klasy, z których zamierzamy skorzystać. Przypominamy, że aby odwołać się do plików dll niezbędnych do kompilacji klasy, należy wpisać:
Tworząc klasę, można umieścić ją w przestrzeni nazw. Celem tych przestrzeni nazw jest uniknięcie konfliktów nazw między klasami, na przykład w przypadku ich sprzedaży. Rozważmy dwie firmy: E1 i E2, które dystrybuują klasy spakowane odpowiednio w plikach dll, E1.dll oraz E2.dll. Załóżmy, że klient C kupuje te dwa zestawy klas, w których obie firmy zdefiniowały klasę o nazwie personne. Klient C kompiluje program w następujący sposób:
Jeśli kod źródłowy prog.vb wykorzystuje klasę personne, kompilator nie będzie wiedział, czy ma pobrać klasę personne z klasy E1.dll, czy też z klasy E2.dll. Zgłosi błąd. Jeśli firma E1 zadba o utworzenie swoich klas w przestrzeni nazw o nazwie E1, a firma E2 w przestrzeni nazw o nazwie E2, obie klasy o nazwie personne będą się wówczas nazywać E1.personne i E2.personne. Klient będzie musiał używać w swoich klasach albo E1.personne, albo E2.personne, ale nie personne. Przestrzeń nazw pozwala wyeliminować tę niejednoznaczność. Aby utworzyć klasę w przestrzeni nazw, należy napisać:
Namespace istia.st
Public Class personne
' definicja klasy
...
end Class
end Namespace
Dla przykładu utwórzmy w przestrzeni nazw naszą klasę personne, którą analizowaliśmy wcześniej. Jako przestrzeń nazw wybierzemy istia.st. Klasa personne przyjmuje następującą postać:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
' utworzenie przestrzeni nazw istia.st
Namespace istia.st
Public Class personne
' atrybuty
Private prenom As String
Private nom As String
Private age As Integer
' metoda
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' metoda
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
Klasa ta jest kompilowana do pliku personne.dll:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
Teraz wykorzystajmy klasę personne w klasie testowej:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Imports istia.st
' test pg
Public Module test
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
p1.identifie()
End Sub
End Module
Aby uniknąć wpisywania
Dim p1 As New istia.st.personne
zaimportowaliśmy przestrzeń nazw istia.st za pomocą klauzuli imports:
Imports istia.st
Teraz skompilujmy program testowy:
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:05 254 test.vb
dos>vbc /r:personne.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:08 3 072 test.exe
12/03/2004 18:05 254 test.vb
W wyniku tego powstaje plik test.exe, którego uruchomienie daje następujące wyniki:
3.7. Przykład IMPOTS
Powracamy do obliczeń podatku omówionych już w poprzednim rozdziale i przetwarzamy je przy użyciu klasy. Przypomnijmy problem:
Rozważamy uproszczony przypadek podatnika, który ma do zadeklarowania wyłącznie swoje wynagrodzenie:
- obliczamy liczbę udziałów pracownika nbParts = nbEnfants/2 + 1, jeśli jest on osobą stanu wolnego, nbEnfants/2 + 2, jeśli jest żonaty, gdzie nbEnfants to liczba jego dzieci.
- jeśli ma co najmniej troje dzieci, otrzymuje dodatkową połowę udziału
- oblicza się jego dochód podlegający opodatkowaniu R = 0,72 * S, gdzie S to jego roczne wynagrodzenie
- oblicza się jego współczynnik rodzinny QF = R / nbParts
- oblicza się jego podatek I. Rozważmy następującą tabelę:
12620,0 | 0 | 0 |
13190 | 0,05 | 631 |
15640 | 0,1 | 1290,5 |
24 740 | 0,15 | 2072,5 |
31810 | 0,2 | 3309,5 |
39 970 | 0,25 | 4900 |
48360 | 0,3 | 6898,5 |
55790 | 0,35 | 9316,5 |
92970 | 0,4 | 12106 |
127860 | 0,45 | 16754,5 |
151250 | 0,50 | 23147,5 |
172 040 | 0,55 | 30710 |
195 000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Każdy wiersz zawiera 3 pola. Aby obliczyć podatek I, należy znaleźć pierwszy wiersz, w którym QF <= pole1. Na przykład, jeśli QF = 23000, znajdziemy wiersz
Podatek I wynosi wówczas 0,15*R – 2072,5*nbParts. Jeśli QF jest takie, że relacja QF <= pole1 nigdy nie jest spełniona, wówczas stosuje się współczynniki z ostatniego wiersza. W tym przypadku:
co daje podatek I = 0,65*R – 49062*nbParts.
Klasa „impot” zostanie zdefiniowana w następujący sposób:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Public Class impot
' dane niezbędne do obliczenia podatku
' pochodzą ze źródła zewnętrznego
Private limites(), coeffR(), coeffN() As Decimal
' producent
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' sprawdza się, czy wszystkie trzy tabele mają ten sam rozmiar
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' w porządku
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' obliczenie podatku
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' obliczenie liczby udziałów
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' obliczenie dochodu podlegającego opodatkowaniu i współczynnika rodzinnego
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' obliczenie podatku
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
Tworzony jest obiekt podatku zawierający dane umożliwiające obliczenie podatku podatnika. Jest to stała część obiektu. Po utworzeniu tego obiektu można wielokrotnie wywoływać jego metodę calculer, która oblicza podatek podatnika na podstawie jego stanu cywilnego (żonaty lub nie), liczby dzieci oraz rocznego wynagrodzenia. Program testowy mógłby wyglądać następująco:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' interaktywny program do obliczania podatku
' użytkownik wprowadza trzy dane za pomocą klawiatury: żonaty nbEnfants wynagrodzenie
' następnie program wyświetla kwotę podatku do zapłaty
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' tabele danych niezbędnych do obliczenia podatku
Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
' utworzenie obiektu podatku
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(limites, coeffR, coeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' pętla nieskończona
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' wymagane są parametry obliczenia podatku
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' co należy zrobić?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' sprawdzanie liczby argumentów w wprowadzonej linii
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' sprawdzanie poprawności parametrów
If Not erreur Then
' żonaty
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' wynagrodzenie
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' jeśli parametry są poprawne – obliczamy podatek
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
Oto przykładowe wykonanie powyższego programu:
dos>vbc /t:library impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dir>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:21 2 805 test.vb
dos>vbc /r:impots.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:26 6 144 test.exe
12/03/2004 18:21 2 805 test.vb
dos>test
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :x x x
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :





