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.

In Pocket speichern vorlesen Druckansicht 25 Kommentare lesen

(Bild: B.Forenius/shutterstock.com)

Lesezeit: 6 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

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.

Modernes C++ – Rainer Grimm

Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tätig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und häufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschäftigt er sich intensiv mit seiner Leidenschaft C++.

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.

(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.

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:

  1. Konvertieren der Werte von Python nach C/C++,
  2. verwenden der konvertierten Werte, um die C/C++-Funktionalität auszuführen und
  3. 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.

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.

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)