Gleich und doch nicht ganz: Polymorphismen und Überladung

Wer (mindestens) ein kleines Kind zu Hause hat, kennt vielleicht dieses Phänomen: An einem Tag steht die Spielküche noch in der einen Ecke und schon am nächsten Tag muss das komplette Zimmer umgeräumt werden, wobei natürlich nicht nur die Spielküche verrückt wird, sondern möglichst alle nur irgendwie beweglichen Möbel. Auch wenn sich das Aussehen des Zimmers deutlich verändert hat, ist es dennoch noch eindeutig als Kinderzimmer erkennbar.
Zugegeben, dieser Vergleich „hinkt“ (wie die meisten Vergleiche) ein wenig, denn eigentlich müsste es heißen „an einem Tag betreten sie das Zimmer durch die Tür während sie am nächsten Tag durch das Fenster hineinklettern“. Die Grundidee wird aber trotzdem klar – das Kinderzimmer unterliegt dem Konzept des Polymorphismus.

Beim Stöbern habe ich einen Artikel gefunden, in dem die wichtigsten Eigenschaften und Formen von Polymorphismus beschrieben werden. Grundsätzlich lässt sich aber sagen, dass polymorpher Code immer dort eingesetzt wird, wo gleiche oder ähnliche Codestrukturen an Variationen während der Laufzeit angepasst werden müssen- dazu können gehören:

  • Unterschiedliche Laufzeitumgebungen und Interpreter – im Rahmen von Java Script, z.B. unterschiedliche Browser
  • Unterschiedliche Argumente (z.B. bei Methoden), deren vorherige Umwandlung zu aufwändig oder schlicht nicht möglich wären
  • Beim Aufruf z.B. einer Methode unterschiedliche Rückgabewerte notwendig sind, die ebenfalls nicht einfach ineinander umgewandelt werden können
  • bei z.B. vererbten Klassen, eine Kindklasse andere Operationen beim Aufrufen einer Methode ausführen muss als die Elternklasse

Wer häufig Anwendungen auf Basis des .net-Frameworks von Microsoft entwickelt, dem ist dieses Konzept – in Form von sogenannten Überladungen – mit ziemlicher Sicherheit bereits begegnet.

//version 1
public int GetSum(int val1, int val2){

   return val1 + val2;

}

//version 2
public int GetSum(int[] values){

    int result = 0;

    foreach(int val in values){

        result += val;

    }

    return result;

}

//version 3
public string GetSum(string val1, string val2){

    int result = int.Parse(val1) + int.Parse(val2);

    return result.ToString();

}

Werden Methoden überladen (wie in diesem Beispiel Version 1 als initiale Version der Methode GetSum()),  so besitzen sie in der Regel den gleichen Namen, aber werden z.B. mit anderen Argumenten (Version 2) aufgerufen, oder/ und geben andere Datentypen zurück (Version 3).

Das Überschreiben von Methoden ist eine Technik, die vorwiegend im Zusammenhang mit Klassenvererbung eingesetzt wird. Eine ausführliche Beschreibung bei der Programmierung in JAVA liefert dieser Artikel, für C#.net würde ein Codebeispiel folgendermaßen aussehen:

//die Eltern-Klasse
public class ParentClass{

    public virtual void DoSomething(){

        MessageBox.Show(DateTime.Now.ToString());

    }

}

//die Kind-Klasse
public class ChildClass : ParentClass{

    public override void DoSomething(){

        MessageBox.Show("Hello World!");

    }

}

Auch hierbei wird der Name der Methode DoSomething(), aus der Eltern-Klasse, in der Kind-Klasse wiederverwendet. Diesmal wird jedoch die Methode in der Elternklasse als virtual definiert (womit sie überschrieben werden darf) und in der Kind-Klasse mit override dekoriert (wobei klar wird, dass damit die Methode aus der Eltern-Klasse „ersetzt“ wird).

Eine besondere Form des Polymorphismus hat sich für JavaScript herausgebildet: Neben der Verwendung der bereits beschriebenen Überladungsmethoden,  ist es häufig zusätzlich notwendig den verwendeten Browser für die Durchführung von Aufgaben zu berücksichtigen. Während in einigen Webbrowsern identische Methoden unterschiedliche Namen tragen, gibt es in einigen Browsern Eigenschaften oder Methoden, die ein Anderer gar nicht kennt. Ein Beispiel dafür ist document.documentMode in Microsofts Internet Explorer:

if (document.documentMode == 7) {
    doSomethingIn.IE7_only;
} else {
    doSomething.everwhereElse;
}

Häufig wird die Prüfung auf derartige Methoden verwendet um die korrekte Version des Browsers zu bestimmen und entsprechende, spezifische Operationen durchzuführen.
Gleichzeitig ist es aber auch möglich eigene Implementierungen für Methoden mitzugeben, die in einem Browser gar nicht nativ unterstützt werden. Diese eigenen Lösungen werden als sogenannte Polyfills bezeichnet und ein Beispiel dafür wäre die string.startsWith()-Methode, die vom Internet Explorer erst seit Version 12 nativ unterstützt wird:

//add the 'string.startsWith()' function (polyfill) (e.g. for the Internet Explorer)
if (!String.prototype.startsWith) {
  String.prototype.startsWith = function(searchString, position) {
    position = position || 0;
    return this.indexOf(searchString, position) === position;
  };
}

Gerade vor dem Hintergrund der verschiedenen Clean Code-Initiativen, gibt es immer wieder heftige Diskussionen zur Verwendung von polymorphem Code – und besonders zur Vererbung von Klassen und deren Methoden. Auch wenn ich viele der Argumente dagegen nachvollziehen kann, haben mir diese Techniken bereits in einer Vielzahl von Projekten gute Dienste geleistet. Ziel sollte es immer sein möglichst aufgeräumten und nachvollziehbaren Code zu produzieren. Warum aber sollte es besser sein, die Wand zum Kinderzimmer erneut durchzubrechen und eine zweite Tür hineinzumachen, als ggf. durch das Fenster herein zu klettern?

Gesucht: Gefunden! Ein wiederverwendbarer Extensions-Manager für .net

Die Zeiten der monströßen, schwer zu wartendenden und noch schwerer zu überarbeitenden Software-Monolithen ist glücklicherweise vorbei. Die meisten der heutzutage neu entwickelten Anwendungen werden häufig nicht nur unter den Gesichtspunkten der Übersichtlichkeit (des Quellcodes) und der logischen Modularisierung, sondern auch unter dem Aspekt der (späteren) Erweiterbarkeit bzw. der einfachen Anpassbarkeit (z.B. für underschiedliche Anwendergruppen) gestaltet. Unter dem Schlagwort „loose gekoppelt“ wird dabei häufig das beschrieben, was die meisten Anwender schlicht als „Plugin“ kennen.

Es gibt einige, teils sehr mächtige, Erweiterungen für das .net-Framework, die genau für solche Anwendungsbereiche entwickelt und optimiert wurden. Eines der Bekanntesten ist wohl Prism.
Häufig verhält es sich mit solchen Bibliotheken aber wie mit den sprichwörtlichen Kanonen, vor denen sich Spatzen in Acht nehmen sollten: Sie vergrößern nicht nur die Zahl der Anwendungsdateien oder die Gesamtgröße des Builds, sondern setzen teilweise komplizierte Gestaltungsmuster und Quellcode-Konventionen voraus.

Nicht selten, genügt das von Microsoft standardmäßig zur Verfügung gestellte Managed Extensibility Framework (MEF) völlig.
Es gibt bereits einige recht gute Tutorials zu diesem Thema, so dass ich an dieser Stelle nicht zu sehr bei den Grundlagen beginnen möchtem dennoch sei erwähnt: Grundsätzlich besteht das Laden der Extensions aus dem Erstellen von Katalogen (basierend auf einzelnen Assembly-Dateien oder ganzen Verzeichnissen), die dann vom MEF zu einer Liste an Implementierungen kompiliert werden.
Im Quellcode kann dies dann folgendermaßen aussehen:

<ImportMany(ICalculate)>
Private _allExtensions As ObservableCollection(Of Lazy(Of ICalculate, ICalculateMetadata)) = New ObservableCollection(Of Lazy(Of ICalculate, ICalculateMetadata))

Public Sub Update(ByVal paths As String())

        'assemble the catalog
        Dim catalog = New AggregateCatalog()

        For Each path As String In paths

            Select Case True

                Case File.Exists(path)
                    catalog.Catalogs.Add(New AssemblyCatalog(path))

                Case Directory.Exists(path)
                    catalog.Catalogs.Add(New DirectoryCatalog(path))

            End Select

        Next

        'compose the collection
        Dim container As CompositionContainer = New CompositionContainer(catalog)
        container.ComposeParts(Me)

    End Sub
  • ICalculate ist dabei das in einer Extension zu implementierende Interface
  • ICalculateMetadata die zur Verfügung gestellten Metadaten
  • Die Liste verfügbarer Extensions wird mit der Variable _allExtensions bereitgestellt

Stefan Henneken hat einen Artikel ein gutes Konzept beschrieben, mit dem man – basierend auf einer Basisklasse – Metadaten verwenden kann um Extensions zusätzlich zu beschreiben.

Um nun eine wiederverwendbaren (Manager-)Klasse zu erstellen, betten wir nun die oben genannte Routine in eine Klasse mit generischen Typen ein:

Public Class BasicManager(Of TInterface, TMetadata)

<ImportMany()>
Private _allExtensions As ObservableCollection(Of Lazy(Of TInterface, TMetadata)) = New ObservableCollection(Of Lazy(Of TInterface, TMetadata))

Public ReadOnly Property Extensions As Lazy(Of TInterface, TMetadata)()
        Get
            Return _validExtensions.ToArray
        End Get
End Property

Public Sub Update(ByVal paths As String())
...
End Sub

End Class

Zu beachten ist, dass neben den Datentypen in _allExtensions, auch das ImportMany-Attribute angepasst werden muss. Da MEF keine generischen Datentypen unterstützt, wird der Datentyp einfach leer gelassen. Und genau damit wird es nun problematisch:
Normalerweise wird genau über diesen Datentyp eine Filterung der gefundenen Interface-Implementierungen durchgeführt. So lange man nur ein Interface in seiner Anwendung hat, ist dies recht unproblematisch, Hat man jedoch mehrere Interfaces verfügbar, würde _allExtensions alle Extensions für alle Interfaces enthalten. Um dies zu korrigieren, wird die Update-Routine, mit dem Aufruf einer ValidateExtensions-Methode ergänzt:

Private Sub ValidateExtensions()

        _validExtensions = New ObservableCollection(Of Lazy(Of TInterface, TMetadata))

        For Each extension In _allExtensions

            Try

                If ImplementsInterface(extension) Then

                    _validExtensions.Add(extension)

                End If

            Catch ex As Exception

            End Try

        Next

    End Sub

    Private Function ImplementsInterface(ByRef extension) As Boolean

        For Each iFace In extension.Value.GetType.GetInterfaces

            If iFace.ToString = GetType(TInterface).ToString Then

                Return True

            End If

        Next

        Return False

    End Function

Alle gefundenen (und in _allExtensions gespeicherten) Extensions werden darin durchaufen und nur die in _validExtensions gespeichert, die auch tatsächlich das (gewünschte) Interface implementieren. Der Rückgabewert der öffentlichen Eigenschaft Extensions wird dann natürlich auch auf _validExtensions gesetzt.

Natürlich handelt es sich hierbei um ein stark vereinfachtes Beispiel und häufig werden weitere Zusatzfunktionen für das Laden von Plugins benötigt. Dennoch, ist es schon mit dieser übersichtlichen Menge an Code möglich eine stabile und Wartungsfreundliche Plugin-Infrastruktur aufzubauen – die zudem noch wiederverwendbar ist. Wer selbst etwas experimentieren möchte, kann dies auch gern mit dem auf Github verfügbaren Beispieldateien tun.

Nachgefragt – Nicht-Blockende Dialoge in WPF

Jeder Entwickler kommt irgendwann an den Punkt, an dem er seine Anwender nach weiteren Angaben oder Eingaben fragen muss. Sofern es sich nicht um eine „Single Page“- oder „inline“- Anwendung handelt, gehört es zum üblichen Vorgehen, einen Dialog zu öffnen auf die Eingabe (und deren Bestätigung) zu warten und die Informationen anschließend (weiter) zu verarbeiten.

Möchte man dies z.B. in einer Windows Forms-Anwendung realisieren (um z.B. eine Datei zu öffnen), gibt es den recht komfortablen OpenFileDialog. In der Windows Presentation Foundation (WPF) gibt es diesen und ähnliche Dialoge nicht. Der übliche Tipp in WPF für den eben genannten Anwendungsfall lautet, auf das System.Windows.Forms-Assembly referenzieren und dann den OpenFileDialog verwenden. Wie sieht es aber aus, wenn man eine Eingabe benötigt, für den es keinen vorgefertigten Dialog gibt oder die Standarddialoge nicht ausreichen?

Folgt man dem MVVM-Designmuster in WPF, ist es eigentlich ganz einfach einen Dialog zu entwickeln: Ein Window (als View) verwenden und die Elemente per XAML an die Eigenschaften eines zugehörigen View Models binden – anschließend alles per Window.Show() anzeigen. So weit so gut, jedoch liegt auch hier die Herausforderung wieder im Detail. Was ist zum Beispiel…

  • … wenn der Dialog/ das Warten auf die Eingabe die übrige Anwendung nicht blockieren darf (weil beispielsweise asynchrone Prozesse im Hintergrund ausgeführt werden)?
  • .. wenn man häufig verschiedene Dialoge benötigt und die Codebase möglichst schlank halten möchte (z.B. auch vor dem Hintergrund des Bugfixings und der Wartbarkeit)?

Zudem finde ich es persönlich sehr viel eleganter lediglich mittels ShowDialog-Methode (eines View Models) sowohl den View zu öffnen, als auch auf die Bestätigung durch den Nutzer zu warten – anstatt den View „separat“ zu instanziieren und dann z.B. über irgendwelche Event Handler auf die Nutzereingaben reagieren zu müssen.

Um nicht bei jedem Projekt wieder von vorn beginnen zu müssen, habe ich mir ein wieder verwendbares Dialog-Framework gebastelt (das auch Bestandteil meiner WPF-Basisklassenarchitektur ist):

Abgeleitet von einer View Model(-Basisklasse) – die u.a. INotifyPropertyChanged implementiert – ist das „Herzstück“ der Implementierung die DialogViewModelBase-Klasse, die eine öffentliche Funktion namens ShowDialog() bereitstellt.

Public Function ShowDialog() As Boolean

'show the view
ShowView()

'wait for user input
_myBlock = New EventWaitHandle(False, EventResetMode.ManualReset)
WaitForEvent(_myBlock, New TimeSpan(24, 0, 0))

'remove the wait handle
_myBlock.Dispose()

'return the dialog closing indicator
Return Me.DialogResult

End Function

In dieser Methode passieren drei Dinge, die den Großteil der eben genannten Probleme mit der Arbeit mit Dialogen abdecken:

  1. Der zugehörige View wird mittels ShowView() angezeigt
  2. Es wird darauf gewartet, dass der Nutzer den View auch wieder schließt – entweder in Form einer Eingabebestätigung oder eines Abbruchs
  3. Nach dem Schließen des View wird ein Boolean zurückgegeben, der anschließend im aufrufenden ViewModel ausgewertet werden kann

Für den ersten Punkt, stellt die DialogViewModelBase-Klasse zwei zu überschreibende Methoden bereit:

Protected MustOverride Sub ShowView()

Protected MustOverride Sub CloseView()

Ist man sich sicher, dass man lediglich Windows zur Darstellung der Nutzeroberfläche verwendet, kann man diese beiden Methoden auch weglassen und DialogViewModelbase eine entsprechende Eigenschaft mitgeben bzw. das Öffnen und Schließen des View direkt in die Methoden Showdialog() und Close() einfließen lassen. Da ich mich in meiner Basisinfrastruktur dazu nicht zu sehr festlegen (und später z.B. auch UserControls verwenden möchte), habe ich mich für diese generische Lösung entschieden.

Die Methode namens Close(), setzt nicht nur die DialogResult-Eigenschaft, sondern ruft ebenfalls die eben beschriebene CloseView()-Methode auf.

Protected Sub Close(ByVal result As Boolean)

            'set the dialog result
            Me.DialogResult = result

            'close the view
            CloseView()

            'release the blocking
            If Not IsNothing(_myBlock) Then

                _myBlock.Set()

            End If

End Sub

Nun aber das große Geheimnis des NICHT-blockens: Die WaitForEvent()-Methode. Nachdem ich einige (mehr oder minder erfolgreiche) Lösungsansätze probiert habe, bin ich in diesem Artikel auf eine sehr elegante Implementierung gestoßen.

Private Function WaitForEvent(eventHandle As EventWaitHandle, Optional timeout As TimeSpan = Nothing) As Boolean

            Dim didWait As Boolean = False
            Dim frame = New DispatcherFrame()
            Dim start As New ParameterizedThreadStart(Sub()

                                                          didWait = eventHandle.WaitOne(timeout)
                                                          frame.[Continue] = False

                                                      End Sub)

            Dim thread = New Thread(start)
            thread.Start()
            Dispatcher.PushFrame(frame)
            Return didWait

End Function

Die Grundidee beruht darauf einen zusätzlichen Thread zu verwenden um asynchron auf ein Ereignis zu warten – wodurch der Hauptthread (und mit ihm auch die Oberfläche) nicht blockiert wird. Gleichzeitig wartet die Anwendung auf die Rückmeldung aus dem Dialog und führt nachgelagerte Operationen erst aus, nachdem ShowDialog() eine Antwort zurückgegeben hat.

Was ist nun aber mit dem kleinen Kreuzchen rechts oben in der Titelleiste des Dialogfensters? Normalerweise ist dieses Kreuzchen (bei einem Window) dafür gedacht, das Fenster zu schließen – tut es auch, leider schließt es dabei aber nicht den „Warte“-Thread, was in der Praxis bedeutet, dass die Anwendung nicht sauber geschlossen wird.

Um dieses Problem zu lösen, gibt es jedoch einen recht einfachen Trick, den ich in diesem Artikel gefunden habe: Mithilfe eines Behaviors wird ein delegated Command (in meinem ViewModelReleayCommand) an das ClosedEvent des Window gebunden, so dass – wie bei jedem anderen Command auch – die Close()-Methode (mit einem Negativwert für das DialogResult) aufgerufen werden kann.

Den kompletten Quellcode findet ihr auf Github. Viel Spaß beim Experimentieren!

Immer Synchron, dank Block Exchange und Syncthing

Gunrdsätzlich tue ich mich etwas schwer damit meine Daten und Dateien an Cloud-Services zu übergeben oder mit einem Online-Server zu synchronisieren, den ich nicht selbst verwalte. Ich glaube, ich gehöre nicht zu „den üblichen Verschwörungstheoretikern“, die zunächst einmal jedem Anbieter böse Absichten unterstellen, ich weiß aber dennoch ganz gern wer welche Daten von mir besitzt und (in groben Zügen) welches Risiko ich damit eingehe den Dienst von Anbieter A oder B zu verwenden.

Dies ist auch einer der Gründe warum ich bisher Dienste wie Dropbox, OneDrive oder Google Drive eher versucht habe zu vermeiden. Umso mehr hat es mich gefreut, als ich auf ein Projekt namens Syncthing gestoßen bin.

Klassische Cloud-Dateidienste funktionieren in der Regel so, dass es einen zentralen Server gibt, auf den die Daten hochgeladen werden und einen lokalen Service (auf meinem Rechner), der das Dateisystem überwacht und alle Änderungen an den Server weitergibt. Hat man also mehrere Computer, auf denen man Daten gemeinsam nutzen oder synchron halten möchte, müssen die Änderungen auf einem der Computer zunächst einmal an den Server weitergegeben und dann auf den zweiten PC heruntergeladen werden. Syncthing funktiniert da anders: Syncthing vernetzt mehrere Computer mithilfe des sogenannten Block Exchange-Protokolls direkt miteinander. Das hat folgende Vorteile:

  • Die Daten werden direkt zwischen den Computern ausgetauscht. Grundsätzlich ist kein zentraler Server notwendig so dass die zeitliche Verzögerung zwischen der Aktualisierung einzelner Verzeichnissedeutlich reduziert werden kann.
  • Ein Syncthing-Netzwerk kann ausschließlich mit eigenen Computern betrieben werden. Zum Einen entfällt damit die Notwendigkeit eines (kostenpflichtigen) Cloud-Service-Anbieters und zum Anderen liegen die Daten ausschließlich auf den eigenen Computersystemen.
  • Die Daten werden zwischen den einzelnen Netzwerkknoten verschlüsselt übertragen. Kommt es also zu einem man-in-the-middle-Angriff, sieht der Angreifer lediglich verschlüsselte Daten. Dies ist vor allem auch für den nächsten Punkt sehr interessant:
  • Syncthing lässt sich nicht nur innerhalb lokaler Netzwerke betreiben, es ist auch möglich über das Internet zu kommunizieren. Um die Authentizität der vernetzten Computer sicherzustellen, besitzt jeder Host eine eineindeutig ID, die vor jedem Datenaustausch mit dem jeweiligen Partner ausgetauscht werden muss.
  • Der Datenaustausch kann ziemlich granular definiert und konrolliert werden. Grundsätzlich wird immer auf Verzeichnisbasis geteilt, jedoch kann jedes der Verzeichnisse beliebige Unterverzeichnisse beinhalten und es wird für jedes Verzeichnis separat festgelegt mit welchem Partner geteilt werden darf.

Ein zusätzlicher Vorteil besteht darin, dass die Anwendung nicht auf ein bestimmtes Betriebsystem festgelegt ist – vielmehr handelt es sich bei Syncthing um eine Bibliothek, die die Netzwerk-Service bereitstellt, der widerum in eigene Anwendungen integriert werden kann:

  • Für Windows-Systeme z.B. gibt es eine Implementierung namens SyncTrayzor, die über Github verfügbar ist und für Client-PCs entwickelt wurde. Es gibt einige gute Anleitungen um SyncTrayzor auch als Windows-Dienst einzurichten (um ihn z.B. als Server-Komponente zu verwenden).
  • Da ich selbst ein Synology NAS betreibe, bevorzuge ich für einen Syncthing-Server jedoch das von der SynoCommunity bereitgestellte Softwarepaket. Da sich Syncthing auch automatisch aktualisiert, ist es unerheblich, dass das Paket nicht die neueste Version der Bibliothek enthält – diese wird automatisch beim ersten Start heruntergeladen und installiert.
  • Wer seine Handy-Daten zentral sichern oder austauschen möchte, dem sei die Android-App zu empfehlen.

Mein Fazit: Auch wenn Syncthing (aktuell noch) nicht für die Datensynchronisation auf Enterprise-Level gedacht ist, ist es ein unglaublich einfach zu handhabendes, stabiles und performantes Tool, das auf keinem PC fehlen sollte.