Patterns in der Softwarearchitektur: Das Schichtenmuster
Das Schichtenmuster unterteilt eine Aufgabe in horizontale Schichten. Jede Schicht erbringt einen Dienst für die höhere Schicht.
(Bild: B.Forenius/shutterstock.com)
- Rainer Grimm
Patterns sind eine wichtige Abstraktion in der modernen Softwareentwicklung und Softwarearchitektur. Sie bieten eine klar definierte Terminologie, eine saubere Dokumentation und das Lernen von den Besten. Das Schichtenmuster unterteilt eine Aufgabe in horizontale Schichten. Jede Schicht hat eine bestimmte Verantwortung und erbringt einen Dienst für die höhere Schicht.
Das Schichtenmuster oder die Schichtenarchitektur ist ein Architekturmuster, das laut dem Buch "Pattern-Oriented Software Architecture, Volume 1" hilft, Struktur in das Chaos zu bringen.
Auch bekannt als
- N-Tier-Architekturmuster
Zweck
- Große Systeme, die zerlegt werden müssen
Problem
- Ein System, das Operationen auf verschiedenen Ebenen durchführt,
- höhere Ebenen nutzen niedrigere Ebenen
Lösung
- Strukturiere das System in Schichten,
- die Dienste einer höheren Ebene basieren auf den Diensten der unteren Ebenen.
Struktur
(Bild: Chunte7, CC BY-SA 3.0, via Wikimedia Commons)
Client
- Greift auf die oberste Ebene zu
Layer J
- kapselt die spezifische Rolle und Verantwortung des
Layer J,
- bietet seine Dienste mithilfe des
Layer J-1
an und - kann nur auf
Layer J-1
zugreifen.
Obwohl es nicht festgelegt ist, bestehen die meisten Schichtenarchitekturen aus drei oder vier Schichten. Jede Schicht ist unabhängig von den anderen. In der reinen Version kann eine Schicht nur auf die darunter liegende zugreifen. Eine Schicht kann nicht auf ihre obere zugreifen, da dies zusätzliche Abhängigkeiten schaffen würde und die Kontrollstruktur verkompliziert. Außerdem kann eine Schicht, die von einer höheren abhängt, nicht ohne Weiteres in einer anderen Anwendung verwendet werden. Eine Schicht stellt ihre Funktionen oft durch die Implementierung des Fassaden-Musters bereit. Das Fassaden-Muster bietet eine vereinfachte Schnittstelle zu einem komplexen System.
Beispiele
Das Schichtenmuster wird seit den Anfängen der Softwareentwicklung häufig verwendet. Dementsprechend gibt es viele Anwendungsfälle:
OSI-Modell und TCP/IP-Modell
The Open Systems Interconnection model (OSI model) is a conceptual model that 'provides a common basis for the coordination of [ISO] standards development for the purpose of systems interconnection'.[2] In the OSI reference model, the communications between a computing system are split into seven different abstraction layers: Physical, Data Link, Network, Transport, Session, Presentation, and Application. (https://en.wikipedia.org/wiki/OSI_model)
Ähnliches gilt für das vereinfachte TCP/IP-Modell: The Internet protocol suite, commonly known as TCP/IP, is a framework for organizing the set of communication protocols used in the Internet and similar computer networks according to functional criteria. The foundational protocols in the suite are the Transmission Control Protocol (TCP), the User Datagram Protocol (UDP), and the Internet Protocol (IP). (https://en.wikipedia.org/wiki/Internet_protocol_suite)
Embedded-Systeme
Wer Software für eingebettete Systeme entwickelt, verwendet typischerweise in C++ verschiedene Abstraktionsebenen.
- Man beginnt normalerweise mit dem Board Support Package (BSP), das Board-spezifische Konfigurationen wie Boot-Firmware und Gerätetreiber enthält, damit das eingebettete Betriebssystem funktionieren kann.
- Der Hardware Abstraction Layer (HAL) befindet sich über dem BSP. Es handelt sich dabei um eine Abstraktionsschicht zwischen der Hardware und der Software, die auf dem eingebetteten System läuft. Sie hat die Aufgabe, Unterschiede in der Hardware vor dem Betriebssystem zu verbergen.
Erweitern/Einbetten von Python in C/C++
Das Erweitern von Python in C/C++ besteht aus den folgenden Schritten:
- Konvertieren der Werte von Python nach C/C++,
- verwenden der konvertierten Werte, um die C/C++-Funktionalität auszuführen und
- konvertieren der Ergebnisse von C/C++ nach Python.
Das Einbetten macht das Gleiche in umgekehrter Reihenfolge. Wie geht es auf der Python- und der C-Schicht weiter? Hier ist die vereinfachte Strategie.
Alle Python-Datentypen wie int
erben von object
.
Das C-Pendant zum Datentyp object
ist das C struct PyObject
. C ist nicht objektorientiert. PyObject
ist eine Art Ausgangspunkt für den Python-Objektspeicher. Die Implementierung findet sich auf GitHub: object.c. PyObject
hat im Wesentlichen einen Referenzzähler und einen Zeiger auf den entsprechenden Typ.
Wenn man eine Methode für einen Python-Typ aufruft, geht dieser Aufruf an die C-Struktur PyObject
, die in object.c
definiert ist. In object.c
bestimmt die C-Funktion Py_TYPE
den Typ des Objekts und ruft die entsprechende Funktion auf der C-Schicht auf. Das heißt, wenn die entsprechende Methode auf dem abgeleiteten Typ implementiert ist, wird diese aufgerufen. Ist dies nicht der Fall, wird die Standardimplementierung von PyObject
aufgerufen, falls dies möglich ist.
Vor- und Nachteile
Vorteile
- Ersetzen von Schichten
Jede Schicht hat eine bestimmte Rolle und bestimmte Aufgaben. Sie bietet ihre Dienste für die höhere Schicht über eine Schnittstelle an. Die höhere Schicht hängt nur von der Schnittstelle der unteren Schicht ab. Folglich kann die untere Schicht leicht ersetzt werden.
- Testbarkeit
Jede Schicht hat ihre Dienste gekapselt. Das macht es einfach, die Funktionalität jeder Schicht zu testen. Innerhalb der Schichten müssen feinere Tests wie Unit-Tests durchgeführt werden.
- Entwicklung
Dank der Trennung der verschiedenen Schichten (separation of concern), kann jede Schicht isoliert implementiert werden. Zunächst muss die Schnittstelle zwischen den Schichten definiert werden.
Nachteile
- Granularität der Schichten
Es kann eine Herausforderung sein, die richtige Granularität der Schichten zu finden. Zu viele Layer können zu Schichten führen, die nur eine minimale Verantwortung haben. Außerdem kann die Architektur schwer zu verstehen sein. Zu wenige Layer machen es ziemlich kompliziert, Schichten zu ersetzen, sie zu testen und isoliert zu entwickeln.
- Performanz
Ein Clientaufruf löst eine Reihe von Aufrufen aus, die in der untersten Schicht enden. Diese Abfolge von Aufrufen kann sich auf die Performanz der Anwendung negativ auswirken. Das gilt vor allem, wenn die Schichten remote sind.
Wie geht's weiter?
Das Pipes-and-Filters-Muster ist ziemlich praktisch, wenn man ein System hat, das Daten in mehreren Schritten verarbeitet, und jeder Schritt unabhängig entwickelt werden soll. Darüber werde ich in meinem nächsten Artikel schreiben. (rme)