Programowanie obiektowe w VB cz 2 Interfejsy Interfejsy są listą metod, właściwości, zdarzeń i indeksowników. Jeśli jakaś klasa implementuje jakiś interfejs, znaczy to, że użytkownik tej klasy może skorzystać ze wszystkich możliwości, jakie zdefiniowane zostały w interfejsie. Używanie interfejsów jest alternatywą do używania mechnizmu dziedziczenia i klas abstrakcyjnych (ze słowem kluczowym MustInherit). Pod względem syntaktyki interfejsy niewiele różnią się od klas abstrakcyjnych (z wszystkimi metodami abstrakcyjnymi). Różnica głównie polega na tym, że klasy abstrakcyjne stanowią korzeń w drzewie jednokrotnego dziedziczenia, w który pojawiają się klasy udostępniające implementacje metod abstrakcyjnym. W przypadku interfejsów dziedziczenie może być wielokrotne (mówi się tu o rozszerzaniu interfejsów). Ten sam interfejs może być ponadto implementowany przez klasy z różnych drzew dziedziczenia, co więcej, ilość implementowanych interfejsów przez klasy nie jest ograniczona do jednego (tzn. ilość implementowanych przez daną klasę interfejsów może być arbitralna). Podczas specyfikowania interfejsów łatwo zapomnieć kto jest za co odpowiedzialny. Dlatego zawsze należy pamiętać, że: Interfejs jest swego rodzaju kontraktem. Nazwy interfejsów przyjęłó się zaczynać dużą literą I stąd np. interfejs IPokazujący mógłby być interfejsem zawierającym metodę Pokaż. Klasa implementująca interfejs jest klasą, która zgadza się wypełnić kontrakt zdefiniowany w interfejsie. Np. klasa implementująca interfejs IPokazujący powinna dostarczyć implementację metody Pokaż. Klasa klienta jest klasą, która wywołuje metodę interfejsu dostarczoną przez klasę implementującą. Np. klasa Edytor może wywoływać metodę Pokaż klasy Analizator, która implementuje interfejs pokazujący. Interfejsy są szeroko używane w platformie.net. Wystarczy przyjrzeć się klasom kolekcji (array lists, stacks, and queues) Definicja interfejsu Składnia definicji interfejsu jest następująca: [ <attrlist> ] [ Public Private Protected Friend Protected Friend ] _ [ Shadows ] Interface name [ Inherits interfacename[, interfacename ]] [ [ Default ] Property proname ] [ Function memberame ] [ Sub memberame ] [ Event memberame ] End Interface Większość z elementów występujących w powyższym wyrażeniu znana jest z definicji klasy. Z tym, że teraz elementy wewnątrz interfejsu nie mają implementacji (są tu tylko sygnatury metod i zdarzeń) ani modyfikatora dostępu (wszystkie metody interfejsu domyślnie są Public).
Przykład interfejsu, który definiuje metody Read, Write oraz właściwośc Status: Interface IStorable Sub Read() Sub Write(object) Property Status() As Integer End Interface Implementacja interfejsu Przypuśćmy, że chcemy mieć klasę Document, której obiekty mogłyby być zapisywane w bazie danych. Przypuśćmy, że klasa ta jest już zaimplementowana, a chcielibyśmy do niej dodać metody zapisu i odczytu do i z bazy danych. Można to zrobić, deklarując klasę Document jako klasę implementującą interfejs IStorable. Aby zaimplementować interfejs IStorable należy: 1. Zadeklarować klasę Document jako klasę implementująca interfejs IStorable jak następuje: Public Class Document Implements IStorable Można też alternatywnie napisać: Public Class Document : Implements IStor 2. Zaimplementować wszystkie metody interfejsu (oraz zdarzenia, właściwości, itd. jeśli interfejs zawiera ich deklaracje). Dla klasy Document i metody Read wyglądałoby to tak: Public Sub Read() Implements IStorable.Read Console.WriteLine("Implementing the Read Method for IStorable") 'Read W ogólności definicja klasy Document implementującej interfejs IStorable mogłaby wyglądać tak: Public Class Document : Implements IStorable Public Sub Read() Implements IStorable.Read '... 'Read Public Sub Write(ByVal o As Object) Implements IStorable.Write '... 'Write Public Property Status() As Integer Implements IStorable.Status '... End Property End Class 'Document Przykład pełnej implementacji: Option Strict On Imports System Namespace InterfaceDemo ' define the interface Interface IStorable Sub Read() Sub Write(ByVal obj As Object) Property Status() As Integer End Interface 'IStorable ' create a class which implements the IStorable interface
Public Class Document Implements IStorable Public Sub New(ByVal s As String) Console.WriteLine("Creating document with: {0}", s) 'New ' implement the Read method Public Sub Read() Implements IStorable.Read Console.WriteLine("Implementing the Read Method for IStorable") 'Read ' implement the Write method Public Sub Write(ByVal o As Object) Implements IStorable.Write Console.WriteLine( _ "Implementing the Write Method for IStorable") 'Write ' implement the property Public Property Status() As Integer Implements IStorable.Status Get Return mystatus End Get Set(ByVal Value As Integer) mystatus = Value End Set End Property ' store the value for the property Private mystatus As Integer = 0 End Class 'Document Class Tester Public Sub Run() Dim doc As New Document("Test Document") doc.status = -1 doc.read() Console.WriteLine("Document Status: {0}", doc.status) 'Run Public Shared Sub Main() Dim t As New Tester t.run() 'Main End Class 'Tester End Namespace 'InterfaceDemo Wynik działania programu: Creating document with: Test Document Implementing the Read Method for IStorable Document Status: -1
A oto przykład implementacji dwóch interfejsów w jednej klasie: Option Strict On Imports System Namespace InterfaceDemo Interface IStorable Sub Read() Sub Write(ByVal obj As Object) Property Status() As Integer End Interface 'IStorable ' here's the new interface Interface ICompressible Sub Compress() Sub Decompress() End Interface 'ICompressible ' Document implements both interfaces Public Class Document Implements ICompressible, IStorable ' the document constructor Public Sub New(ByVal s As String) Console.WriteLine("Creating document with: {0}", s) 'New ' implement IStorable Public Sub Read() Implements IStorable.Read Console.WriteLine("Implementing the Read Method for IStorable") 'Read Public Sub Write(ByVal o As Object) Implements IStorable.Write Console.WriteLine( _ "Implementing the Write Method for IStorable") 'Write Public Property Status() As Integer Implements IStorable.Status Get Return mystatus End Get Set(ByVal Value As Integer) mystatus = Value End Set End Property ' implement ICompressible Public Sub Compress() Implements ICompressible.Compress Console.WriteLine("Implementing Compress") 'Compress Public Sub Decompress() Implements ICompressible.Decompress Console.WriteLine("Implementing Decompress") 'Decompress
' hold the data for IStorable's Status property Private mystatus As Integer = 0 End Class 'Document Class Tester Public Sub Run() Dim doc As New Document("Test Document") doc.status = -1 doc.read() doc.compress() Console.WriteLine("Document Status: {0}", doc.status) 'Run Shared Sub Main() Dim t As New Tester t.run() 'Main End Class 'Tester End Namespace 'InterfaceDemo Wynik działania programu: Creating document with: Test Document Implementing the Read Method for IStorable Implementing Compress Document Status: -1 Rzutowanie do interfejsu Interfejsy można używać podobnie jak używa się klas abstrakcyjnych w deklaracjach zmiennych referencyjnych. Można na przykład zdefiniować zmienna typu interfejs, aby w niej przechować referencję do obiektu klasy, która ten interfejs implementuje. Dim doc As New Document() Dim isdoc As IStorable = doc isdoc.status = 0 isdoc.read( ) Takie triki w programowaniu pozwalają w dynamiczny sposób przypisywać zmiennym referencje do obiektów różnych klas, kiedy jedynym wymaganym ograniczeniem jest, aby wszystkie obiekty posiadały metody danego interfejsu. Można więc zadeklarować: IStorable isdoc As New IStorable( ) Sprawdzanie interfejsu Aby mieć pewność, że we wszystkich miejscach, gdzie referencjom do interfejsów przypisano obiekty implementujące te interfejsy należy włączyć opcję Option Strict On. Jeśli się tego nie zrobi, może zdarzyc się, że program skompiluje się poprawnie, jednak podczas wykonywania zgłosi błąd (gdyż obiekt nie będzie posiadał metody interfejsu, której się od niego oczekiwało):
System.InvalidCastException: Specified cast is not valid. Aby więc uniknąć takich sytuacji dobrze jest zapytać, czy dany obiekt dysponuje właściwym interfejsem. Robi się to za pomocą operatora Is. Odpowiednim wyrażeniem jest TypeOf espression Is type Przykład: Option Strict On Imports System Namespace InterfaceDemo Interface IStorable Sub Read() Sub Write(ByVal obj As Object) Property Status() As Integer End Interface 'IStorable ' here's the new interface Interface ICompressible Sub Compress() Sub Decompress() End Interface 'ICompressible ' document implements only IStorable Public Class Document Implements IStorable ' the document constructor Public Sub New(ByVal s As String) Console.WriteLine("Creating document with: {0}", s) 'New ' implement IStorable Public Sub Read() Implements IStorable.Read Console.WriteLine("Implementing the Read Method for IStorable") 'Read Public Sub Write(ByVal o As Object) Implements IStorable.Write Console.WriteLine( _ "Implementing the Write Method for IStorable") 'Write Public Property Status() As Integer Implements IStorable.Status Get Return Status End Get Set(ByVal Value As Integer) Status = Value End Set End Property ' hold the data for IStorable's Status property
Private mystatus As Integer = 0 End Class 'Document Class Tester Public Sub Run() Dim doc As New Document("Test Document") ' only cast if it is safe If TypeOf doc Is IStorable Then Dim isdoc As IStorable = doc isdoc.read() Else Console.WriteLine("Could not cast to IStorable") ' this test will fail If TypeOf doc Is ICompressible Then Dim icdoc As ICompressible = doc icdoc.compress() Else Console.WriteLine("Could not cast to ICompressible") 'Run Shared Sub Main() Dim t As New Tester t.run() 'Main End Class 'Tester End Namespace 'InterfaceDemo Wynik działania programu: Creating document with: Test Document Implementing the Read Method for IStorable Could not cast to ICompressible Rozszerzanie interfejsów Rozszerzanie interfejsów jest niczym innym jak budowaniem wielokrotnego drzewa dziedziczenia (interfejs może dziedziczyć po więcej niż jednym interfejsie bazowym). Option Strict On Imports System Namespace InterfaceDemo Interface IStorable Sub Read() Sub Write(ByVal obj As Object) Property Status() As Integer
End Interface 'IStorable ' the Compressible interface is now the ' base for ICompressible2 Interface ICompressible Sub Compress() Sub Decompress() End Interface 'ICompressible ' extend ICompressible to log the bytes saved Interface ICompressible2 Inherits ICompressible Sub LogSavedBytes() End Interface 'ICompressible2 ' Document implements both interfaces Public Class Document Implements ICompressible2, IStorable ' the document constructor Public Sub New(ByVal s As String) Console.WriteLine("Creating document with: {0}", s) 'New ' implement IStorable Public Sub Read() Implements IStorable.Read Console.WriteLine("Implementing the Read Method for IStorable") 'Read Public Sub Write(ByVal o As Object) Implements IStorable.Write Console.WriteLine( _ "Implementing the Write Method for IStorable") 'Write Public Property Status() As Integer Implements IStorable.Status Get Return mystatus End Get Set(ByVal Value As Integer) mystatus = Value End Set End Property ' implement ICompressible Public Sub Compress() Implements ICompressible.Compress Console.WriteLine("Implementing Compress") 'Compress Public Sub Decompress() Implements ICompressible.Decompress Console.WriteLine("Implementing Decompress") 'Decompress ' implement ICompressible2 Public Sub LogSavedBytes() Implements ICompressible2.LogSavedBytes Console.WriteLine("Implementing LogSavedBytes")
'LogSavedBytes ' hold the data for IStorable's Status property Private mystatus As Integer = 0 End Class 'Document Class Tester Public Sub Run() Dim doc As New Document("Test Document") If TypeOf doc Is IStorable Then Dim isdoc As IStorable = doc isdoc.read() Else Console.WriteLine("Could not cast to IStorable") If TypeOf doc Is ICompressible2 Then Dim ildoc As ICompressible2 = doc Console.Write("Calling both ICompressible and ") Console.WriteLine("ICompressible2 methods...") ildoc.compress() ildoc.logsavedbytes() Else Console.WriteLine("Could not cast to ICompressible2") If TypeOf doc Is ICompressible Then Dim icdoc As ICompressible = doc Console.WriteLine( _ "Treating the object as Compressible... ") icdoc.compress() Else Console.WriteLine("Could not cast to ICompressible") 'Run Shared Sub Main() Dim t As New Tester t.run() 'Main End Class 'Tester End Namespace 'InterfaceDemo Wynik działania programu: Creating document with: Test Document Implementing the Read Method for IStorable Calling both ICompressible and ICompressible2 methods... Implementing Compress Implementing LogSavedBytes Treating the object as Compressible...
Implementing Compress Inny przykład (rozszerzanie interfejsu z dziedziczeniem wielokrotnym): Jeśli mamy interfejs: Interface IStorableCompressible Inherits IStorable, ICompressible2 Sub LogOriginalSize() End Interface oraz klasę dokument: Public Class Document Implements IStorableCompressible można zrobić co następuje: If TypeOf doc Is IStorable Then Dim isdoc As IStorable = doc If TypeOf doc Is ICompressible Then Dim icdoc As ICompressible = doc If TypeOf doc Is ICompressible2 Then Dim ic2doc As ICompressible2 = doc If TypeOf doc Is IStorableCompressible Then Dim iscdoc As IStorableCompressible = doc Zaś zmienne zadeklarowane powyżej można wykorzystać tak: isdoc.read( ) icdoc.compress( ) ic2doc.logsavedbytes( ) iscdoc.logoriginalsize( ) Nadpisywanie metod interfejsów Metody interfejsów można nadpisywać tak, jak to się robi z metodami klas. Option Strict On Imports Microsoft.VisualBasic Imports System Namespace OverridingInterfaces Interface IStorable Sub Read() Sub Write() End Interface ' simplify Document to implement only IStorable Public Class Document : Implements IStorable ' the document constructor Public Sub New(ByVal s As String) Console.WriteLine("Creating document with: {0}", s)
' make read virtual Public Overridable Sub Read() Implements IStorable.Read Console.WriteLine("Document Virtual Read Method for IStorable") ' NB: Not virtual! Public Sub Write() Implements IStorable.Write Console.WriteLine("Document Write Method for IStorable") End Class ' derive from Document Public Class Note : Inherits Document Public Sub New(ByVal s As String) MyBase.New(s) Console.WriteLine("Creating note with: {0}", s) ' override the Read method Public Overrides Sub Read() Console.WriteLine("Overriding the Read method for Note!") ' implement my own Write method Public Shadows Sub Write() Console.WriteLine("Implementing the Write method for Note!") End Class Class Tester Public Sub Run() ' create a Document object Dim thenote As Document = New Note("Test Note") ' cast the Document to IStorable If TypeOf thenote Is IStorable Then Dim isnote As IStorable = thenote isnote.read() isnote.write() Console.WriteLine(vbCrLf) ' direct call to the methods thenote.read() thenote.write() Console.WriteLine(vbCrLf) ' create a note object
Dim note2 As New Note("Second Test") ' Cast the note to IStorable If TypeOf note2 Is IStorable Then Dim isnote2 As IStorable = note2 isnote2.read() isnote2.write() Console.WriteLine(vbCrLf) ' directly call the methods note2.read() note2.write() Public Shared Sub Main() Dim t As New Tester t.run() End Class End Namespace Wynik działania programu: Creating document with: Test Note Creating note with: Test Note Overriding the Read method for Note! Document Write Method for IStorable Overriding the Read method for Note! Document Write Method for IStorable Creating document with: Second Test Creating note with: Second Test Overriding the Read method for Note! Document Write Method for IStorable Overriding the Read method for Note! Implementing the Write method for Note!