Ist MVVM sinnlos? [geschlossen]


91

Ist eine orthodoxe MVVM-Implementierung sinnlos? Ich erstelle eine neue Anwendung und habe Windows Forms und WPF in Betracht gezogen. Ich habe mich für WPF entschieden, weil es zukunftssicher ist und viel Flexibilität bietet. Es gibt weniger Code und es ist einfacher, mithilfe von XAML wesentliche Änderungen an Ihrer Benutzeroberfläche vorzunehmen.

Da die Wahl für WPF offensichtlich ist, habe ich mir gedacht, dass ich MVVM genauso gut als meine Anwendungsarchitektur verwenden kann, da es Mischbarkeit, Trennungsprobleme und Testbarkeit von Einheiten bietet. Theoretisch scheint es schön wie der heilige Gral der UI-Programmierung. Dieses kurze Abenteuer; hat sich jedoch zu echten Kopfschmerzen entwickelt. Wie in der Praxis erwartet, habe ich festgestellt, dass ich ein Problem gegen ein anderes getauscht habe. Ich neige dazu, ein besessener Programmierer zu sein, weil ich die Dinge richtig machen möchte, damit ich die richtigen Ergebnisse erzielen und möglicherweise ein besserer Programmierer werden kann. Das MVVM-Muster hat meinen Produktivitätstest gerade nicht bestanden und ist gerade zu einem großen Glücksfall geworden!

Das klare Beispiel ist das Hinzufügen von Unterstützung für ein modales Dialogfeld. Der richtige Weg ist, ein Dialogfeld einzurichten und es an ein Ansichtsmodell zu binden. Es ist schwierig, dies zum Laufen zu bringen. Um vom MVVM-Muster zu profitieren, müssen Sie Code an mehreren Stellen in den Ebenen Ihrer Anwendung verteilen. Sie müssen auch esoterische Programmierkonstrukte wie Vorlagen und Lamba-Ausdrücke verwenden. Dinge, bei denen Sie auf den Bildschirm starren und sich am Kopf kratzen. Dies macht Wartung und Debugging zu einem Albtraum, der darauf wartet, passiert zu werden, wie ich kürzlich herausgefunden habe. Ich hatte ein About-Feld, das einwandfrei funktionierte, bis ich beim zweiten Aufruf eine Ausnahme bekam, die besagte, dass das Dialogfeld nach dem Schließen nicht mehr angezeigt werden konnte. Ich musste dem Dialogfenster einen Ereignishandler für die Schließfunktion hinzufügen. eine weitere in der IDialogView-Implementierung und schließlich eine weitere im IDialogViewModel. Ich dachte, MVVM würde uns vor solch extravagantem Hackery retten!

Es gibt mehrere Leute mit konkurrierenden Lösungen für dieses Problem und sie sind alle Hacks und bieten keine saubere, leicht wiederverwendbare, elegante Lösung. Die meisten MVVM-Toolkits beschönigen Dialoge, und wenn sie diese ansprechen, handelt es sich lediglich um Warnfelder, für die keine benutzerdefinierten Schnittstellen oder Ansichtsmodelle erforderlich sind.

Ich habe vor, das MVVM-Ansichtsmuster aufzugeben, zumindest seine orthodoxe Implementierung. Was denken Sie? Hat sich die Mühe für Sie gelohnt, wenn Sie welche hatten? Bin ich nur ein inkompetenter Programmierer oder ist MVVM nicht das, worauf es ankommt?


6
Ich habe immer gefragt, ob MVVM überentwickelt ist oder nicht. Interessante Frage.
Taylor Leese

11
Muster wie MVVM und MVC scheinen zu überentwickelt zu sein, bis Sie einige Änderungen vornehmen oder eine Komponente austauschen müssen. Wenn Sie das zum ersten Mal tun müssen, zahlt sich die
Robert Harvey

41
Lambdas sind esoterisch? Neuigkeiten für mich.
Ray Booysen

5
@ Ray - Haha, +1 für diesen Kommentar! : D
Venemo

7
Wie Alan Cooper vor über einem Jahrzehnt in About Face betonte, machen Sie wahrscheinlich etwas falsch, wenn Sie Benutzeroberflächen entwerfen und modale Dialoge kein Randfall sind.
Robert Rossney

Antworten:


61

Tut mir leid, wenn meine Antwort etwas länger wurde, aber beschuldigen Sie mich nicht! Ihre Frage ist ebenfalls lang.

Zusammenfassend ist MVVM nicht sinnlos.

Das klare Beispiel ist das Hinzufügen von Unterstützung für ein modales Dialogfeld. Der richtige Weg ist, ein Dialogfeld einzurichten und es an ein Ansichtsmodell zu binden. Es ist schwierig, dies zum Laufen zu bringen.

Ja, das ist es wirklich.
MVVM bietet Ihnen jedoch eine Möglichkeit, das Erscheinungsbild der Benutzeroberfläche von der Logik zu trennen. Niemand zwingt Sie, es überall zu verwenden, und niemand hält eine Waffe gegen Ihre Stirn, damit Sie für alles ein separates ViewModel erstellen.

Hier ist meine Lösung für dieses spezielle Beispiel:
Wie die Benutzeroberfläche mit einer bestimmten Eingabe umgeht, geht das ViewModel nichts an. Ich würde der .xaml.cs-Datei der Ansicht Code hinzufügen, der das Dialogfeld instanziiert und dieselbe ViewModel-Instanz (oder bei Bedarf etwas anderes) wie ihren DataContext festlegt.

Um vom MVVM-Muster zu profitieren, müssen Sie Code an mehreren Stellen in den Ebenen Ihrer Anwendung verteilen. Sie müssen auch esoterische Programmierkonstrukte wie Vorlagen und Lamba-Ausdrücke verwenden.

Nun, Sie müssen es nicht an mehreren Stellen verwenden. So würde ich es lösen:

  • Fügen Sie die XAML zur Ansicht hinzu und nichts in der .xaml.cs
  • Schreiben Sie jede App-Logik (mit Ausnahme der Dinge, die direkt mit UI-Elementen funktionieren würden) in ein ViewModel
  • Der gesamte Code, der von der Benutzeroberfläche ausgeführt werden sollte, aber nichts mit Geschäftslogik zu tun hat, wird in die .xaml.cs-Dateien übertragen

Ich denke, dass der Zweck von MVVM in erster Linie darin besteht, die Logik der Anwendung von der konkreten Benutzeroberfläche zu trennen und somit einfache Änderungen (oder einen vollständigen Austausch) der Benutzeroberfläche zu ermöglichen.
Ich verwende das folgende Prinzip: Die Ansicht kann vom ViewModel alles wissen und annehmen, was sie will, aber das ViewModel kann NICHTS über die Ansicht wissen.
WPF bietet ein schönes Bindungsmodell, mit dem Sie genau das erreichen können.

(Übrigens sind Vorlagen und Lambda-Ausdrücke nicht esoterisch, wenn sie richtig verwendet werden. Aber wenn Sie nicht möchten, verwenden Sie sie nicht.)

Dinge, bei denen Sie auf den Bildschirm starren und sich am Kopf kratzen.

Ja, ich kenne das Gefühl. Genau das, was ich fühlte, als ich MVVM zum ersten Mal sah. Aber sobald Sie den Dreh raus haben, wird es sich nicht mehr schlecht anfühlen.

Ich hatte eine About-Box, die gut funktionierte ...

Warum sollten Sie ein ViewModel hinter eine About-Box stellen? Das hat keinen Sinn.

Die meisten MVVM-Toolkits beschönigen Dialoge, und wenn sie diese ansprechen, handelt es sich lediglich um Warnfelder, für die keine benutzerdefinierten Schnittstellen oder Ansichtsmodelle erforderlich sind.

Ja, denn die Tatsache, dass sich ein UI-Element im selben Fenster oder in einem anderen Fenster befindet oder derzeit den Mars umkreist, ist für ViewModels kein Problem.
Trennung von Bedenken

BEARBEITEN:

Hier ist ein sehr schönes Video mit dem Titel Build your own MVVM Framework . Es ist sehenswert.


2
+1 für die letzten drei Wörter. Aber der Rest der Antwort ist auch gut. :)
Robert Harvey

12
+1 für den Rat zur Verwendung von Code-Behind. Es ist ein weit verbreitetes Missverständnis, dass es "schlecht" ist, Code-Behind in MVVM zu verwenden ... aber für rein UI-bezogene Dinge ist dies der richtige Weg.
Thomas Levesque

@ Thomas: Ja, ich könnte nicht mehr zustimmen. Ich habe mehrere Implementierungen gesehen, bei denen Leute den gesamten Code (auch im Zusammenhang mit der Benutzeroberfläche) in das ViewModel einfügen, weil (ihnen zufolge) "dort ist der Code". Es war ziemlich hackig.
Venemo

3
@Venemo, ich denke, Sie können eine Menge der Dinge, die Sie in den Code-Behind einfügen möchten, mithilfe von Techniken wie benutzerdefinierten Verhaltensweisen kapseln. Dies ist praktisch, wenn Sie wiederholt Klebercode schreiben. Im Allgemeinen denke ich jedoch, dass es besser ist, Code-Behind als Kleber zu verwenden, als umständliches XAML zusammen zu hacken. Meiner Meinung nach besteht das Hauptanliegen darin, sicherzustellen, dass der Code-Behind nichts enthält, das ausgereift genug ist, um Unit-Tests zu rechtfertigen. Alles, was ausreichend komplex ist, ist besser in ViewModel oder einer Erweiterungsklasse wie Behavior oder MarkupExtension gekapselt.
Dan Bryant

7
@ Thomas: Sie haben Recht, der größte Mythos über MVVM ist, dass der Zweck von MVVM darin besteht, Code-Behind loszuwerden. Der Zweck besteht darin, die Nicht-UI-OCDE aus dem dahinter liegenden Code herauszuholen. Das Einfügen von Nur-UI-Code in das ViewModel ist genauso schlecht wie das Einfügen von Problemdomänencode in den CodeBehind.
Jim Reineri

8

Es ist schwierig, dies zum Laufen zu bringen. Um vom MVVM-Muster zu profitieren, müssen Sie Code an mehreren Stellen in den Ebenen Ihrer Anwendung verteilen. Sie müssen auch esoterische Programmierkonstrukte wie Vorlagen und Lamba-Ausdrücke verwenden.

Für ein alltägliches modales Dialogfeld? Sie machen dort sicherlich etwas falsch - die MVVM-Implementierung muss nicht so komplex sein.

Wenn man bedenkt, dass Sie sowohl für MVVM als auch für WPF neu sind, verwenden Sie wahrscheinlich überall suboptimale Lösungen und erschweren die Dinge unnötig - zumindest habe ich dies getan, als ich zum ersten Mal bei WPF war. Stellen Sie sicher, dass das Problem wirklich MVVM und nicht Ihre Implementierung ist, bevor Sie aufgeben.

MVVM, MVC, Document-View usw. sind eine alte Musterfamilie. Es gibt Nachteile, aber keine schwerwiegenden Mängel der von Ihnen beschriebenen Art.


5

Ich bin mitten in einer recht komplexen MVVM-Entwicklung mit PRISM, daher musste ich mich bereits mit solchen Problemen auseinandersetzen.

Meine persönlichen Schlussfolgerungen:

MVVM gegen MVC / PopUps & Co.

  • MVVM ist wirklich ein großartiges Muster und ersetzt MVC in den meisten Fällen dank der leistungsstarken Datenbindung in WPF vollständig
  • Das Aufrufen Ihrer Serviceschicht direkt vom Präsentator aus ist in den meisten Fällen eine legitime Implementierung
  • Dank der Syntax {Binding Path = /} können auch recht komplexe Listen- / Detailszenarien von reinem MVVM implementiert werden
  • Wenn jedoch eine komplexe Koordination zwischen mehreren Ansichten implementiert werden muss, ist ein Controller obligatorisch
  • Ereignisse können verwendet werden; Das alte Muster, das das Speichern von IView- (oder AbstractObserver-) Instanzen im Controller impliziert, ist veraltet
  • Der Controller kann in jeden Presenter per IOC-Container injiziert werden
  • Der IEventAggregator-Dienst von Prism ist eine weitere mögliche Lösung, wenn der Controller nur für die Ereignisverteilung verwendet wird (in diesem Fall kann er den Controller vollständig ersetzen).
  • Wenn Ansichten dynamisch erstellt werden sollen, ist dies ein sehr gut geeigneter Job für den Controller (in Prisma wird dem Controller ein IRegionManager injiziert (IOC)).
  • Modale Dialogfelder sind in modernen Verbundanwendungen größtenteils veraltet, mit Ausnahme von wirklich blockierenden Vorgängen wie obligatorischen Bestätigungen. In diesen Fällen kann die modale Aktivierung als Dienst innerhalb des Controllers abstrahiert und von einer speziellen Klasse implementiert werden, die auch erweiterte Unit-Tests auf Präsentationsebene ermöglicht. Der Controller ruft beispielsweise IConfirmationService.RequestConfirmation („Sind Sie sicher“) auf, was zur Laufzeit eine modale Dialoganzeige auslöst und während des Komponententests leicht verspottet werden kann

5

Ich beschäftige mich mit dem Problem der Dialoge durch Betrug. Mein MainWindow implementiert eine IWindowServices-Schnittstelle, die alle anwendungsspezifischen Dialoge verfügbar macht. Meine anderen ViewModels können dann die Dienstschnittstelle importieren (ich verwende MEF, aber Sie können die Schnittstelle einfach manuell über Konstruktoren übergeben) und damit das Notwendige erreichen. Hier ist zum Beispiel, wie die Benutzeroberfläche für eine kleine Dienstprogrammanwendung von mir aussieht:

//Wrapper interface for dialog functionality to allow for mocking during tests
public interface IWindowServices
{
    bool ExecuteNewProject(NewProjectViewModel model);

    bool ExecuteImportSymbols(ImportSymbolsViewModel model);

    bool ExecuteOpenDialog(OpenFileDialog dialog);

    bool ExecuteSaveDialog(SaveFileDialog dialog);

    bool ExecuteWarningConfirmation(string text, string caption);

    void ExitApplication();
}

Dadurch werden alle Dialogausführungen an einem Ort gespeichert und können für Unit-Tests problemlos entfernt werden. Ich folge dem Muster, dass der Client des Dialogfelds das entsprechende ViewModel erstellen muss, das er dann nach Bedarf konfigurieren kann. Die Aufrufblöcke ausführen und anschließend kann der Client den Inhalt des ViewModel anzeigen, um die Dialogergebnisse anzuzeigen.

Ein "reineres" MVVM-Design mag für eine große Anwendung wichtig sein, bei der Sie eine sauberere Isolierung und eine komplexere Zusammensetzung benötigen. Für kleine bis mittelgroße Apps halte ich jedoch einen praktischen Ansatz mit geeigneten Diensten zum Freilegen der erforderlichen Haken für ausreichend .


Aber wie nennt man das? Wäre es nicht besser, den Dialog nur innerhalb der Funktionen der IWindowServices-Klasse zu erstellen, als ihn übergeben zu lassen? Auf diese Weise müsste die aufrufende Modellansicht nichts über die jeweilige Dialogimplementierung wissen.
Joel Rodgers

Die Schnittstelle wird in jede meiner ViewModel-Instanzen eingefügt, die Zugriff auf die Anwendungsdialoge benötigen. In den meisten Fällen übergebe ich das ViewModel für den Dialog, bin aber etwas faul geworden und habe die WPF OpenFileDialog und SaveFileDialog für die Dateidialogaufrufe verwendet. Mein primäres Ziel war die Isolation für Unit-Tests, daher ist dies für dieses Ziel ausreichend. Wenn Sie eine bessere Isolation wünschen, möchten Sie wahrscheinlich ein OpenFileViewModel und ein SaveFileViewModel erstellen, die die erforderlichen Eigenschaften für die Dialoge duplizieren.
Dan Bryant

Beachten Sie, dass dies definitiv kein reiner Ansatz ist, da das ViewModel, das die Dialoge verwendet, das spezifische ViewModel für jedes Dialogfeld kennt, das es öffnen möchte. Ich bin der Meinung, dass dies ziemlich sauber ist, aber Sie können jederzeit eine zusätzliche Isolationsschicht mit einer Klasse hinzufügen, die lediglich die für die Dialognutzung erforderlichen Parameter verfügbar macht und unnötige Sichtbarkeit der während des Bindens verwendeten ViewModel-Eigenschaften verbirgt. Für kleinere Anwendungen halte ich diese zusätzliche Isolierung für übertrieben.
Dan Bryant

5

Designmuster sollen Ihnen helfen, nicht behindern. Ein kleiner Teil davon, ein guter Entwickler zu sein, besteht darin, zu wissen, wann man "gegen die Regeln verstößt". Wenn MVVM für eine Aufgabe umständlich ist und Sie festgestellt haben, dass der zukünftige Wert die Mühe nicht wert ist, verwenden Sie das Muster nicht. Zum Beispiel, wie andere Poster kommentiert haben, warum sollten Sie den gesamten Aufwand durchlaufen, um eine einfache About-Box zu implementieren?

Designmuster sollten niemals dogmatisch befolgt werden.


2
Richtig. Eine einfache About-Box sollte einfach sein, aber was ist, wenn Sie Informationen wie Version, Lizenzierung, Ausführungsprozess, Firmenname usw. anzeigen müssen? Dies sind alles Informationen, die irgendwo leben. In Standardformularen können Sie einfach alle diese Informationen binden und damit fertig werden. MVVM sagt, Sie sollten ein Ansichtsmodell dafür erstellen, das redundant ist.
ATL_DEV

1

Da das Muster selbst MVVM ist großartig. Die mit NET 4.0-Datenbindungsunterstützung gelieferte Steuerungsbibliothek von WPF ist jedoch sehr begrenzt. Sie ist viel besser als WinForm, reicht jedoch nicht für bindbare MVVM aus. Ich würde sagen, dass die Leistung etwa 30% der für bindbare MVVM erforderlichen Leistung beträgt.
Bindbares MVVM: In dieser Benutzeroberfläche wird ViewModel nur mithilfe der Datenbindung mit View verbunden.
Das MVVM-Muster bezieht sich auf die Objektdarstellung des ViewState. Es beschreibt nicht, wie Sie die Synchronisierung zwischen View und ViewModel aufrechterhalten. In WPF ist es Datenbindung, aber es kann alles sein. Und tatsächlich können Sie MVVM-Muster in jedem UI-Toolkit verwenden, das Ereignisse \ Rückrufe unterstützt. Sie können es in reinem WinAPI in WinForms verwenden (ich habe es getan, und es funktioniert nicht viel mehr mit Ereignissen \ Rückrufen), und Sie können es sogar in Text verwenden Konsole, wie das Umschreiben von Norton Commander von DoS mithilfe des MVVM-Musters.

Kurzum: MVVM ist nicht sinnlos, es ist großartig. Die Steuerungsbibliothek von NET 4.0 WPF ist Papierkorb.

Hier ist das einfache Proof-of-Concept-ViewModel, mit dem Sie mit WPF keine Daten in reiner MVVM-Weise binden können.

public class PersonsViewModel
{
    public IList<Person> PersonList;
    public IList<ColumnDescription> TableColumns;
    public IList<Person> SelectedPersons;
    public Person ActivePerson;
    public ColumnDescription SortedColumn;
}

Sie können die DataGrid-Spaltenüberschriften von WPF nicht an Daten binden, Sie können ausgewählte Zeilen usw. nicht an Daten binden usw. Sie werden dies entweder auf einfache Weise im Code tun oder 200 Zeilen XAML-Hack-Code für diese 5 Zeilen des einfachsten ViewModel schreiben. Sie können sich nur vorstellen, wie es mit komplexen ViewModels schlimmer wird.
Die Antwort ist also einfach, es sei denn, Sie schreiben eine Hello World-Anwendung. Die Verwendung von bindbarem MVVM in WPF ist sinnlos. Sie werden die meiste Zeit damit verbringen, über Hack nachzudenken, um Ihr ViewModel zu binden. Die Datenbindung ist nett, aber seien Sie bereit, auf 70% der Zeit zurückzugreifen.


Sie können dies mit Konvertern an ein DataGrid binden.
Cameron MacFarland

@CameronMacFarland: Nicht alle, einige Eigenschaften sind schreibgeschützt und nicht bindend, andere existieren einfach nicht und es gibt nur Ereignisse, die Statusänderungen melden.
Alex Burtsev

Ich gebe zu, ich habe nicht viel Erfahrung mit dem WPF DataGrid. Ich neige dazu, es zu vermeiden, da es hässlich ist und nicht mehr zu WPF passt. Eine Kombination aus Konvertern und AttachedProperties zur Behandlung von Ereignissen sollte Ihnen jedoch das bieten, was Sie benötigen.
Cameron MacFarland

1
Alex, die Probleme, die Sie haben, sind mit dem Design des DataGrid, nicht mit MVVM. Es ist einfach falsch zu sagen, dass "Datenbindung nett ist, aber bereit ist, auf 70% der Zeit des Ereignisses zurückzugreifen." Ich habe einige objektiv große WPF-Anwendungen geschrieben, in denen es überhaupt keine Ereignishandler in der Benutzeroberfläche gibt - mit Ausnahme des Ereignishandlers, den das (Telerik-) Datenraster für die Initialisierung benötigt.
Robert Rossney

3
Ich denke, dass Sie vielleicht mehr Erfolg haben, wenn Sie nicht die Einstellung "Das ist schlecht entworfen und funktioniert nicht" einnehmen, sondern versuchen: "Warum funktioniert das für andere Menschen, aber nicht für mich?" Sie werden vielleicht feststellen, dass der Grund, warum Dinge schwierig sind, darin besteht, dass Sie noch nicht wissen, wie Sie sie tun sollen.
Robert Rossney

0

Nein, es ist nicht sinnlos, aber es ist schwierig, den Kopf herumzureißen, obwohl das Muster selbst lächerlich einfach ist. Es gibt Unmengen von Fehlinformationen und verschiedene Gruppen, die um den richtigen Weg kämpfen. Ich denke, mit WPF und Silverlight sollten Sie MVVM verwenden, sonst sind Sie überfordert, die „alte“ Win-Forms-Methode zu codieren und zu versuchen, Probleme in einem neuen Modell zu lösen, was Sie nur in Schwierigkeiten bringt. Dies ist in Silverlight eher der Fall, da alles asynchron sein muss (Hacks sind möglich, aber Sie sollten einfach eine andere Plattform auswählen).

Ich würde empfehlen, diesen Artikel zu lesen. Vereinfachen Sie die WPF-Baumansicht, indem Sie das ViewModel-Muster sorgfältig verwenden, um zu sehen, wie MVVM gut implementiert werden kann, und um es Ihnen zu ermöglichen, die Mentalität Ihrer Gewinnformen auf die neue Denkweise in MVVM umzustellen. Kurz gesagt, wenn Sie etwas erledigen möchten, wenden Sie die Logik zuerst auf das ViewModel an, nicht auf die Ansicht. Sie möchten einen Artikel auswählen? Ein Symbol ändern? Iterieren Sie nicht über UI-Elemente, sondern aktualisieren Sie einfach die Modelleigenschaften und lassen Sie die Datenbindung das Wesentliche tun.


-1

Ich habe das gleiche Problem bei vielen MVVM-Implementierungen gesehen, wenn es um (modale) Dialoge geht. Wenn ich mir die Teilnehmer des MVVM-Musters ansehe, habe ich das Gefühl, dass etwas fehlt, um eine kohärente Anwendung zu erstellen.

  • Die Ansicht enthält die spezifischen GUI-Steuerelemente und definiert das Erscheinungsbild der Benutzeroberfläche.
  • ViewModel repräsentiert den Status und das Verhalten der Präsentation.
  • Das Modell kann ein Geschäftsobjekt aus der Domänenschicht oder ein Dienst sein, der die erforderlichen Daten bereitstellt.

Aber es fehlt:

  • Wer erstellt die ViewModels?
  • Wer ist für den Anwendungsworkflow verantwortlich?
  • Wer vermittelt zwischen ViewModels, wenn sie miteinander kommunizieren müssen?

Mein Ansatz ist es, einen (Anwendungsfall-) Controller einzuführen, der für die fehlenden Punkte verantwortlich ist. Wie dies funktioniert, können Sie den Beispielanwendungen des WPF Application Framework (WAF) entnehmen.


Das von Josh Smith implementierte Mediator-Muster löste alle Kommunikationsprobleme meines View-Modells. Messenger.NotifyColleagues bot die Möglichkeit, völlig unabhängige Ansichtsmodelle zu erstellen, die auf globale Ereignisse reagieren konnten (sofern sie sich darum kümmerten), ohne dass zwei Ansichtsmodelle voneinander bekannt waren. Es hat unseren Speck schon ein paar Mal gerettet.
JasonD
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.