Commit 2f7b1ccd authored by Jens Ehlers's avatar Jens Ehlers
Browse files

Initial commit of the module documentation

parents
This diff is collapsed.
This diff is collapsed.
<p>Das Architekturmuster <i>Model-View-Controller (MVC)</i> ist der klassische Ansatz zur Entkopplung eines grafischen User Interface (GUI) vom Rest des Anwendungssystems. MVC spielt also nur dann eine Rolle, wenn die zu konstruierende Anwendung irgendein nicht weiter spezifiziertes GUI benötigt. Entstanden ist der Begriff bereits 1979 im Kontext der ersten objektorientierten Programmiersprache Smalltalk am <a href="https://de.wikipedia.org/wiki/Xerox_PARC">Xerox PARC</a>. MVC ist ursprünglich auf Desktop-Anwendungen ausgerichtet, aber nicht darauf festgelegt. Es ist zunächst zu betonen, dass die Komponenten <i>Model</i>, <i>View</i> und <i>Controller</i> nicht mit den Schichten Datenhaltung, Präsentation und Verarbeitungslogik der oben vorgestellten 3-Schichtenarchitektur gleichzusetzen sind. Insbesondere der <i>Controller</i> entspricht nicht der Verarbeitungsschicht, sondern bildet vielmehr gemeinsam mit der Datenhaltungsschicht die <i>Model</i>-Komponente im MVC-Muster – wie in der folgenden Abbildung verdeutlicht wird. Die Präsentationsschicht wird unterteilt in die grafische Darstellung der Ausgabe (<i>View</i>) und die Steuerung der Anwendereingaben (<i>Controller</i>).</p>
<img src="media/architecture_mvc.png" style="width:900px">
<label>Architekturmuster Model-View-Controller</label>
<span>Folgend werden die Interaktionen und die Abhängigkeiten der Komponenten im MVC-Muster beschrieben:</span>
<ul>
<li><b>Model</b>: Das <i>Model</i> benachrichtigt eine oder mehrere <i>Views</i>, die als Beobachter registriert sind, sobald sich sein Zustand ändert, damit die <i>Views</i> sich entsprechend aktualisieren können. Änderungen am <i>Model</i> werden durch Zugriffe eines <i>Controller</i> ausgelöst, der Eingabedaten zur Validierung, Verarbeitung und Speicherung an das <i>Model</i> weiterreicht.</li>
<li><b>View</b>: Die <i>View</i> präsentiert die Ausgabe des Systems für den Anwender. Heute wird der grundlegende strukturelle Aufbau einer <i>View</i> häufig deklarativ formuliert (z.B. in HTML oder XML). Eine <i>View</i> verarbeitet selbst keine Daten, sondern benachrichtigt bei Eingaben des Anwenders einen angebundenen <i>Controller</i>. Im MVC-Muster wird eine <i>View</i> von einem assoziierten <i>Model</i> über dessen Zustandsänderungen informiert. Die <i>View</i> greift dann direkt auf das <i>Model</i> zu, um die darzustellenden Daten abzuholen. Diese direkte Abhängigkeit einer <i>View</i> von einem <i>Model</i> wird in den neueren Varianten des MVC-Musters, nämlich <i>Model-View-Presenter (MVP)</i> und <i>Model-View-ViewModel (MVVM)</i> aufgehoben.</li>
<li><b>Controller</b>: Der <i>Controller</i> nimmt die Eingaben des Anwenders entgegen. Wenn der Anwender innerhalb der Anwendung z.B. zu einem anderen Dialog navigiert, steuert der <i>Controller</i> die Aktualisierung der <i>View</i>. Wenn der Anwender in der <i>View</i> dargestellte Daten manipuliert, ruft der <i>Controller</i> das assoziierte <i>Model</i> auf, um die Eingaben weiterzugeben.</li>
</ul>
<p>Wenn es zur Umsetzung des MVC-Musters kommt, existiert jedoch kein einheitliches Verständnis über die Zuständigkeiten der einzelnen Komponenten sowie deren Beziehungen und Sichtbarkeit untereinander. Wer nach "MVC" googelt, findet je nach Programmiersprache und Zielumgebung unterschiedliche Varianten. Die Ursachen für diese Variation liegen in der hohen Abstraktion des Musters. Wenn es zur Implementierung kommt, muss das Muster in unterschiedliche Protokolle und Frameworks eingebettet werden. Beispielsweise unterscheidet sich diesbezüglich die Implementierung einer Web-Anwendung mit PHP stark von der Implementierung einer Desktop-Anwendung mit Java oder einer mobilen Anwendung für iOS mit Swift.</p>
<p>Es haben sich dabei insbesondere zwei Varianten herausgebildet, die das Zusammenspiel der Komponenten konkretisieren und sich im Detail vom ursprünglichen MVC unterscheiden. Die grundlegende Idee von MVC bleibt bei beiden Varianten weiterhin gültig: Die fachlichen Teile einer Anwendung, die unabhängig von einem konkreten UI-Framework sind, sollen von der konkreten Ein- und Ausgabe des UI entkoppelt werden.</p>
<ul>
<li><b>Model-View-Presenter (MVP)</b>: MVP wurde in 2004 in einem <a href="https://martinfowler.com/eaaDev/uiArchs.html">Artikel vom Martin Fowler</a> vorgeschlagen und darin von MVC abgegrenzt. Im MVP-Muster läuft sämtliche Kommunikation zwischen <i>View</i> und <i>Model</i> über einen zwischengeschalteten <i>Presenter</i>, damit keine Abhängigkeit zwischen <i>View</i> und <i>Model</i> existiert. Der <i>Presenter</i> arbeitet nur auf Interfaces von <i>View</i> und <i>Model</i>, damit diese in Komponententests (engl. <i><a href="https://en.wikipedia.org/wiki/Unit_testing">Unit Testing</a></i>) durch <a href="https://de.wikipedia.org/wiki/Mock-Objekt">Mock-Objekte</a> ausgetauscht und unabhängig voneinander getestet werden können. Martin Fowler hat das MVP-Muster später noch in <i><a href="https://martinfowler.com/eaaDev/SupervisingPresenter.html">Supervising Controller</a></i> und <i><a href="https://martinfowler.com/eaaDev/PassiveScreen.html">Passive View</a></i> untergliedert. In aktuellen Frameworks ist MVP meist begrifflich durch MVVM ersetzt worden.</li>
<li><b>Model-View-ViewModel (MVVM)</b>: MVVM wurde als Begriff in 2005 durch das Framework <a href="https://docs.microsoft.com/de-de/dotnet/framework/wpf/">Windows Presentation Foundation (WPF)</a> innerhalb von Microsoft .NET geprägt. Es ist insbesondere auf UIs ausgerichtet, in denen Teile der <i>View</i> deklarativ in HTML oder XML beschrieben werden. Daher haben auch diverse JavaScript-Frameworks wie <a href="https://angular.io/">Angular</a>, <a href="https://vuejs.org/">Vue.js</a> und zuerst <a href="https://knockoutjs.com/">Knockout</a> das MVVM-Muster übernommen. Wie bei MVC und MVP werden der Zustand und die Steuerung des Verhaltens der <i>View</i> von ihrer grafischen Darstellung getrennt und ins sogenannte <i>ViewModel</i> ausgelagert. Das <i>ViewModel</i> soll wie der <i>Presenter</i> in MVP frei von Abhängigkeiten zu Klassen des konkreten UI-Frameworks sein. <i>View</i> und <i>ViewModel</i> werden per <i>Two-Way-Binding</i> (bidirektionaler Beobachter) aneinander gebunden, so dass ihre Zustände nicht voneinander abweichen können. Wie genau das in Java implementiert werden kann, folgt später.</li>
</ul>
<img src="media/architecture_mvp_mvvm.png" style="width:1100px">
<label>Architekturmuster Model-View-Presenter und Model-View-ViewModel</label>
\ No newline at end of file
<p>Die vorgestellten Entwurfsmuster haben Lösungsansätze für den Feinentwurf aufgezeigt, d.h. sie betreffen konkrete Entwurfsentscheidungen für die objektorientierte Programmierung im Inneren einer Komponente. Architekturmuster haben im Unterschied zu den Entwurfsmustern den Anspruch, die grundlegende Struktur und Interaktion zwischen den Komponenten eines Anwendungssystems zu bestimmen. Demzufolge treffen wir Architekturmuster im Entwicklungsprozess in der vorausgehenden Phase des Grobentwurfs an. Grundsätzlich betrachten Architekturmuster auf einer abstrakten Ebene das Zusammenspiel von Komponenten, ohne dabei vorauszusetzen, wie diese Komponenten konkret implementiert werden, z.B. in welcher Programmiersprache. Auf dieser Ebene bleiben Architekturmuster per Definition abstrakt. Das gemeinsame Ziel sämtlicher Architekturmuster ist es, entsprechend dem Prinzip der <a href="#unit-architecture" class="navigate">Modularisierung</a> Abhängigkeiten zwischen den Komponenten bewusst so zu gestalten, dass eine möglichst geringe Kopplung erreicht wird.</p>
<p>Als Ausgangspunkt wird meistens ein sogenanntes "monolithisches System" angenommen, für das scheinbar keine durchdachte Struktur vorliegt und das uns dem Begriff Monolith entsprechend als ein einzelner großer Baustein erscheint. Ein monolithisches System ist typischerweise bereits seit vielen Jahren im produktiven Einsatz. In der Vergangenheit wurde es kontinuierlich erweitert, bis es seine heutige Komplexität erreicht hat. Es läuft überraschend stabil, aber es ist nicht mehr (oder kaum noch) wartbar, erweiterbar und skalierbar. Unter den Entwicklern wird es als sogenanntes <i>Legacy-System</i> bezeichnet, dessen präskriptive Architektur längst erodiert ist. Wenn wir genau in den Monolithen hineinschauen, werden wir nichtsdestotrotz irgendeine Struktur erkennen, aber die Abhängigkeiten der Module oder Klassen sind so stark miteinander verwoben, dass sich keine Komponenten herauslösen lassen. Das <i><a href="https://de.wikipedia.org/wiki/Refactoring">Refactoring</a></i> des Systems erscheint daher dringend notwendig.</p>
<img src="media/uluru.png" style="width:450px">
<label>Uluru. Monolith oder nicht? Im allgemeinen Sprachgebrauch: ja. Aus geomorphologischer Perspektive: eher nein, da sich verschiedene, teilweise verwachsene Schichten unterscheiden lassen.</label>
<span>Zusammenfassend ergeben sich folgende Nachteile eines monolithische Systems <a href="#cite-KB18">[KB18]</a>:</span>
<ul>
<li>Die fehlende Modularisierung führt dazu, dass die Komplexität nicht aufgeteilt und reduziert werden kann.</li>
<li>Die korrektive Wartung gestaltet sich sehr aufwändig, da eine Änderung an einer bestimmten Stelle im Code eine kaskadierende Folge von weiteren Änderungen an anderen Stellen verursacht. Dadurch gelingt auch eine schrittweise perfektive Wartung nicht, insbesondere die Migration einzelner Aspekte auf eine neue technologische Basis (z.B. andere Laufzeitumgebung, andere Programmiersprache).</li>
<li>Die Wiederverwendung einzelner Komponenten ist ausgeschlossen.</li>
<li>Skalierbarkeit im Kontext von Cloud Platformen ist stark eingeschränkt.</li>
</ul>
<p>Die Schichtenarchitektur (auch Schichtenmodell) ist ein einfaches Muster zur Strukturierung eines Softwaresystems, bei dem die einzelnen Aspekte (z.B. Komponenten oder Klassen) eines Systems jeweils unterschiedlichen Schichten (engl. <i>Tier</i> oder <i>Layer</i>) zugeordnet werden. Es gilt das grundsätzliche Prinzip, dass aus höheren Schichten auf tiefere Schichten zugegriffen werden darf, aber nicht von unten nach oben. Die Abhängigkeiten sind also von oben nach unten gerichtet. Bei einer strikten (oder geschlossenen) Schichtenarchitektur sind ausschließlich Zugriffe auf die nächsttiefere Schicht zulässig, d.h. es dürfen keine Schichten übersprungen werden. Im Gegensatz dazu darf in einer nicht-strikten (oder offenen) Schichtenarchitektur eine beliebige tiefere Schicht aufgerufen werden. Der Kontrollfluss und die Daten müssen dadurch nicht durch zwischenliegende Schichten durchgeschleust werden, was im verteilten System zu einer verbesserten Performance führen kann. Als Nachteil steht diesem Vorteil gegenüber, dass sich der Grad der Kopplung im System erhöht, wenn die tieferen Schichten potentiell von diversen höheren Schichten eingebunden werden und nicht nur von der nächsthöheren Schicht.</p>
<a href="https://de.wikipedia.org/wiki/Schichtenarchitektur"><img src="media/architecture_schichtenarchitektur.png" style="width:450px"></a>
<label>Prinzip der Schichtenarchitektur</label>
<span>Die bekannteste Schichtenarchitektur besteht aus 3 Schichten:</span>
<ul>
<li><b>Präsentation</b>: Über diese Schicht interagiert der Anwender mit dem System. Sie beinhaltet das <i><a href="https://en.wikipedia.org/wiki/User_interface">User Interface (UI)</a></i>, dessen Bedienbarkeit den Entwurf dieser Schicht maßgeblich bestimmt. Der Anwender bekommt die Daten in diesem <i>Frontend</i> aufbereitet dargestellt und sendet seine Eingaben über diese Schicht an die unterliegenden Schichten.</li>
<li><b>Verarbeitung der Logik</b>: In dieser Schicht findet die Verarbeitung der Daten gemäß der Anwendungslogik statt, die sich wiederum aus den funktionalen Anforderungen an das System ergibt. Die Eingaben der Anwender werden hier validiert. Ressourcen-intensive Verarbeitungsschritte auf Massendaten sollten parallelisiert ausgeführt werden.</li>
<li><b>Datenhaltung</b>: Diese Schicht speichert die Daten dauerhaft und stellt eine Schnittstelle bereit, um die Daten abzufragen und zu manipulieren. Dazu wird i.d.R. ein Datenbanksystem als sogenanntes <i>Backend</i> verwendet.</li>
</ul>
<p>Wie die folgende Abbildung zeigt, können diese 3 Schichten in einem verteilten System, das aus Client und Server besteht, je nach Anwendung unterschiedlich zugeordnet werden. Ein <i>Thin Client</i> ist ausschließlich für die Präsentation der Daten zuständig. Ein <i>Fat Client</i> dagegen übernimmt neben der Präsentation auch die eigentliche Verarbeitung der Daten, d.h. insbesondere Validierung und Transformation, und u.U. sogar die Datenhaltung.
<img src="media/architecture_3tier.png" style="width:750px">
<label>Verschiedene Varianten einer 3-Schichtenarchitektur im verteilten System</label>
<p>Die unterschiedlichen Varianten der 3-Schichtenarchitektur sollen folgend kurz erläutert werden, wobei erst in späteren Kapiteln auf konkrete Frameworks zur Implementierung eingegangen wird. Wir stellen uns dazu eine Web-Anwendung vor, deren Client-Komponenten in JavaScript implementiert sind und innerhalb eines Web-Browsers des Anwenders ausgeführt werden. Die Server-Komponenten seien in Java implementiert. Grundsätzlich sind auch andere Anwendungstypen, z.B. eine Desktop-Anwendung oder eine mobile Anwendung, und andere Programmiersprachen vorstellbar.</p>
<style>
.ol_A_bold { counter-reset:item; }
.ol_A_bold>li { list-style-type:none; counter-increment:item; text-indent: -25px; }
.ol_A_bold>li:before { padding-right: 15px; font-weight:bold; content:counter(item, upper-alpha); list-style-type: lower-alpha; }
</style>
<ol type="A" class="ol_A_bold">
<li>In einer Web-Anwendung finden Verarbeitung und Datenhaltung i.d.R. auf dem Server statt. Viele Frameworks sind darauf ausgerichtet, eine serverseitige <i><a href="https://en.wikipedia.org/wiki/Web_template_system">Template-Engine</a></i> einzusetzen, um HTML-Templates mit Daten aus den verbundenen Datenbanken zu befüllen und HTML-Dokumente an den Client auszuliefern. Im Java-Umfeld zählen zu den aktuell verbreiteten Template-Engines u.a. <a href="https://mustache.github.io/">Mustache</a>, <a href="https://www.thymeleaf.org/">Thymeleaf</a>, <a href="https://freemarker.apache.org/">FreeMarker</a> sowie die mittlerweile veralteten <a href="https://jcp.org/en/jsr/detail?id=245">JavaServer Pages (JSP)</a>. Wenn eine serverseitige Template-Engine eingesetzt wird, erstreckt sich die Präsentationsschicht über Client und Server.</li>
<li>Alternativ dazu kann eine Web-Anwendung so gebaut sein, dass der Server anstatt ganze HTML-Dokumente nur strukturierte Daten im JSON- oder XML-Format an den Client ausliefert. Diese Daten werden durch den Client empfangen, verarbeitet und in das <i><a href="https://en.wikipedia.org/wiki/Document_Object_Model">Document Object Model (DOM)</a></i> des Browsers dynamisch eingearbeitet. In diesem Fall sind Präsentations- und Verarbeitungsschicht über die Schnittstelle zwischen Client und Server klar voneinander getrennt.</li>
<li>Um die Bedienbarkeit für den Anwender durch schnellere Rückmeldungen zu verbessern, bietet es sich an, einfache Validierungen der Anwendereingaben bereits im Client durchzuführen. Andernfalls wäre für jede Validierung eine Anfrage an den Server erforderlich, deren Antwortzeit von der verfügbaren Datenübertragungsrate abhängig ist. Auf diese Weise wird ein Teil der Anwendungslogik in den Client verschoben, so dass die Verarbeitungsschicht sich auf Client und Server aufteilt.</li>
<li>Es kann auch sämtliche Anwendungslogik einer Web-Anwendung im Client implementiert werden. Lediglich die Datenhaltung wird noch auf einem Server bereitgestellt, um die Daten zwischen mehreren Clients über eine zentrale Stelle austauschen zu können. Jeder Client benötigt in dieser Architektur einen persönlichen Zugang zur Datenhaltungsschicht. Ein entsprechender Dienst, der sich auf reine Datenhaltung und vorausgehende Authentifizierung beschränkt, ist z.B. <a href="https://firebase.google.com/">Google Firebase</a>.</li>
<li>Wenn die Daten ausschließlich zentral auf einem Server gehalten werden, kann die Anwendung nicht ohne aktive Verbindung zum Server zwecks Datenaustausch genutzt werden. Um dem Anwender zu ermöglichen, die Anwendung auch offline zu nutzen, werden Daten auf dem Client zwischengespeichert und bei aktiver Verbindung zum Server periodisch synchronisiert. Bei dieser Architektur erstreckt sich die Datenhaltungsschicht also auf Client und Server gleichermaßen. Die Herausforderung liegt in der Synchronisation der Daten, wenn diese sowohl auf dem Client als auch auf dem Server (indirekt von einem anderen Client) verändert worden sind. Diese Architektur wird häufig für mobile Anwendungen eingesetzt, da diese hinsichtlich der Verbindung zum Server zwischen online und offline schwanken.</li>
</ol>
\ No newline at end of file
<h4>Futures</h4>
<p>Ein <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Runnable.html"><code>Runnable</code></a> hat die Einschränkung, dass seine Methode <code>run</code> kein Ergebnis zurückgibt. Falls in einem nebenläufigen Thread ein Ergebnis berechnet werden soll, bietet sich dafür das funktionale Interface <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Callable.html"><code>Callable&lt;V></code></a> an, das über die Methode <code>call</code> ein Ergebnis vom Typ <code>V</code> zurückgibt.
<code>Callables</code> werden häufig im Zusammenhang mit <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Future.html"><code>Futures</code></a> verwendet. Sowohl <code>Callables</code> als auch <code>Futures</code> wurden in Java 5 (2004) eingeführt. Ein <code>Future&lt;V></code> ist ein Platzhalter für ein Ergebnis vom Typ <code>V</code>, das noch nicht vollständig berechnet worden ist. Es verspricht, das Ergebnis einer nebenläufigen Berechnung zu speichern, sobald diese abgeschlossen ist (engl. <i>Promise</i>). Die Berechnung findet typischerweise in einem <code>Callable</code> statt, das durch einen <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ExecutorService.html"><code>ExecutorService</code></a> ausgeführt wird. Der Aufrufer der nebenläufigen Berechnung kann auf dem <code>Future</code> mittels der Methode <code>isDone</code> prüfen, ob die Berechnung bereits abgeschlossen ist, und mittels der Methode <code>get</code> blockierend auf das fertige Ergebnis warten. Das folgende Code-Beispiel illustriert das Warten auf ein Future in Zeile 20.</p>
<pre><code class="language-java line-numbers">import java.util.concurrent.Callable;
import java.util.concurrent.Future;
// ...
class MyFuture {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// task being executed concurrently
Callable&lt;Integer> callable = () -> {
Thread.sleep(1000);
return 1;
};
ExecutorService exec = Executors.newSingleThreadExecutor();
Future&lt;Integer> f = exec.submit(callable); // start execution of concurrent task
System.out.println(f.isDone()); // prints 'false'
System.out.println(f.get()); // waits until the task is done, then prints '1'
exec.shutdown();
}
}</code></pre>
<p>In Java 8 (2014) wurden ergänzend <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html"><code>CompletableFutures</code></a> eingeführt. Ein <code>CompletableFuture</code> ist eine Weiterentwicklung eines regulären <code>Future</code> – inspiriert durch <a href="https://google.github.io/guava/releases/23.0/api/docs/com/google/common/util/concurrent/ListenableFuture.html"><code>ListenableFutures</code></a> in Google's <a href="https://github.com/google/guava">Guava</a>-Bibliothek.
<code>CompletableFutures</code> sollen es ermöglichen, mehrere voneinander abhängige, nebenläufige Berechnungen in einer Verarbeitungskette hintereinander zu schalten. Der Aufrufer kann z.B. formulieren, dass in einem nebenläufigen Thread zunächst Aufgabe A bearbeitet werden soll und anschließend Aufgabe B, die auf das Ergebnis von Aufgabe A als Input angewiesen ist. Es folgt ein einfaches Code-Beispiel zur Veranschaulichung:</p>
<pre><code class="language-java line-numbers">import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.function.Function;
// ...
class MyCompletableFuture {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// task A being executed concurrently
Supplier&lt;Integer> supplier = () -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) { }
return 1;
};
// task B being executed concurrently, dependent on the result of task A
Function&lt;Integer, Integer> plusOne = (x) -> x + 1;
ExecutorService exec = Executors.newSingleThreadExecutor();
CompletableFuture&lt;Integer> f = CompletableFuture.supplyAsync(supplier, exec); // start execution of task A
System.out.println(f.isDone()); // prints 'false'
CompletableFuture&lt;Integer> f2 = f.thenApplyAsync(plusOne, exec); // start task B when task A is done, i.e. chaining of tasks
System.out.println(f2.get()); // waits until both tasks are done, then prints '2'
exec.shutdown();
}
}</code></pre>
<h4>Parallele Streams</h4>
<p>Ein <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html"><code>Stream</code></a> wird in Java per Default sequentiell in einem Thread ausgeführt. Streams stehen seit Java 8 (2014) zur Verfügung. Im folgenden Code-Beispiel wird eine Liste von 64 Zahlen in einem Stream verarbeitet, um die größte der Zahlen zu ermitteln (Zeile 3).</p>
<pre><code class="language-java line-numbers">List&lt;Integer> numbers = IntStream.rangeClosed(1, 64).boxed().collect(Collectors.toList());
Collections.shuffle(numbers);
int max = numbers.stream().reduce(Integer.MIN_VALUE, Math::max);</code></pre>
<p>Durch den Aufruf der Methode <code>parallel</code> kann der Stream parallelisiert werden, so dass seine Verarbeitung auf mehrere nebenläufige Threads aufgeteilt wird. Per Default werden so viele Threads genutzt, wie Prozessorkerne gemäß <code>Runtime.getRuntime().availableProcessors()</code> zur Verfügung stehen. Für den Entwickler ist die Parallelisierung von Streams damit sehr einfach gestaltet.</p>
<pre><code class="language-java line-numbers">int max = numbers.stream().parallel().reduce(Integer.MIN_VALUE, Math::max);</code></pre>
<p>Die grundsätzliche Erwartung ist, dass durch die Nutzung mehrerer parallel arbeitender Threads der parallelisierte Stream schneller fertig wird als der sequentielle Stream. Das lässt sich mit einem ausreichend großen Berechnungsaufwand für den Stream und entsprechender Multicore-Hardware auch demonstrieren (<a href="/blob/master/concurrency/src/main/java/streams/ParallelStreamProcessingTime.java" class="repo-link">siehe hier</a>).</p>
<p>Grundsätzlich ist die Annahme, dass ein sequentieller und ein paralleler Stream stets zum gleichen Ergebnis führen. Es gibt aber Ausnahmefälle, die entsprechend dokumentiert sind, z.B. die häufig genutzte Methode <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html#forEach(java.util.function.Consumer)"><code>forEach</code></a>, mit der über die Elemente eines Streams iteriert werden kann. Die Methode <code>forEach</code> ist wie folgt dokumentiert: <i>"The behavior of this operation is explicitly nondeterministic. For parallel stream pipelines, this operation does not guarantee to respect the encounter order of the stream, as doing so would sacrifice the benefit of parallelism. For any given element, the action may be performed at whatever time and in whatever thread the library chooses. If the action accesses shared state, it is responsible for providing the required synchronization."</i> Die folgende Methode <code>log</code> gibt die Elemente eines parallelen Streams daher nicht unbedingt in der richtigen Reihenfolge aus.</p>
<pre><code class="language-java line-numbers">void log(Stream&lt;String> stream) { stream.forEach(System.out::println); }</code></pre>
<p>Über die Methode <code>isParallel</code> kann für einen Stream ermittelt werden, ob es sich um einen sequentiellen oder parallelen Stream handelt. Eine Fallunterscheidung in der Methode <code>log</code> sorgt dafür, dass sie sich deterministisch verhält.</p>
<pre><code class="language-java line-numbers">void log(Stream&lt;String> stream) {
if (!stream.isParallel()) stream.forEach(System.out::println);
else stream.forEachOrdered(System.out::println);
}</code></pre>
<p>Tatsächlich könnte auch in beiden Fällen die Methode <code>forEachOrdered</code> genutzt werden, was die obige Fallunterscheidung wieder überflüssig macht.</p>
<p>Die interne Implementierung paralleler Streams basiert auf dem Fork-Join-Framework, das in Java 7 (2011) eingeführt worden ist.
Das Fork-Join-Framework agiert nach dem Prinzip des Divide & Conquer-Algorithmus. Die grundlegende Idee besteht dabei darin, dass ein Ausgangsproblem so in Teilprobleme zerlegt wird, dass die später zusammengeführten Ergebnisse der Teilprobleme das Ausgangsproblem lösen. Ein <i>Fork</i> ist dabei das Zerlegen in Teilprobleme und ein <i>Join</i> das Zusammenfassen der Teilergebnisse zum Gesamtergebnis des Ausgangsproblems. Der Algorithmus lässt sich rekursiv über mehrere Hierarchiestufen anwenden, d.h. dass die Teilprobleme der ersten Stufen sukzessive in noch kleinere, eigene Teilprobleme zerlegt werden können. Die folgende Abbildung visualisiert das Prinzip des Divide & Conquer-Algorithmus anhand des Ausgangsproblems die größte Zahl im Indexbereich 0..63 eines Arrays oder einer Liste zu finden.</p>
<img src="media/concurrency_forkjoin.png" style="width:900px">
<label>Divide & Conquer-Prinzip des Fork-Join-Framework</label>
<p>Beim ersten Fork wird der Indexbereich in die beiden Teilbereiche 0..31 und 32..63 aufgeteilt. Damit sind 2 Teilaufgaben entstanden: (a) Finden der größten Zahl im Indexbereich 0..31 sowie (b) finden der größten Zahl im Indexbereich 32..63.
Diese Teilaufgaben können in der Fork-Phase rekursiv in noch kleinere Teilaufgaben zerlegt werden: Finden der größten Zahl in den Indexbereichen 0..15, 16..31, 32..47 und 48..63. Entscheidend ist, dass jede Teilaufgabe unabhängig von den anderen in einem nebenläufigen Thread gelöst werden kann. Die Methode <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html#reduce(T,java.util.function.BinaryOperator)"><code>reduce</code></a> (s. oben) führt dazu die Elemente eines Streams paarweise zusammen. In diesem Fall wird der Methode <code>reduce</code> dazu die Methode <code>Math.max</code> als <code>BinaryOperator</code> übergeben, um jeweils die größere von zwei Zahlen zurückzugeben. Angenommen es liegt eine Quadcore-Hardware zugrunde, würden in der Fork-Phase 4 Threads genutzt, die unabhängig voneinander die Teilaufgaben bearbeiten. In der anschließenden Join-Phase werden die Ergebnisse der Teilaufgaben aggregiert, indem sie paarweise als Argumente dem <code>BinaryOperator</code> (hier <code>Math:max</code>) übergeben werden, der in der Methode <code>reduce</code> ausgeführt wird.</p>
<p>Wenn N die Anzahl der verfügbaren Prozessorkerne ist, wird das Ausgangsproblem tatsächlich nicht nur in N Teilaufgaben zerlegt sondern in etwa 4·N. Die Anzahl der erzeugten Teilaufgaben entspricht der Zweierpotenz, die größer als 4·(N-1) ist. Auf einer Quadcore-Hardware werden also 16 Teilaufgaben erzeugt, die nacheinander von 4 Threads bearbeitet werden. Dieses Verhalten lässt sich auch beobachten (<a href="/blob/master/concurrency/src/main/java/streams/ParallelStreamCountForks.java" class="repo-link">siehe hier</a>). Der per Default genutzte Thread-Pool namens <i>ForkJoinPool.common</i> lässt sich bei Bedarf verändern oder austauschen. Über die folgende Anweisung wird z.B. die Anzahl der Threads in diesem Thread-Pool auf 8 erhöht.</p>
<pre><code class="language-java line-numbers">System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "8");</code></pre>
<p>Das Fork-Join-Framework unterscheidet also zwischen der Anzahl an Teilaufgaben (= <i>Tasks</i>), die abgearbeitet werden müssen, und der Anzahl an Threads, die dazu zur Verfügung stehen. Das folgende UML-Klassendiagramm visualisiert die Beziehungen zwischen den beteiligten Interfaces und Klassen.</p>
<img src="media/concurrency_forkjoin_classes.png" style="width:540px">
<label>Grundlegende Klassen des Fork-Join-Framework</label>
<ul>
<li><b>Tasks</b>: <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ForkJoinTask.html"><code>ForkJoinTasks</code></a> sind abgeschlossene Arbeitseinheiten/Teilprobleme, die nicht auf andere Tasks warten müssen. Sie sich nicht an einen bestimmten Thread zur Ausführung gebunden und implementieren das <code>Future</code>-Interface, versprechen also i.d.R. ein Ergebnis bestimmten Typs (= <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/RecursiveTask.html"><code>RecursiveTask</code></a>). Tasks ohne Ergebnis heißen <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/RecursiveAction.html"><code>RecursiveAction</code></a>.</li>
<li><b>Threads</b>: In einem <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ForkJoinPool.html"><code>ForkJoinPool</code></a> werden spezielle Threads, nämlich <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ForkJoinWorkerThread.html"><code>ForkJoinWorkerThreads</code></a> verwaltet, die <code>ForkJoinTasks</code> ausführen. Ein <code>ForkJoinPool</code> unterscheidet sich anderen <code>ExecutorServices</code> durch sogenanntes <i>Work Stealing</i>, d.h. alle Threads im Pool versuchen aktiv, in den Pool gesendete Tasks zu ergreifen und auszuführen. Dies ermöglicht eine effiziente Verarbeitung, insbesondere wenn die Tasks ihrerseits neue Tasks erzeugen – dadurch dass ein <code>ForkJoinTask</code> typischerweise eine Teilaufgabe rekursiv in kleinere Teilaufgaben zerlegt und diese wieder an den Pool sendet.</li>
</ul>
<p>Das Fork-Join-Framework kann auch manuell ohne parallele Streams eingesetzt werden. Das folgende Code-Beispiel demonstriert wie die Berechnung einer bestimmmten Fibonacchi-Zahl über eine <code>RecursiveTask</code> parallelisiert werden kann. Das Beispiel ist aus der Dokumentation der <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/RecursiveTask.html"><code>RecursiveTask</code></a>-Klasse übernommen. Die Implementierung ist nicht effizient, da die gebildeten Teilaufgaben zu klein sind. Stattdessen ist – wie bei allen Fork-Join-Anwendungen – eine sinnvolle, minimale Granularität zu wählen, ab der eine Aufgabe sequentiell gelöst wird, anstatt sie zu unterteilen. Die Methode <code>fork</code> in Zeile 9 erzeugt eine Teilaufgabe, die nebenläufig über den <code>ForkJoinPool</code> abgearbeitet wird. Über die Methode <code>join</code> in Zeile 11 wird im aufrufenden Thread auf das Ergebnis dieser Teilaufgabe gewartet.</p>
<pre><code class="language-java line-numbers">class FibonacciTask extends RecursiveTask&lt;Integer> {
int n;
FibonacciTask(int n) { this.n = n; }
@Override
protected Integer compute() {
if (n &lt;= 1) return n;
FibonacciTask f1 = new FibonacciTask(n - 1);
f1.fork(); // fork: execute this task concurrently in the same pool the current task is running in
FibonacciTask f2 = new FibonacciTask(n - 2);
return f2.compute() + f1.join(); // join: wait for the concurrent task to be done
}
public static void main(String[] args) {
System.out.println(new FibonacciTask(7).compute()); // 7-th Fibonacchi number is 13
}
}</code></pre>
<p>Eine beliebte Übungsaufgabe ist es, die grundlegenden Sortieralgorithmen <a href="https://de.wikipedia.org/wiki/Quicksort">QuickSort</a> und <a href="https://de.wikipedia.org/wiki/Mergesort">MergeSort</a>, die ebenfalls auf dem Divide & Conquer-Prinzip basieren, über das Fork-Join-Framework zu parallelisieren. Probiere es aus! Vergleiche die Laufzeiten einer sequentiellen und einer parallelisierten Implementierung der Sortieralgorithmen! Hilfreich ist hierbei die Methode <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ForkJoinTask.html#invokeAll(java.util.Collection)"><code>invokeAll</code></a>, die auf einem <code>ForkJoinTask</code> ausgeführt werden kann, um mit einer Anweisung eine Menge von Teilaufgaben zu erzeugen (<code>fork</code>) und direkt auf alle Teilergebnisse zu warten (<code>join</code>).</p>
<p>In 2004 prägte eine wissenschaftliche Veröffentlichung von Google Research maßgeblich den Begriff <a href="https://ai.google/research/pubs/pub62">MapReduce</a>, dem wir insbesondere im Umfeld der Massendatenverarbeitung in verteilten Systemen (z.B. mittels <a href="https://hadoop.apache.org/">Hadoop</a> und <a href="https://spark.apache.org/">Spark</a>) und allgemein in vielen APIs zur funktionalen Programmierung begegnen. MapReduce beschreibt ein Programmiermodell, das ebenfalls auf dem Divide & Conquer-Prinzip aufbaut, und daher dem Fork-Join-Framework sehr ähnlich ist, wobei die <i>Map</i>-Phase der Fork-Phase entspricht und die <i>Reduce</i>-Phase der Join-Phase. Die Ausführung der parallelen Threads ist bei MapReduce-Anwendungen aber i.d.R. nicht auf eine Maschine beschränkt, sondern verteilt sich auf ein ganzes Cluster von Maschinen.</p>
<h4>Reaktive Streams</h4>
<p>In Java sind reaktive Streams seit Java 9 (2017) durch die sogenannte <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html">Flow API</a> standardisiert. Es werden hier insbesondere die Interfaces <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.Publisher.html"><code>Publisher</code></a> und <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.Subscriber.html"><code>Subscriber</code></a> bereitgestellt, über die eine asynchrone Stream-Verarbeitung mit nicht-blockierenden Gegendruck (engl. <i>Backpressure</i>) realisiert werden kann. Eine Datenquelle (<i>Publisher</i>) informiert seine Beobachter (<i>Subscriber</i>) über neue Nachrichten bzw. Zustandsänderungen, wobei sämtliche Publisher und Subscriber in nebenläufigen Threads ausgeführt werden. Der Gegendruck entsteht für einen Subscriber dadurch, dass er auch bei hoher Frequenz neu eingehender Nachrichten vom Publisher, diese verarbeiten können muss. Der max. mögliche Durchsatz zur Nachrichtenverarbeitung auf der Seite des Subscriber muss bestimmt werden und schränkt ggf. ein, wie viele eingehende Nachrichten aus den beobachteten Datenquellen akzeptiert werden können. Ein Code-Beispiel für die Verwendung des <code>Publisher</code>- und <code>Subscriber</code>-Interface ist bereits am Ende des Kapitels zum <a href="#unit-observer" class="navigate">Entwurfsmuster Beobachter</a> dargestellt worden. Bemerkenswert ist, dass die Flow API einen Konsens darstellt, der im Rahmen der Initiative <a href="http://www.reactive-streams.org/">Reactive Streams for the JVM</a> entstanden ist. Die Interfaces der Flow API (<code>Publisher</code>, <code>Subscriber</code>, <code>Subscription</code> und <code>Processor</code>) werden von verschiedenen beliebten Stream-Bibliotheken wie <a href="https://github.com/ReactiveX/RxJava/wiki/Reactive-Streams">RxJava</a>, <a href="https://akka.io/docs/">Akka</a> und <a href="http://vertx.io/">Vert.x</a> implementiert. Dadurch können Datenströme, die auf Basis dieser Bibliotheken entwickelt worden sind, unter Aufrechterhaltung des Gegendrucks miteinander verbunden werden.</p>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
/*=-====Bootstrapthemes.co btco-hover-menu=====*/
.navbar-light .navbar-nav .nav-link {
color: rgb(64, 64, 64);
}
.btco-hover-menu a , .navbar > li > a {
text-transform: capitalize;
padding: 10px 15px;
}
.btco-hover-menu .active a,
.btco-hover-menu .active a:focus,
.btco-hover-menu .active a:hover,
.btco-hover-menu li a:hover,
.btco-hover-menu li a:focus ,
.navbar>.show>a, .navbar>.show>a:focus, .navbar>.show>a:hover{
color: #000;
background: transparent;
outline: 0;
}
/*submenu style start from here*/
.dropdown-menu {
padding: 0px 0;
margin: 0 0 0;
border: 0px solid transition !important;
border: 0px solid rgba(0,0,0,.15);
border-radius: 0px;
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
/*first level*/
.btco-hover-menu .collapse ul > li:hover > a{background: #f5f5f5;}
.btco-hover-menu .collapse ul ul > li:hover > a, .navbar .show .dropdown-menu > li > a:focus, .navbar .show .dropdown-menu > li > a:hover{background: #fff;}
/*second level*/
.btco-hover-menu .collapse ul ul ul > li:hover > a{background: #fff;}
/*third level*/
.btco-hover-menu .collapse ul ul, .btco-hover-menu .collapse ul ul.dropdown-menu{background:#f5f5f5;}
.btco-hover-menu .collapse ul ul ul, .btco-hover-menu .collapse ul ul ul.dropdown-menu{background:#f5f5f5}
.btco-hover-menu .collapse ul ul ul ul, .btco-hover-menu .collapse ul ul ul ul.dropdown-menu{background:#f5f5f5}
/*Drop-down menu work on hover*/
.btco-hover-menu{background: none;margin: 0;padding: 0;min-height:20px}
@media only screen and (max-width: 991px) {
.btco-hover-menu .show > .dropdown-toggle::after{
transform: rotate(-90deg);
}
}
@media only screen and (min-width: 991px) {
.btco-hover-menu .collapse ul li{position:relative;}
.btco-hover-menu .collapse ul li:hover> ul{display:block}
.btco-hover-menu .collapse ul ul{position:absolute;top:100%;left:0;min-width:250px;display:none}
/*******/
.btco-hover-menu .collapse ul ul li{position:relative}
.btco-hover-menu .collapse ul ul li:hover> ul{display:block}
.btco-hover-menu .collapse ul ul ul{position:absolute;top:0;left:100%;min-width:250px;display:none}
/*******/
.btco-hover-menu .collapse ul ul ul li{position:relative}
.btco-hover-menu .collapse ul ul ul li:hover ul{display:block}
.btco-hover-menu .collapse ul ul ul ul{position:absolute;top:0;left:-100%;min-width:250px;display:none;z-index:1}
}
/*//Copy this css*/
.navbar-light .navbar-nav .nav-link {
color: rgb(64, 64, 64);
}
.btco-menu li > a {
padding: 10px 15px;
color: #000;
}
.btco-menu .active a:focus,
.btco-menu li a:focus ,
.navbar > .show > a:focus{
background: transparent;
outline: 0;
}
.dropdown-menu .show > .dropdown-toggle::after{
transform: rotate(-90deg);
}
#loader-wrapper{position:fixed;z-index:1000;top:270;left:0;background:#fff;width:100%;height:100%;text-align:center}
.loader{position:relative;top:20%;left:50%;-webkit-transform:translate(-50%,-50%);-moz-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);-o-transform:translate(-50%,-50%);transform:translate(-50%,-50%);text-align:center}.loader div{display:inline-block;background-color:#141414;width:14px;height:14px;margin:3px;-webkit-border-radius:50%;-moz-border-radius:50%;-ms-border-radius:50%;-o-border-radius:50%;border-radius:50%;-webkit-animation:bouncedelay 1.2s infinite ease-in-out;animation:bouncedelay 1.2s infinite ease-in-out;-webkit-animation-fill-mode:both;animation-fill-mode:both}
.loaded #loader-wrapper{visibility:hidden;background:rgba(255,255,255,0);transition:ease-out .3s}
.loaded #loader-wrapper,.loaded .loader{-webkit-transition:ease-out .3s;-moz-transition:ease-out .3s;-o-transition:ease-out .3s}
.loaded .loader{opacity:0;transition:ease-out .3s}
.loader .bounce1{-webkit-animation-delay:-.32s;animation-delay:-.32s}
.loader .bounce2{-webkit-animation-delay:-.16s;animation-delay:-.16s}@-webkit-keyframes bouncedelay{0%,100%,80%{-webkit-transform:scale(0);-moz-transform:scale(0);-ms-transform:scale(0);-o-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}@keyframes bouncedelay{0%,100%,80%{-webkit-transform:scale(0);-moz-transform:scale(0);-ms-transform:scale(0);-o-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}
#loader-wrapper .text{position:relative; top:23%;}
\ No newline at end of file
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+java */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
/* line numbers */
pre[class*="language-"].line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
pre[class*="language-"].line-numbers > code {
position: relative;
white-space: inherit;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid #999;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
pointer-events: none;
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}
/* Fallback, in case JS does not run, to ensure the code is at least visible */
[class*='lang-'] script[type='text/plain'],
[class*='language-'] script[type='text/plain'],
script[type='text/plain'][class*='lang-'],
script[type='text/plain'][class*='language-'] {
display: block;
font: 100% Consolas, Monaco, monospace;
white-space: pre;
overflow: auto;
}
\ No newline at end of file
<h4>Adapter</h4>
<p>Ein Adapter hilft zwei zunächst inkompatible Schnittstellen miteinander zu verbinden. Die Analogie dieses Entwurfsmusters zu einem Stromnetzadapter für Auslandsreisen ist naheliegend. Der Stromnetzadapter ermöglicht es, eine Steckdose mit einem Stecker für ein elektrisches Gerät zu verbinden, auch wenn der Stecker nicht direkt passt, weil er eine andere Schnittstelle aufweist.</p>
<img src="media/patterns_adapter_plug_socket.png" style="width:540px">
<label>Analogie zum Adapter-Muster</label>
<p>In der Software-Entwicklung wird ein Adapter (engl. auch <i>Wrapper</i>) eingesetzt, wenn eine existierende Klasse verwendet werden soll, deren Interface nicht dem vorgesehenen Interface entspricht. Die existierende Klasse soll oder kann nicht verändert werden, insbesondere wenn sie Teil einer externen Bibliothek ist, die von Dritten entwickelt wird. Die zu adaptierende Klasse aus der Bibliothek ist häufig von Interesse, da sie komplexes Verhalten implementiert, das wiederverwendet werden soll. Das Prinzip der Wiederverwendung ist grundsätzlich effizient, wenn externe Bibliotheken in ein Projekt eingebunden werden, von denen bekannt ist, dass sie umfänglich getestet worden sind und dass sie sich in anderen Projekten bereits praktisch bewährt haben. Im Java-Umfeld werden Bibliotheken i.d.R. aus einem <a href="https://mvnrepository.com/">zentralen Repository</a> über Build-Management-Werkzeuge wie <a href="https://maven.apache.org/">Maven</a> oder <a href="https://gradle.org/">Gradle</a> automatisiert in ein Projekt eingebunden.</p>
<p>Das Adapter-Muster gibt es in zwei Varianten: Objektadapter und Klassenadapter. In beiden Varianten wird die Ziel-Schnittstelle <code>Target</code> von der Adapter-Klasse <code>Adapter</code> implementiert. Weiterhin ruft der Adapter in der von <code>Target</code> vorgeschriebenen Methode <code>operation</code> die Methode <code>service</code> der adaptierten Klasse <code>Adaptee</code> auf. Es können natürlich auch mehrere Methoden adaptiert werden. Beim Objektadapter besteht eine Assoziation zwischen Adapter und adaptierter Klasse, während beim Klassenadapter der Adapter die adaptierte Klasse erweitert. Der Klassenadapter ist in Java nicht zu realisieren, falls <code>Target</code> kein Interface sondern eine zustandsbehaftete Klasse ist, da in diesem Fall Mehrfachvererbung erforderlich wäre. Die folgenden UML-Klassendiagramme visualisieren Objekt- und Klassenadapter im Vergleich zueinander.</p>
<img src="media/patterns_adapter.png" style="width:1000px">
<label>Entwurfsmuster Adapter (in Varianten Objekt- und Klassenadapter)</label>
<p>In dem folgenden Beispiel zur Motivation eines Adapters möchten wir die Fläche und den Umfang für unterschiedliche 2-dimensionale Formen bestimmen. Dazu haben wir das Interface <code>Shape</code> spezifiziert und bereits Implementierungen für einfache Formen wie Kreis, Rechteck und Dreieck entwickelt.</p>
<ul class="nav nav-tabs" id="adapter-tabs" role="tablist">
<li class="nav-item"><a href="#adapter-tabs-shape" class="nav-link active" data-toggle="tab" role="tab">Shape</a></li>
<li class="nav-item"><a href="#adapter-tabs-circle" class="nav-link" data-toggle="tab" role="tab">Circle</a></li>
<li class="nav-item"><a href="#adapter-tabs-rectangle" class="nav-link" data-toggle="tab" role="tab">Rectangle</a></li>
<li class="nav-item"><a href="#adapter-tabs-triangle" class="nav-link" data-toggle="tab" role="tab">Triangle</a></li>
</ul>
<div class="tab-content" id="adapter-tabs-content">
<div class="tab-pane show active" id="adapter-tabs-shape" role="tabpanel">
<pre><code class="language-java line-numbers">interface Shape {
Double perimeter();
Double area();
}</code></pre>
</div>
<div class="tab-pane" id="adapter-tabs-circle" role="tabpanel">
<pre><code class="language-java line-numbers">class Circle implements Shape {
Point2D center;
Double radius;
Circle(Point2D center, Double radius) {
this.center = center;
this.radius = radius;
}
@Override
public Double area() { return Math.PI * radius * radius; }
@Override
public Double perimeter() { return 2 * Math.PI * radius; }
}</code></pre>
</div>
<div class="tab-pane" id="adapter-tabs-rectangle" role="tabpanel">
<pre><code class="language-java line-numbers">class Rectangle implements Shape {
Point2D topLeft;
Double width, height;
Rectangle(Point2D topLeft, Double width, Double height) {
this.topLeft = topLeft;
this.width = width;
this.height = height;
}
@Override