Software Engineering II

Im Grunde geht es beim Software Engineering um 4 Kernfragen

  1. Qualität: Wie stellen wir sicher, dass das die Software das tut, was der Kunde gefordert hat?
  2. Kunde: Wie erreichen wir, dass die Software für den Anwender nützlich ist?
  3. Entwickler: Wie erreichen wir, dass die Entwicklung des Softwareprodukts effizient und insbesondere für neue Entwickler verständlich und somit wartbar ist?
  4. Kosten/Zeit: Wie stellen wir sicher, dass das Softwareprojekt mit den vom Kunden vorgegebenen Resourcen (Geld, Technologie, Manpower) im Zeit- und Kostenrahmen erstellt wird?

Dokumentation und Wissensmanagement in der Softwareentwicklung

Entwicklung:

  • Softwarekontextgestaltung
  • Requirement Engineering
  • Architektur
  • Feinentwurf
  • Implementierung
  • Konfigurationsmanagement

Qualitätsmanagement

  • Produkt-Testing, Inspektion und Metriken
  • Prozessmessung und Verbesserung

Evoluation

  • Einführung
  • Inbetriebnahme
  • Weiterentwicklung
  • Wiederverwendbarkeit (Reengineering)
  • Änderungsmanagement

Projektmanagement

  • Team
  • Kosten
  • Termine
  • Risiken
  • Auftraggeber / Auftragnehmer

Projekt und Prozess

Der wichtigste Unterschied zwischen Projekt und Prozess ist, dass das Projekt einer Einmaligkeit entspricht und ein Prozess eine Regelmäßigkeit aufweist. Somit ist ein Projekt als ein einmaliges außerordentliches Vorhaben definiert und der Prozess als ein regelmäßiger und wiederkehrender Ablauf.

Vergleichbar ist das Projekt mit einer Klasse in der objektorientierten Programmierung, die Klasse darf nur einmal existieren. Ein Prozess hingegen entspricht einem Objekt, welches mehrer Instanzen haben kann.

Verifikation und Validierung

  • Bei der Verifikation geht es darum, zu überprüfen, ob wir es richtig gemacht haben. Dies geschieht z.B. durch die Testierung und Messung
  • Die Validierung dagegen ist mehr Subjektiv, hier geht es darum, ob das Richtige gemacht wurde. Hierzu benötigen wir das feedback des Kunden / Nutzer, dessen Zufriedenheit ist unsere Validierung.

Die Nutzerzufriedenheit lässt sich leider nicht messen und ist eine subjektive Einschätzung. Dennoch steht sie in enger Verbindung mit der Verifikation. Denn nur Verifizierter Code kann validiert werden.

Fehler und Mangel

Bei jeder Umsetzung von Softwareprodukten kommt es früher oder später zu Problemen. Gut ist es natürlich, wenn diese Probleme schon vor der Auslieferung erkannt und bestenfalls behoben werden können. Jedoch unterscheidet man bei der Softwareentwicklung zwischen Fehler und Mangel.

Fehler

Ein Fehler ist eine Nichterfüllung einer vom Kunden festgelegten Anforderung.

Ein Softwareprodukt kann nur auf Fehler geprüft werden, jedoch nicht auf deren Abwesenheit. Das heist, es ist nicht möglich alle Möglichkeiten zu überprüfen, sondern nur auf explizite Fälle.

Hierzu muss das Produkt verifiziert werden.

Mangel

Ein Mangel könnte man als einen nicht schwerwiegenden Fehler beschreiben. Ein Mangel ist eine nicht angemessene Erfüllung einer festgelegten Anforderung.

Ein Mangel wird durch die Validierung festgestellt.

Modelierung

Im Software Engineering spricht man oft von Modellen oder der Modellierung. Ein Model versteht sich als ein Abbild der Realität, also einem Nachbau der Wirklichkeit. Denn schliesslich möchte man beim Software Engineering die Realität nachbauen. Da die Realität meist zu komplex ist um diese komplett nachzubauen oder zu modellieren, ist ein Modell eine Abstraktion der Realität. Man beschränkt sich auf das Wesentliche.

Ein Modell ist immer Zweckgebunden und hat Interprätationsfreiraum.

Kohäsion, Kopplung und Komplexität

Unter Kohäsion versteht man den innerer Zusammenhang einer Einheit, zzum Beispiel einer Methode, Klasse oder einem Modul. Stichwort DRY-Prinzip „Don’t repeat yourself“ oder „wiederhole dich nicht“, in der Softwareentwicklung auch bekannt als „once and only once“ – „einmal und nur einmal“. Es besagt, Redundanzen zu vermeiden oder zumindest stark einzuschränken.

Die Kopplung ist eine Metrik für externe Abhängigkeit von Einheiten und Verknüpfungen. Je höher die Anzahl der Verknüpfungen desto höher ist die Kopplung.

Die Komplexität ist eine Metrik für die Operationszahl oder Vererbungstiefe. Eine Methode mit vielen Operationen ist eine Komplexe Operation.

Eine qualitativ hochwertige Software möchte Kohäsion, Kopplung und Komplexität so gering wie möglich halten. Denn je Komplexer der Code ist, desto fehleranfälliger ist dieser. Auch ist die Wartbarkeit eines Komplexen Codes für neue Entwickler oder nach einiger Zeit sehr Zeitintensiv.

Rationale

Oder auch die Kommunikation von Entscheidungen bei der Entwicklung

  1. Question – zu erst stellt sich die Frage, was zu tun ist.
  2. Options – danach, welche Optionen zur Verfügung stehen.
  3. Criteria – darauf folgt, welche Kriterien müssen erfüllt werden
  4. Argument – Mit welcher Begründung können wir das machen
  5. Decision – zum Schluss die Entscheidung oder die Umsetzung

Anforderungen

bei den Anforderungen wird zwischen funktionalen und nicht-funktionalen Anforderungen unterscheiden.

Eine funktionale Anforderung (oder FR für functional requirement) beschreibt man so gut wie immer mit „WAS“. Zum Beispiel „Was muss die Funktion machen?“ – Berechnen.

Eine funktionale Anforderung ist darüber immer durch einen oder mehrere Tasks oder Systemfunktionen beschreibbar.

Wichtige Begriffe

Eine nicht-funktionale (oder NFR für non functional requirement) Anforderung beschreibt meist „WIE GUT“. Zum Beispiel: „Wie schnell kann die Berechnung durchgeführt werden?“

Qualitätsmerkmale einer Software

  • Functional suitability: Vollständigkeit und Korrektheit
  • Reliability: Zuverlässigkeit der Software
  • Performance efficiency: Effizienz der Software
  • Compatibility: Kompatibilität
  • Usability: Benutzbarkeit der Anwendung
  • Security: Sicherheit gegen Angriffe
  • Maintainability: Wartbarkeit und Erweiterbarkeit
  • Portability: Anpassung / Anpassungsfähigkeit

Testierung

Komponententest

Der Komponententest ist der „kleinste Test“. Dieser Test testiert die einzelnen Komponenten, z.B. die Methoden der einzelnen Klassen. Hierbei wird auf die Funktionalität der Einzelteile hin getestet.

Integrationstest

Bei Integrationstest werden die getesteten einzelnen Komponenten auf deren zusammenhängende Funktionalität überprüft. Demnach ist der Integrationstest ein weiterer Schritt und erfolgt nach dem Komponententest.

Systemtest

Der Systemtest testet wie der Name schon sagt das gesamte System. Hierbei werden einzelnen Szenarien getestet, ob die einzelnen Komponenten richtig interagieren. Dieser Test wird vor dem Release durch die Entwickler durchgeführt.

Abnahmetest

Der Abnahmetest ist gleich dem Systemtest, nur dass dieser durch den Kunden / Auftraggeber durchgeführt wird. Hier besteht die letzte Möglichkeit Mängel oder Änderungen festzuhalten.

Testverfahren

Whitebox Testing

Das Whitebox-Testing ist ein Testverfahren, bei dem der Tester den Code des zu testenden Gegenstandes kennt. Er kann also gezielt auf Grenzwerte oder problematische Eingaben testen. In der Regel werden Komponententestes im Whitebox-Testingverfahren durchgeführt.

Beim Whitebox-Testing wird auch die Zeilenüberdeckung überprüft.

Blackbox-Testing

Beim Blackbox-Testing ist die Implementierung unbekannt, sprich der Testende kennt den Code der Implementierung nicht. Der test wird anhand der festgelegten Spezifizierung durchgeführt. „Tut das Programm das, was es tun soll?“.

Diagramme

UML

Hier geht es zur ausführlichen Dokumentation: http://wiki.haberland.it/doku.php?id=projekte.haberland.it:software-engineering:uml-interaktionsdiagramme

Wichtig ist die Unterscheidung zwischen Aggregationen und Generalisierung. Eine Generalisierung ist eine Vererbungsbeziehung zwischen Klassen, während eine Aggregation eine Beziehung darstellt.

Schwache Aggregation

Eine schwache Aggregation bedeutet, dass hier eine Assoziation zu eigenständigen Objekten besteht, welche nicht zwingend notwendig ist.

Beispiel zu Aggregationen:

Eine Ehe besteht aus zwei Ehepartnern, die auch nach einer Scheidung der Ehe als eigenständige Personen fortbestehen. (Quelle: Wikipedia)

Starke Aggregation = Kompossition

Die Komposition beschreibt eine Assoziation, welche zwingend notwendig ist. So kann das Teil-Objekt ohne das Aggregat-Objekt nicht existieren.

Beispiel zur starken Aggregationen = Kompossition

Ein Gebäude besteht aus einzelnen Stockwerken, die nach dessen Abriss nicht eigenständig fortbestehen können. (Quelle: Wikipedia)

Generalisierung

Die Generalisierung beschreibt die Vererbungsbeziehung zwischen Objekten. So ist beispielsweise die Klasse Hund eine Spezialisierung von Tier. Sie deklariert beispielsweise das Attribut Rasse und verfügt zusätzlich implizit über die Attribute Gewicht und Größe aus der Klasse Tier.

Domänenklassendiagramm

Das Domänenklassendiagramm beschreibt Entitäten, Attribute und deren Beziehungen zueinander.

Entität

Die Entitäten symbolisieren die einzelnen Informationsobjekte. Wodurch in der Datenmodellierung ein eindeutigen Objekt bezeichnet wird.

Attribute

Attribute sind Merkmale die eine Entität besitzt. So besitzt z.B. die Entität Tier unter anderem die Attribute Gewicht und Größe.

Beziehungen

Mit den Beziehungen wird die Verbindung zwischen den einzelnen Entitäten gekennzeichnet. Hat man beispielsweise die Klassen Auto und Person, so könnte man eine Beziehung zwischen den beiden Klassen als „fährt“ oder „besitzt“ kennzeichen. Wichtig ist hierbei, dass man die Beziehungen mit Multiplizitäten kennzeichnet.

Multiplizitäten

Multiplizitäten sind Intervalle, die eine Beziehung kennzeichnen. So könnte in unserem Beispiel die Multiplizität zwischen den Kassen Auto und Person bei der Beziehung „fahren“ nur ein Intervall von 0..1 haben. Da eine Person entweder kein oder ein Auto fahren kann, aber nicht mehre Autos gleichzeitig.

Hingegen könnte bei der Beziehung „besitzt“ ein Intervall von 0..*, denn eine Person kann entweder kein oder beliebig viele Autos (theoretisch) besitzen.

Analyseklassendiagramm

Entity

Auch im Analyseklassendiagramm ist die Entity eine Klasse. Und besitzt Attribute und Funktionen. Es ist hilfreich, wenn man beim Analyseklassendiagramm zuerst die Klassen einzeichnet.

Controll

Wie der Name schon sagt, werden hier die Funktionen dargestellt, welche kein Objekt darstellen.

Boundary

Übersetzt Grenze, steht für das Interface, mit dem das Programm mit dem Nutzer kommuniziert.

Entwurfsklassendiagramm

Das Entwurfsklassendiagramm dient als Vorbereitung der Implementierung und berücksichtigt auch alle Entwurfsziele.

  • Einordnung der Klassen in das bereits bestehende Klassendiagramm
  • Vervollständigung der Listen der Attribute und der Operationen
  • Festlegung von Datentypen, Sichtbarkeit (+, -, #, …)
  1. Welche Entitäten, Dialoge und Funktionen gibt es?
    Wie stehen sie zueinander in Beziehung?
    einfaches Analyseklassendiagramm
  2. Welche Attribute und Funktionen werden benötigt
    und wo kann man die Steuerklassen einordnen?
    verfeinertes Analyseklassendiagramm
  3. Wo können die neuen Klassen im bestehenden Klassendiagramm zugeordnet werden?
    Was muss noch hinzugefügt werden?
    Entwurfsklassendiagramm

Zustandsdiagramme

Zustandsdiagramme zeigen, wie der Name schon sagt Zustände un deren Übergänge an. Hierbei sind die Knoten die Zustände des Objektes und die Kanten die Zustandsübergänge.

Wichtig ist, dass alle Zustände dargestellt werden, bzw. erfasst werden.

Dialogmodell

Das Dialogmodell ist ein Zustandsdiagramm, in dem die Zustände die Workspaces und die Übergänge sind die Systemfunktionen.

H* steht für Tiefer History-Zustand H*: merkt sich über alle Ebenen hinweg die Subzustände.

Sequenzendiagramme

Das Sequenzendiagramm ist Abstrakt und zeigt den Austausch von Nachrichten zwischen Objekten. Konkret: Aufruf von Methoden auf Objekten bzw. statischen Klassen.

Dabei beginnen die Lebenslinien mit Objektname:Klassenname, bei statischen Klassen nur mit dem Klassennamen. Synchrone Methodenaufrufe werden mit durchgezogenem Pfeil und gefüllter Spitze dargestellt. Die Rückgabewerte werden mit gestricheltem Pfeil und offener Spitze gezeigt. Methoden ohne Rückgabewert, also void, werden nicht explizit eingezeichnet.

Es gibt opt-Blöcke: welche die Option, also if ohne else kennzeichnen und alt-Blöcke: welche für die Alternative stehen, also if mit else.

Wichtige Regeln für das Sequenzendiagram

  • der zeitliche Ablauf ist immer von oben nach unten
  • Aufrufe können in beide Richtungen erfolgen
  • die Objektnamen im Kopf der Lebenslinien sind gleich der Namen im restlichen Diagramm.
  • die Methode gehört immer zu dem Objekt / der Klasse an der Pfeilspitze
  • das Aufrufende Objekt / Klasse ist immer am Pfeilanfang
  • die Sequenz muss durchgängig sein

Beim Erstellen oder Bearbeiten ist immer zu beachten:

  • in welcher Klasse wird die Methode aufgerufen? (Pfeilanfang)
  • zu welcher Klasse gehört die aufgerufene Methode? (Pfeilspitze)
  • erfolgt der Aufruf statisch?
  • ist ein Übergabeparameter vorhanden?

Kontrollflussgraphen

Ziel ist die Größtmögliche Abdeckung des Codes eng. code coverage. wodurch die Korrektheit der Implementierung wird getestet werden soll.
Toter (unerreichbarer) Code kann damit aufgedeckt werden. Dadurch soll ein hohes Maß an Sicherheit gewährleistet werden.

Achtung: Inkorrekte Umsetzungen kann mit dieser Methode nicht getestet werden!

Der Kontrollflussgraph soll alle Abläufe eines Codes abbilden. Unterschiedliche Arten den Graphen zu durchlaufen entspricht der Coverage im Graphen und somit dem Code Coverage.

Regeln zur Erstellung eines Kontrollflussgraph

  1. Objekt-Blöcke bilden
  2. If-Abfragen bilden Verzweigungen
  3. eine for oder while Schleife ist eine Verzweigung in der Schleifenbedingung.
    Auf den Zustand für den Rücksprung in die Schleifenbedingung beachten
  4. Schließende Klammern einzeln beachten!
  5. returns bilden einen künstlichen Endzustand

Wie erreicht man Coverage?

Verschiedene Arten der Überdeckung:

  • Anweisungsüberdeckung:
    alle Knoten werden mind. einmal überdeckt, d.H.
    Für alle Knoten im Kontrollflussgraphen gibt es mind. einen Test. Dadurch sind Tote Anweisungen leicht aufzufinden.
  • Zweigüberdeckung:
    Alle Kanten werden mind. einmal überdeckt, d.H.
    Für alle Zweige im Kontrollflussgraphen gibt es mind. einen Test. Dadurch werden nicht erreichbare Zweige aufgedeckt. Achtung: Berechnungsfehler und Steuerfehler werden nicht erkannt.
  • Pfadüberdeckung:
    Alle möglichen Pfade durch das Diagramm werden einmal durchlaufen

Design Patterns oder auch Entwurfsmuster

Die Grundidee der Nutzung Design Patterns sind Kapselung der konkreten Klasse und die Kapselung der Erzeugung und Konfiguration. Durchdie Verwendung von Entwurfsmuster ist das System flexibler, da:

  • konkrete Klasse und
  • der konkrete Erzeuger nicht zur compiletime bekannt sein müssen,
  • der Erzeugungsprozess zur compiletime nicht fest sein muss und dadurch
  • der Zeitpunkt der Erzeugung aufgeschoben werden kann.

Creational patterns: Erzeugungsmuster

Abstract Factory: Abstrakte Fabrik

  • Interface zur Erzeugung einer Familie von Objekten,
  • konkrete Klassen der zu instanziierenden Objekte nicht spezifiziert
  • System kann mit Produktfamilie(n) konfiguriert werden

Dadurch entstehen folgende Vorteile, das System ist unabhängig von der Art der Erzeugung und das System kann zur Laufzeit Produktfamilie einfach austauschen.

Einziger Nachteil ist, dass neue Produkte nur schwer zu integrieren sind.

Builder: Erbauer

  • komplexe Objekte können unabhängig von der Erzeugung der Bestandteile erzeugt werden
  • kann internen Zustand des Konstruktionsablaufes kapseln

Dadurch ergibt sich der Vorteil, dass die Konstruktion und die Repräsentation getrennt wird.

  • Erzeugungsprozess kann im director ohne Änderung am client geändert werden
  • Neue Repräsentationen durch neue concrete builder

Nachteile sind eine enge Kopplung zwischen product, concrete builder und anderen beteiligten Klassen, so wie die vollständige Initialisierung der Daten nicht garantiert werden kann.

Factory Method: Fabrikmethode

  • Entkoppelt die Erzeugung und die Art des Produkts
  • Abstrahiert die “new”-Syntax durch Methode der Factory

Dadurch erhalten wir die Vorteile, dass die Fabrik durch eine statische Methode repräsentiert werden kann. Die Erzeugung erfolgt ohne client-Änderungen und die Semantik der Methoden sind aussagekräftiger als “new”.

Jedoch gibt es den Nachtei durch Dopplung der Hierarchie, die Fabrikhierarchie bildet im Endeffekt die Produkthierarchie ab.

Prototype: Prototyp

  • Vermeidet Produkt- & Fabrikhierarchie
  • Kann komplexe Erzeugung durch klonen eines Prototyps umgehen
  • konkreter Prototyp muss erst zur runtime bekannt sein

Die Vorteile von Prototype sind, dass schnell, neue Objekte durch Variation von Alten erstellt werden können und eine komplexe Hierarchie vorliegt.

Dafür gibt es auch Nachteile, wie copy muss von allen sub Klassen implementiert werden zusätzliche Initialisierungen können auftreten. Zudem wissen wir, dass I/Os sehr teuer sind, dadurch ist das Kopieren ebenfalls teuer.

Singleton

  • erzeugt und verwaltet eine einzige, private, statische Instanz des Objektes.
  • der globale Zugriff auf die Instanz erfolgt mittels statischer Methode

Daraus ergibt sich der Vorteil der einfachen Zugriffskontrolle, eine Spezialisierung durch Unterklassen, die zur runtime gewählt werden können ist einfach umzusetzen. Objekte werden erst initialisiert, wenn diese gebraucht werden. Singleton kann ohne großen Aufwand in ein Multiton umgewandelt werden.

Die Nachteile vom Singleton sind, dass e in verteilten und parallelen Systemen zu Problemen kommen kann. Jedes Objekt und jede Funktion muss einzel getestet werden. Zudem kommt es häufig zu Redundanzen.

Structural patterns: Strukturmuster

Structural patterns sind z.B. Adapter, Bridge / Brücke, Composite / Kompositum, Decorator / Dekorierer, Facade / Fassade, Flyweight / Fliegengewicht usw.

Im Allgemeinen gilt für die Structural pattern, dass diese abstrahiere Beziehungen zwischen Entitäten haben. Die Verschmelzung, Trennung und Erweiterung von Aufgabenbereichen ist einfach.

Der Code / das System ist „sauberer“, da Strukturen klar definiert, Relationen aus dem jeweiligen Muster ersichtlich sind und Absichten wie auch die Hierarchie klar kommuniziert werden.

Adapter

  • ermöglicht die Kommunikation zweier inkompatibler Schnittstellen
  • verbindet existierende Klasse mit einer benötigten inkompatiblen Schnittstelle

Klassenadapter sind (nicht in Java) nur mit Mehrfachvererbung realisierbar. Objektadapter: Adapter hat Assoziation zur adapted class und delegiert Aufrufe dorthin.

Vorteil Adapter und adapted class können einfach ausgetauscht werden.

Brücken

  • Trennt sauber Implementer von Abstraction
  • Sorgt dadurch für Entkopplung

Dadurch ist Implementer zur Laufzeit austauschbar und der Implementer so wie der Abstraction beide unabhängig erweiterbar. Der Client arbeitet nur mit Abstraction.

Kompositum

  • Teil-Ganzes-Hierarchien durch Baumstrukturen
  • Composites enthalten andere Composites oder Leafs
  • Verbirgt Unterschiede zwischen primitiven und zusammengesetzten Objekten

Daraus resultiert eine leichte Erweiterbarkeit durch neue Klassen so wie eine einheitliche Behandlung von Leaf und Composite, welches beides Vorteile sind.

Nachteile dagegen ist dass die Verallgemeinerung die Beschränkung von Typen verhindert und dadurch runtime checks notwendig werden.

Decorator

  • Erweitert Klasse ohne subclassing um Funktionalität
  • Decorator können weitere Decorator dekorieren

Vorteile, mehrere Decorator für Funktionserweiterung möglich. Instanzen sind zur runtime und nach Initialisierung austauschbar. Decorator ersetzt komplexe Vererbungshierarchien.

Nachteil ist jedoch dass decorated component nicht gleich component ist und darum die Identität extra geprüft werden muss. Nachrichten müssen immer bis zur component weitergereicht werden.

Flyweight

  • reduziert Anzahl der verwendeten Objekte zur Laufzeit
  • nutzt gemeinsamen, intrinsischen Zustand und wird mit extrinsischen Kontext benutzt

Vorteile ist die Reduktion von Speicher durch die Auslagerung eines Teils der Zustände, Reduktion der Verantwortlichen Objekte und die Berechnung des extrinsischen Zustands.

Die Nachteile sind dadurch, dass die Komplexität steigt, Interpretation des extrinsischen Zustands muss definiert sein und der runtime overhead steigt durch Laden & Übergabe so wie die Berechnung des extrinsischen Zustands ist teuer.

Behavioral patterns: Verhaltensmuster

Die Grundidee der Behavioral pattern ist, die Abstraktion der Interaktionen zwischen Objekten und die Modellierung komplexer Kontrollflüsse.

Wodurch das System flexibler wird. Logik wird einfacherer austausch- und erweiterbar. Komplexe Prozesse werden abstrahierte. Reaktionen / Entscheidungen passieren zur runtime.

  • Chain of responsibility / Zuständigkeitskette
  • Command / Befehl
  • Interceptor
  • Interpreter
  • Iterator
  • Mediator / Vermittler
  • Memento
  • Observer / Beobachter
  • State / Zustand
  • Strategy / Strategie
  • Template method / Schablonenmethode
  • Visitor / Besucher

Chain of responsibility / Zuständigkeitskette

  • Leitet Anfrage an eine Kette von Objekten weiter
  • Aufrufender weiß nicht ob oder wie viele Objekte die Anfrage bearbeiten

Vorteil, geringe Kopplung, da der Client den Bearbeiter nicht und bei Kettenglieder nur den direkten Nachfolger kennt. Dadurch kann die Zuständigkeit geändert werden ohne den Client darüber informieren zu müssen.

Nachteil ist dafür, dass es keine Garantie gibt, dass Anfrage tatsächlich bearbeitet werden. Ausserdem müssen Zyklen ausgeschlossen werden.

Command / Befehl

  • Bildet Operationen als Objekte ab
  • Ermöglicht command queue oder Umkehren von Operationen

Die Vorteile sind Entkopplung von Auslösendem und Ausführendem. Ausserdem können diese manipuliert werden oder zu komplexen Befehlen kombiniert werden.

Nachteil ist, dass die Anzahl der notwendigen Klassen sehr schnell steigt.

Notiz

Meines Erachtens sind Erzeugungs-, Struktur-, Verhaltens-Muster Creational, Structural, Behavioral pattern die wichtigsten.

Ersten Kommentar schreiben

Antworten

Deine E-Mail-Adresse wird nicht veröffentlicht.


*