Suche…


Abstraktion

Abstraktionsebenen bestimmen, wann die Dinge aufgeteilt werden müssen.

Die Abstraktion wird durch die Implementierung von Funktionen mit immer detaillierterem Code erreicht. Der Einstiegspunkt eines Makros sollte eine kleine Prozedur mit einem hohen Abstraktionsgrad sein , die es ermöglicht, auf einen Blick zu erfassen, was los ist:

Public Sub DoSomething()
    With New SomeForm
        Set .Model = CreateViewModel
        .Show vbModal            
        If .IsCancelled Then Exit Sub
        ProcessUserData .Model
    End With
End Sub

Die DoSomething Prozedur hat einen hohen Abstraktionsgrad : Wir können feststellen, dass sie ein Formular anzeigt und ein Modell erstellt und dieses Objekt an eine ProcessUserData Prozedur ProcessUserData , die weiß, was mit ihr zu tun ist - wie das Modell erstellt wird, ist Aufgabe einer anderen Prozedur:

Private Function CreateViewModel() As ISomeModel
    Dim result As ISomeModel
    Set result = SomeModel.Create(Now, Environ$("UserName"))
    result.AvailableItems = GetAvailableItems
    Set CreateViewModel = result
End Function

Die Funktion CreateViewModel ist nur für das Erstellen einer ISomeModel Instanz verantwortlich. Zu dieser Verantwortung gehört auch der Erwerb einer Reihe verfügbarer Elemente. Wie diese Elemente erworben werden, ist ein Implementierungsdetail, das hinter der GetAvailableItems Prozedur GetAvailableItems wird:

Private Function GetAvailableItems() As Variant
    GetAvailableItems = DataSheet.Names("AvailableItems").RefersToRange
End Function

Hier liest die Prozedur die verfügbaren Werte aus einem benannten Bereich in einem DataSheet Arbeitsblatt. Es könnte genauso gut aus einer Datenbank gelesen werden, oder die Werte könnten hart codiert sein: Dies ist ein Implementierungsdetail , das für keine der höheren Abstraktionsstufen von Belang ist.

Verkapselung

Encapsulation verbirgt Implementierungsdetails vor Clientcode.

Das Handling QueryClose- Beispiel veranschaulicht die Kapselung: Das Formular verfügt über ein Kontrollkästchen, der Clientcode funktioniert jedoch nicht direkt damit. Das Kontrollkästchen enthält Implementierungsdetails. Der Clientcode muss wissen, ob die Einstellung aktiviert ist oder nicht.

Wenn sich der Kontrollkästchenwert ändert, weist der Handler ein privates Feldmitglied zu:

Private Type TView
    IsCancelled As Boolean
    SomeOtherSetting As Boolean
    'other properties skipped for brievety
End Type
Private this As TView

'...

Private Sub SomeOtherSettingInput_Change()
    this.SomeOtherSetting = CBool(SomeOtherSettingInput.Value)
End Sub

Wenn der Clientcode diesen Wert lesen möchte, muss er sich nicht um ein Kontrollkästchen kümmern, sondern verwendet stattdessen die SomeOtherSetting Eigenschaft:

Public Property Get SomeOtherSetting() As Boolean
    SomeOtherSetting = this.SomeOtherSetting
End Property

Die SomeOtherSetting Eigenschaft kapselt den SomeOtherSetting des Kontrollkästchens. Client-Code muss nicht wissen, dass ein Kontrollkästchen vorhanden ist, nur dass es eine Einstellung mit einem booleschen Wert gibt. Durch das Einkapseln des Boolean Werts haben wir dem Kontrollkästchen eine Abstraktionsebene hinzugefügt.


Verwendung von Schnittstellen zur Durchsetzung der Unveränderlichkeit

Lassen Sie uns das noch einen Schritt weiter gehen, indem Sie das Modell des Formulars in einem dedizierten Klassenmodul kapseln . Wenn wir jedoch eine Public Property für den UserName und den Timestamp UserName , müssten wir die Property Let UserName , wodurch die Eigenschaften veränderbar werden. Wir möchten nicht, dass der Clientcode diese Werte nach dem UserName ändern kann.

Die CreateViewModel Funktion im Abstraction- Beispiel gibt eine ISomeModel Klasse zurück: ISomeModel ist unsere Schnittstelle und sieht etwa so aus:

Option Explicit

Public Property Get Timestamp() As Date
End Property

Public Property Get UserName() As String
End Property

Public Property Get AvailableItems() As Variant
End Property

Public Property Let AvailableItems(ByRef value As Variant)
End Property

Public Property Get SomeSetting() As String
End Property

Public Property Let SomeSetting(ByVal value As String)
End Property

Public Property Get SomeOtherSetting() As Boolean
End Property

Public Property Let SomeOtherSetting(ByVal value As Boolean)
End Property

Hinweis Timestamp und UserName Eigenschaften aussetzen nur ein Property Get Accessor. Nun kann die SomeModel Klasse diese Schnittstelle implementieren:

Option Explicit
Implements ISomeModel

Private Type TModel
    Timestamp As Date
    UserName As String
    SomeSetting As String
    SomeOtherSetting As Boolean
    AvailableItems As Variant
End Type
Private this As TModel

Private Property Get ISomeModel_Timestamp() As Date
    ISomeModel_Timestamp = this.Timestamp
End Property

Private Property Get ISomeModel_UserName() As String
    ISomeModel_UserName = this.UserName
End Property

Private Property Get ISomeModel_AvailableItems() As Variant
    ISomeModel_AvailableItems = this.AvailableItems
End Property

Private Property Let ISomeModel_AvailableItems(ByRef value As Variant)
    this.AvailableItems = value
End Property

Private Property Get ISomeModel_SomeSetting() As String
    ISomeModel_SomeSetting = this.SomeSetting
End Property

Private Property Let ISomeModel_SomeSetting(ByVal value As String)
    this.SomeSetting = value
End Property

Private Property Get ISomeModel_SomeOtherSetting() As Boolean
    ISomeModel_SomeOtherSetting = this.SomeOtherSetting
End Property

Private Property Let ISomeModel_SomeOtherSetting(ByVal value As Boolean)
    this.SomeOtherSetting = value
End Property

Public Property Get Timestamp() As Date
    Timestamp = this.Timestamp
End Property

Public Property Let Timestamp(ByVal value As Date)
    this.Timestamp = value
End Property

Public Property Get UserName() As String
    UserName = this.UserName
End Property

Public Property Let UserName(ByVal value As String)
    this.UserName = value
End Property

Public Property Get AvailableItems() As Variant
    AvailableItems = this.AvailableItems
End Property

Public Property Let AvailableItems(ByRef value As Variant)
    this.AvailableItems = value
End Property

Public Property Get SomeSetting() As String
    SomeSetting = this.SomeSetting
End Property

Public Property Let SomeSetting(ByVal value As String)
    this.SomeSetting = value
End Property

Public Property Get SomeOtherSetting() As Boolean
    SomeOtherSetting = this.SomeOtherSetting
End Property

Public Property Let SomeOtherSetting(ByVal value As Boolean)
    this.SomeOtherSetting = value
End Property

Die Schnittstellenmitglieder sind alle Private , und alle Mitglieder der Schnittstelle müssen implementiert werden, damit der Code kompiliert werden kann. Die Public Member sind nicht Teil der Schnittstelle und unterliegen daher nicht dem für die ISomeModel Schnittstelle geschriebenen Code.


Verwenden einer Factory-Methode zum Simulieren eines Konstruktors

Mit einem VB_PredeclaredId- Attribut können wir der SomeModel Klasse eine Standardinstanz SomeModel und eine Funktion schreiben, die wie ein SomeModel der SomeModel ( Shared in VB.NET, static in C #) funktioniert, das der Clientcode aufrufen kann, ohne dass er zuerst erstellt werden muss ein Beispiel, wie wir es hier gemacht haben:

Private Function CreateViewModel() As ISomeModel
    Dim result As ISomeModel
    Set result = SomeModel.Create(Now, Environ$("UserName"))
    result.AvailableItems = GetAvailableItems
    Set CreateViewModel = result
End Function

Diese Factory-Methode weist die Eigenschaftswerte zu, die schreibgeschützt sind, wenn von der ISomeModel Schnittstelle aus ISomeModel wird, hier Timestamp und UserName :

Public Function Create(ByVal pTimeStamp As Date, ByVal pUserName As String) As ISomeModel
    With New SomeModel
        .Timestamp = pTimeStamp
        .UserName = pUserName
        Set Create = .Self
    End With
End Function

Public Property Get Self() As ISomeModel
    Set Self = Me
End Property

Und jetzt können wir gegen die ISomeModel Schnittstelle ISomeModel , die Timestamp und UserName als schreibgeschützte Eigenschaften UserName , die niemals neu zugewiesen werden können (solange der Code gegen die Schnittstelle geschrieben wird).

Polymorphismus

Polymorphismus ist die Fähigkeit, dieselbe Schnittstelle für verschiedene zugrunde liegende Implementierungen bereitzustellen.

Durch die Möglichkeit, Schnittstellen zu implementieren, kann die Anwendungslogik vollständig von der Benutzeroberfläche oder von der Datenbank oder von diesem oder diesem Arbeitsblatt entkoppelt werden.

ISomeView , Sie haben eine ISomeView Schnittstelle, die das Formular selbst implementiert:

Option Explicit

Public Property Get IsCancelled() As Boolean
End Property

Public Property Get Model() As ISomeModel
End Property

Public Property Set Model(ByVal value As ISomeModel)
End Property

Public Sub Show()
End Sub

Der Code-Behind des Formulars könnte folgendermaßen aussehen:

Option Explicit 
Implements ISomeView

Private Type TView
    IsCancelled As Boolean
    Model As ISomeModel
End Type
Private this As TView

Private Property Get ISomeView_IsCancelled() As Boolean
    ISomeView_IsCancelled = this.IsCancelled
End Property

Private Property Get ISomeView_Model() As ISomeModel
    Set ISomeView_Model = this.Model
End Property

Private Property Set ISomeView_Model(ByVal value As ISomeModel)
    Set this.Model = value
End Property

Private Sub ISomeView_Show()
    Me.Show vbModal
End Sub

Private Sub SomeOtherSettingInput_Change()
    this.Model.SomeOtherSetting = CBool(SomeOtherSettingInput.Value)
End Sub

'...other event handlers...

Private Sub OkButton_Click()
    Me.Hide
End Sub

Private Sub CancelButton_Click()
    this.IsCancelled = True
    Me.Hide
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If CloseMode = VbQueryClose.vbFormControlMenu Then
        Cancel = True
        this.IsCancelled = True
        Me.Hide
    End If
End Sub

Allerdings verbietet nichts, ein anderes Klassenmodul zu erstellen, das die ISomeView Schnittstelle implementiert, ohne ein Benutzerformular zu sein - dies könnte eine SomeViewMock Klasse sein:

Option Explicit
Implements ISomeView

Private Type TView
    IsCancelled As Boolean
    Model As ISomeModel
End Type
Private this As TView

Public Property Get IsCancelled() As Boolean
    IsCancelled = this.IsCancelled
End Property

Public Property Let IsCancelled(ByVal value As Boolean)
    this.IsCancelled = value
End Property

Private Property Get ISomeView_IsCancelled() As Boolean
    ISomeView_IsCancelled = this.IsCancelled
End Property

Private Property Get ISomeView_Model() As ISomeModel
    Set ISomeView_Model = this.Model
End Property

Private Property Set ISomeView_Model(ByVal value As ISomeModel)
    Set this.Model = value
End Property

Private Sub ISomeView_Show()
    'do nothing
End Sub

Und jetzt können wir den Code ändern, der mit einer UserForm und die ISomeView Schnittstelle zum ISomeView , z. B. indem Sie die Form als Parameter ISomeView , anstatt sie zu instanziieren:

Public Sub DoSomething(ByVal view As ISomeView)
    With view
        Set .Model = CreateViewModel
        .Show
        If .IsCancelled Then Exit Sub
        ProcessUserData .Model
    End With
End Sub

Da die DoSomething Methode von einer Schnittstelle (z. B. einer Abstraktion ) und nicht von einer konkreten Klasse (z. B. einer bestimmten UserForm ) UserForm , können wir einen automatisierten UserForm schreiben, der gewährleistet, dass ProcessUserData nicht ausgeführt wird, wenn view.IsCancelled True hat test Erstellen Sie eine SomeViewMock Instanz, setzen Sie die IsCancelled Eigenschaft auf True und übergeben Sie sie an DoSomething .


Testbarer Code hängt von Abstraktionen ab

Unit-Tests können in VBA geschrieben werden, es gibt Add-Ins, die es sogar in die IDE integrieren. Wenn Code jedoch eng mit einem Arbeitsblatt, einer Datenbank, einem Formular oder dem Dateisystem gekoppelt ist, erfordert der Komponententest ein tatsächliches Arbeitsblatt, eine Datenbank, ein Formular oder ein Dateisystem. Diese Abhängigkeiten sind ein neuer Out-of-Control-Fehler Punkte, die der zu testende Code isolieren sollte, sodass für Einzeltests kein Arbeitsblatt, keine Datenbank, kein Formular oder kein Dateisystem erforderlich ist.

Durch das Schreiben von Code gegen Schnittstellen, sodass SomeViewMock Stub / Mock-Implementierungen (wie das obige SomeViewMock Beispiel) injizieren kann, können Sie Tests in einer "kontrollierten Umgebung" schreiben und simulieren, was passiert, wenn jede einzelne der 42 möglich ist Permutationen von Benutzerinteraktionen auf die Daten des Formulars, ohne auch nur ein Formular anzuzeigen und manuell auf ein Formularsteuerelement zu klicken.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow