.NET-Architektur mit dem Beispiel „GoFish“ – Design und Development

by Gregor Biswanger 29. Januar 2012 19:41

gofish-1_thumb1In der ersten Folge dieser Architektur-Serie, haben wir die Phasen Vision, Diagnose und Analyse kennengelernt. Dabei wurde auf das Thema Kommunikation eingegangen und das diese durch Metaphorik (Zum Beispiel durch die Common Language) vereinheitlicht wird. Es begann mit Sketching, Modellierung und endete mit Prototyping.

In diesem Teil wird die Design- und Development-Phase vorgestellt, womit das Ziel mit dem Spiel „GoFish“ immer näher rückt.

 

 

 

Das Design

image


Wir beginnen mit der Design-Phase. Auch Entwurfsphase genannt.

  • Ziel der Design Phase ist die Festlegung, wie die Geschäftsanforderungen zu implementieren sind. Diese Phase konzentriert sich in erster Linie auf den Entwurf und die Einschätzung des erforderlichen Aufwands für die Entwicklung individueller Anpassungen.
  • Die wichtigsten Zielvorgaben sind produktspezifische, detaillierte Designspezifikationen, sowie Test Case Dokumente, welche die Funktionstests spezifizieren.

 

Die Abstraktion der Anforderungen

Die bisherigen Phasen konnten beim Auffinden der Anforderungen bestens weiterhelfen. In der Regel werden bereits beim Aufbau der “Common Language” sogenannte “User Stories” vom Kunden beschrieben. Das Schreiben von User-Stories verlangt allerdings eine Art Disziplin. Sie sollten in der Sprache des Kunden liegen. Daher sollen technische Aspekte auf keinen Fall enthalten sein. Genau aus diesem Grund, sollte auch der Kunde „eigentlich“ selbst die User-Stories schreiben.

 

image

Abbildung 1 – Die Abstraktionspyramide

Ein Beispiel als User-Story: „Beim Spielstart werden 52 Karten gemischt.“

Nun hat der Kunde seinen Prototyp erhalten und kann mittels SketchFlow-Player direkt Feedback an den Designer abgeben. Gleichzeitig wird der Entwickler mit der Abstraktion der Anwendungsfälle (User-Stories) zu einzelnen Tasks beginnen. Diese Tasks beschreiben die benötigten Schritte um die Anforderung zu erfüllen.

Als Beispiel sind hier die Tasks zur oben angezeigten User-Story gelistet:

1: Modellieren

2: Code generieren / anlegen

3: Anforderung via Test festlegen

4: Logik implementieren

5: Logik mit anderer Logik koppeln

Schnell wird ersichtlich, dass aus einer einfachen Anforderung viele Tasks entstehen. Anschließend können die User-Stories noch vom Kunden priorisiert und die Tasks vom Entwickler-Team geschätzt werden. Wichtig! Aus Zeitgründen muss oft schon auf User-Story-Ebene eine Zeitschätzung stattfinden, welche dadurch ungenau ausfällt . Zum Schätzen der Zeit kann ich Planning-Poker bestens empfehlen.

Für “GoFish” hatte ich keine User-Stories und Tasks eingeplant. In der Regel erstelle ich für meine privaten Projekte immer eine Art User-Stories und Tasks. Es kann auch nur zur stätigen Übung für sich selbst dienen. Allerdings soll diese Architekturserie nicht den Rahmen sprengen. Zur weiteren Arbeit reichen die Aktivitätsdiagramme und das Komponentendiagramm bestens aus.

 

Komponentendiagramm

Die Design-Phase hat nichts mit dem „Aussehen“, sondern mit dem „entwerfen“ der Software zu tun. In dieser Phase sollten alle Anforderungen weit möglichst bekannt sein. Jetzt gilt es diese Anforderungen in Komponenten aufzuteilen. Somit erhalten wir für unsere Architektur eine Flexibilität und bleiben jederzeit offen für Erweiterungen.

Eine Komponente ist eine modulare Einheit, die innerhalb ihrer Umgebung austauschbar ist. Die internen Daten sind ausgeblendet, aber Komponenten verfügen über eine oder mehrere klar definierte Schnittstellen, über die auf die Funktionen zugegriffen werden kann.

Zum Designen der Komponenten, verwende ich das Komponentendiagramm (engl. component diagram - UML). Auch dieses UML-Diagramm ist in den Architecture Tools von Visual Studio 2010 Ultimate enthalten.

Das Zeichnen von Komponentendiagrammen hat mehrere Vorteile:

  • Bei einem Komponentendiagramm werden die Abhängigkeiten der Komponenten untereinander ersichtlicher.
  • Wenn sich das Entwicklungsteam auf die Hauptblöcke eines Entwurfs konzentriert, kann es ein besseres Verständnis des vorhandenen Entwurfs entwickeln und einen entsprechenden neuen Entwurf erstellen.
  • Indem Sie sich Ihr System als Auflistung von Komponenten mit klar definierten, angebotenen und erforderlichen Schnittstellen vorstellen, optimieren Sie die Abgrenzung zwischen den Komponenten. Dadurch ist der Entwurf einfacher zu verstehen, und er kann bei sich ändernden Anforderungen auch einfacher angepasst werden.
  • Der Entwurf dient später auch für ein „Design by contract“. Womit Schnittstellen (Interfaces) für die Komponenten angelegt werden. Diese dienen dann als Vertrag (contract) für das gesamte Entwicklerteam.

 

Als Vorlage zum Modellieren der Komponenten, dienen die bereits erzeugten Workflows (Aktivitätsdiagramme). Hieraus erstellte ich pro Aktivität eine eigene Komponente. Die Schnittstelle der Komponenten war aus dem Domänenmodell bezogen und wurde somit immer auf eine Liste von Spielkarten (List<Card>) definiert. Dabei betrachtete ich das Expert-Muster, welches besagt: „Eine Aufgabe sollte von der Entität ausgeführt werden, die im Bezug darauf Experte ist.“.

Allerdings sollen Entities keinerlei Logik beinhalten und als einfache POCO‘s (Plain old CLR Object’s) behandelt werden. Als Lösung für die meisten Anforderungen von “GoFish”, eignen sich Extension-Methods geradezu ideal.

Normal steht eine Schnittstelle für eine Methode, so wie es beim CardsGenerator mit der Methode GetCards demonstriert wird (siehe Abbildung 2). Da ich allerdings später die Logik mittels Workflow Foundation 4 koppeln möchte, wird zu jeder Funktion eine weitere Facade-Komponente entwickelt. Womit ich bei der Extension-Methods-Lösung zufrieden bin.

 

SNAGHTML860a8d

Abbildung 2 – Aus den Aktivitäten der Workflows werden Komponenten angelegt.

 

Die einzelnen „Extension-Methods“-Komponenten verschachtel ich in eine CardsExtensionMethods-Komponente und definiere nur noch die Schnittstellen. Somit befinden sich die Funktionalitäten wieder in einem eigenen Kontext.

 

image

Abbildung 3 – Viele einzelne Komponenten werden zu einer kombiniert.

 

Die Schichtenarchitektur

Wobei jetzt grob die einzelnen Funktionalitäten definiert wurden, soll nun ein Schema der Architekturart folgen. Das wird in der Regel auch als Erstes modelliert. Bei diesem Spiel handelt es sich um einen Fat Client. Dabei soll die Schichtenarchitektur als fundamentale Grundlage dienen. Die Schichtenarchitektur bezeichnet grundsätzlich ein Architekturmuster, worin einzelne Aspekte des Softwaresystems konzeptionell einer Schicht (engl. layer) zugeordnet werden. Die erlaubten Abhängigkeitsbeziehungen zwischen den Aspekten werden bei einer Schichtenarchitektur dahingehend eingeschränkt, dass Aspekte einer „höheren“ Schicht nur solche „tieferer“ Schichten verwenden dürfen. Ein System mit einer Schichtenarchitektur bezeichnet man auch als mehrschichtig.
 
Die den Schichten zugeordneten Aspekte können dabei je nach Art des Systems oder Detaillierungsgrad der Betrachtung z. B. Funktionalitäten, Komponenten oder Klassen sein. Ich verwende für jede Schicht ein eigenes Assembly-Projekt. Das ermöglicht einen hohen Grad von Abstraktion und Austauschbarkeit. Womit eine hohe Flexibilität der Architektur gegeben wird.

Mein Tipp! HowTo: 3-Tier / 3-Schichten Architektur und  Ist eine 3 Schichten Architektur mit eigener DAL immer empfehlenswert?

 

Das 2- oder 3-Schichten-Modell?

Die erste Schicht besteht immer aus der auszuführenden Assembly (*.exe). In diesem Fall handelt es sich demnach um ein WPF-Projekt. Die zweite Domain-Schicht beinhaltet die Spielablauflogik. Im Geschäftsumfeld auch als Businesslogic bekannt, welche hier den Ablauf der Geschäftslogik regeln würde (zum Beispiel Zustandsvalidierungen, Berechnungen etc.). Die letzte Schicht ist in der Regel die Datenschicht (DataAccessLayer). Allerdings steht nirgends bei den Anforderungen beschrieben, dass Informationen persistent gespeichert werden sollen, womit auch eine Planung mit einem 2-Schichten Modell vollkommen ausreicht. Die zwei Schichten benötigen allerdings die Daten vom DomainModel (Entities), Contracts (Interfaces), Handler, Services, Aspekte und vieles weitere. Wenn diese Daten in der ersten Schicht untergebracht werden, müsste die zweite Schicht darauf referenzieren, was laut der Schichtenarchitektur strikt untersagt ist. Zudem muss die erste Schicht auch die Funktionen der zweiten aufrufen können. Explizit dafür wird eine weitere Querschicht GoFish.Common hinzugefügt. Jede Schicht ist somit verpflichtet die Common-Layer zu referenzieren.

Für jede Schicht wird eine Komponente angelegt und bekommt den Titel der späteren Assembly.

image

Abbildung 4 – Passend zum Komponentendiagramm werden neue Projekte angelegt. Diese fungieren jeweils als Layer (Schicht).

 

Das Ebenendiagramm (Layer-Diagram)

Für eine bessere visuelle Darstellung der Schichten, gibt es das Layer-Diagram. Auch dieses UML-Diagramm ist in den Architecture Tools von Visual Studio 2010 Ultimate enthalten. Sie können mit dem Layer-Diagram die vorgesehenen oder vorhandenen Abhängigkeiten zwischen bestimmten Schichten darstellen. Diese Abhängigkeiten geben an, welche Schichten die Funktionen auf anderen Schichten verwenden können oder gegenwärtig verwenden. Durch das Gliedern des Systems in Schichten, die verschiedene Rollen und Funktionen beschreiben, kann ein Ebenendiagramm das Verstehen, Wiederverwenden und Verwalten des Codes für Sie erleichtern.

Sie können mit einem Ebenendiagramm die folgenden Aufgaben ausführen:

  • Angeben der vorhandenen oder vorgesehenen logischen Architektur des Systems
  • Ermitteln von Konflikten zwischen dem vorhandenen Code und der vorgesehenen Architektur
  • Visualisieren der Auswirkungen von Änderungen an der vorgesehenen Struktur, wenn Sie das System umgestalten, aktualisieren oder weiterentwickeln
  • Erzwingen der vorgesehenen Architektur während der Entwicklung und Wartung des Codes, indem Sie in die Eincheck- und Buildvorgänge Validierung einschließen

 

image

Abbildung 5 – Die Schichten visuell darstellen mit dem Layer-Diagram. Die erlaubte Abhängigkeit wird durch Pfeile dargestellt.

 

Mein Tipp! Visual Studio 2010: Architektur-Validierung - Arbeiten mit Layerdiagrammen

 

Modellgetriebene Architektur (Model Driven Architecture, MDA)

Ein Teil der modellgetriebenen Architektur ist die modellgetriebene Softwareentwicklung (MDSD). MDSD (Model Driven Software Development) bezeichnet die Verwendung von Modellen und Generatoren zur Verbesserung der Softwareentwicklung. Schon in den Anfangszeiten der Informatik stützte man sich auf Modelle und entwickelte Generatoren, um schematischen Quellcode daraus zu erzeugen.

DomainModel zu Entities generieren

Beim Projekt “GoFish” kann ebenfalls ein minimaler Teil von MDSD mit den bereits erzeugten UML-Diagrammen genutzt werden. So lässt sich das DomainModel nun in Entity-Klassen generieren.

Wichtig! Hierfür muss vorher das Visual Studio 2010 Feature Pack installiert werden. Ansonsten ist die Funktionalität nicht vorhanden.

Um jetzt den Code generieren zu lassen, wird das DomainModel-Diagram geöffnet. Im Drop-Down-Menü vom Designer (Rechtsklick auf eine freie Fläche), wird der Menüpunkt “Generate Code” angeboten (siehe Abbildung 6).

 

 

image

Abbildung 6 – Aus dem Visual Studio 2010 UML-Designer den Code generieren lassen.

 

Etwas unschön ist, dass die Klassen willkürlich im eigenen Projekt erzeugt wurden. Jedoch erwartet man nach der Schichtenarchitektur, das die Entities in die GoFish.Common-Schicht unter dem Verzeichnis Entities generiert werden. Diesen Wunsch kann man unter Visual Studio 2010 selbst konfigurieren. Eine Konfiguration wird auch Projektspezifisch durchgeführt. Daher müssen bei einem neuen Projekt die Einstellungen wiederholt angepasst werden. Die Konfiguration befindet sich in Visual Studio 2010 im Menüpunkt “Architecure – Configure Default Code Generation Settings…” – (siehe Abbildung 7).

 

image

Abbildung 7 – Die Code Generation Settings öffnen.

 

In der “Code Generation”-Konfiguration müssen vom ClassTemplate die Properties “Project Path” und “Target Directory”, wie unter Abbildung 8 geändert werden.

 

SNAGHTML5ffb653

Abbildung 8 – Die Code Generation Settings.

 

Anschließend wird der Code “fast” wie erwünscht im Projekt erzeugt. Die einzige Kritik meinerseits ist, dass die Namespace-Definition der generierten Klassen fehlt. Man muss daher selbst im T4-Template diesen Wunsch einbauen (siehe Abbildung 8 – Template File Path). Ich hatte “etwas unschön” mit ReSharper manuell die Entities mit dem fehlenden Namespace erweitern lassen. Bei einer wiederholten Code Generierung müsste ich diesen Schritt erneut ausführen.

 

image

Abbildung 9 – Die vom DomainModel generierten Entity-Klassen.

 

Leider können allerdings vom Komponentenmodell keine Interfaces (Contracts) generiert werden. Diese erzeugte ich anhand des Diagramms manuell. Ich habe die Contracts erzeugt, die laut Komponentendiagramm instanziiert werden müssen. Das Projekt hat nun durch die Design-Phase seine Infrastruktur erhalten. Auch Interfaces (Contracts) und Entities wurden angelegt.

 

image

Abbildung 10 – Die erste Projektinfrastruktur, Entities und Contracts von GoFish sind nun fertiggestellt.

 

Development

image

Wir beginnen mit der Development-Phase, auch Entwicklungsphase genannt.

  • Ziel der Development Phase ist es, die in den Designspezifikationen festgelegten und genehmigten Anpassungen, Berichte, Integrationen und Datenmigrationsprozesse zu entwickeln und zu testen.
  • Die wichtigsten Zielvorgaben sind die getesteten und überprüften Komponenten und Prozesse.

 

Testgetriebene Entwicklung (engl. Test-Driven Development (TDD))

Für die Planung sind nun alle wichtigen Aspekte erfüllt worden. Ich habe mir nun eine Vorlage von Klassen und Interfaces angelegt, und muss nur noch die Logik dazu implementieren. Als Entwicklungsstil verwende ich schon seit Jahren die testgetriebene Entwicklung (Test-Driven Development, kurz TDD). Bei TDD wird vor der Implementierung der Logik, ein Unit-Test geschrieben. Diese sollen unter TDD die funktionale Anforderung einer Komponente sicherstellen. Im Gegenteil zu klassischen Unit-Tests, die Komponenten nur auf Seiten Effekte testen. Somit wird das “Test” von Test-Driven Development gerne verwechselt und Anhänger dieser Praxis haben daher eine Ergänzung mit Behavior-Driven Development (BDD) ins Leben gerufen. Für klassische Unit-Tests gibt es bereits zahlreiche Tools, die automatisch einen passenden Test Code zur Komponente generieren (siehe zum Beispiel Microsoft Pex).


Mein Tipp!
Unit-Test Generierung mit PEX - Whitebox Testing

 

Als Unit-Test Framework habe ich mich für das Open-Source Framework NUnit entschieden.

 

Hier nochmal der TDD Prozess im Detail:

  1. Schreibe den Testcode
  2. Mache ihn kompilierbar
  3. Starte den Test und lasse ihn fehlschlagen
  4. Setze nur die Anforderung um, damit der Test abgedeckt ist
  5. Starte den Test und prüfe, ob er erfolgreich ist
  6. Refactore den Code zur besseren Übersichtlichkeit und Vermeidung von Coderedundanzen
  7. Starte von Vorne mit dem nächsten Test

 

Also begann ich erst mit dem Schreiben der Tests. Welche Funktionen getestet/implementiert werden müssen, beschreibt uns das Komponentendiagramm (siehe Abbildung 11).

 

image

Abbildung 11 – Die beschriebenen Schnittstellen vom Komponentendiagramm werden mittels “Test-First” implementiert.

 

Beim Schreiben von Unit-Tests gibt es eigene Prinzipien zu beachten, die sich mit gängigen OOP-Prinzipien unterscheiden. Eines davon wäre die AAA-Syntax. (Weitere Infos darüber unter: TDD – Probleme vermeiden mit der AAA-Syntax). Den Source-Code für die Unit-Tests stehen unter Listing 1 & 2.


Mein Tipp! TDD Anti-Patterns - Am schlechten Beispiel lernen

 

   1:  using System.Collections.Generic;
   2:  using GoFish.Common.Entities;
   3:  using GoFish.Common.Extensions;
   4:  using GoFish.Domain;
   5:  using NUnit.Framework;
   6:   
   7:  namespace GoFishGame.Tests
   8:  {
   9:      [TestFixture]
  10:      public class GameSetUpTests
  11:      {
  12:          [Test]
  13:          public void GetCards_ShouldBeGetFiftyTwoCardsWithNonDublicate()
  14:          {
  15:              // Arrange
  16:              CardsGenerator cardsGenerator = new CardsGenerator();
  17:   
  18:              // Act
  19:              List<Card> results = cardsGenerator.GetCards();
  20:   
  21:              // Assert
  22:              Assert.AreEqual(52, results.Count);
  23:              
  24:              CardDublicateCheck(results);
  25:          }
  26:   
  27:          [Test]
  28:          public void GetFiveRandomCards_ShouldBeFiveNonDublicateCards()
  29:          {
  30:              // Arrange
  31:              CardsGenerator cardsGenerator = new CardsGenerator();
  32:              List<Card> results = cardsGenerator.GetCards();
  33:   
  34:              // Act
  35:              List<Card> randomKarten = results.GetFiveRandomCards();
  36:   
  37:              // Assert
  38:              Assert.AreEqual(randomKarten.Count, 5);
  39:              Assert.AreEqual(results.Count, 47);
  40:   
  41:              CardDublicateCheck(randomKarten);
  42:          }
  43:   
  44:          private static void CardDublicateCheck(List<Card> results)
  45:          {
  46:              foreach (Card card in results)
  47:              {
  48:                  var containsCount = results.FindAll(x => x == card);
  49:   
  50:                  if (containsCount.Count > 1)
  51:                      Assert.Fail("Card is dublicate");
  52:              }
  53:          }
  54:      }
  55:  }

Listing 1 –  Der GameSetUp Test-Code.

 

 

   1:  using System.Collections.Generic;
   2:  using GoFish.Common.Entities;
   3:  using GoFish.Common.Extensions;
   4:  using GoFish.Domain;
   5:  using NUnit.Framework;
   6:   
   7:  namespace GoFishGame.Tests
   8:  {
   9:      [TestFixture]
  10:      public class GameCoreLogicTests
  11:      {
  12:          [Test]
  13:          public void FindQuartets_GiveTenCardsWithTwoQuartets_ShouldBeGetTwoQuartets()
  14:          {
  15:              // Arrange
  16:              List<Card> cards = new List<Card>();
  17:   
  18:              Card karte = new Card { Value = ValueType.Ass };
  19:              Card karte2 = new Card { Value = ValueType.Ass };
  20:              Card karte3 = new Card { Value = ValueType.Zwei };
  21:              Card karte4 = new Card { Value = ValueType.Ass };
  22:              Card karte5 = new Card { Value = ValueType.Fünf };
  23:              Card karte6 = new Card { Value = ValueType.Ass };
  24:              Card karte7 = new Card { Value = ValueType.Zwei };
  25:              Card karte8 = new Card { Value = ValueType.Zwei };
  26:              Card karte9 = new Card { Value = ValueType.Zwei };
  27:   
  28:              cards.Add(karte);
  29:              cards.Add(karte2);
  30:              cards.Add(karte3);
  31:              cards.Add(karte4);
  32:              cards.Add(karte5);
  33:              cards.Add(karte6);
  34:              cards.Add(karte7);
  35:              cards.Add(karte8);
  36:              cards.Add(karte9);
  37:   
  38:              Player player = new Player { Cards = cards };
  39:   
  40:              // Act
  41:              List<Card> quartets = player.Cards.FindQuartets();
  42:   
  43:              // Assert
  44:              Assert.AreEqual(8, quartets.Count);
  45:   
  46:              List<Card> assQuartet = quartets.FindAll(karteItem => karteItem.Value == ValueType.Ass);
  47:              List<Card> zweiQuartet = quartets.FindAll(karteItem => karteItem.Value == ValueType.Zwei);
  48:   
  49:              Assert.AreEqual(4, assQuartet.Count);
  50:              Assert.AreEqual(4, zweiQuartet.Count);
  51:          }
  52:   
  53:          [Test]
  54:          public void FindQuartets_GiveFiveCardsWithNonQuartet_ShouldBeGetZeroQuartets()
  55:          {
  56:              // Arrange
  57:              List<Card> cards = new List<Card>();
  58:   
  59:              Card card = new Card { Value = ValueType.Ass };
  60:              Card card2 = new Card { Value = ValueType.Ass };
  61:              Card card3 = new Card { Value = ValueType.Zwei };
  62:              Card card4 = new Card { Value = ValueType.Ass };
  63:              Card card5 = new Card { Value = ValueType.Fünf };
  64:   
  65:              cards.Add(card);
  66:              cards.Add(card2);
  67:              cards.Add(card3);
  68:              cards.Add(card4);
  69:              cards.Add(card5);
  70:   
  71:              Player player = new Player { Cards = cards };
  72:   
  73:              // Act
  74:              List<Card> quartets = player.Cards.FindQuartets();
  75:   
  76:              // Assert
  77:              Assert.AreEqual(0, quartets.Count);
  78:          }
  79:   
  80:          [Test]
  81:          public void GetSameValueCards_SetSecondAssCard_ShouldBeGetOneAssCard()
  82:          {
  83:              // Arrange
  84:              List<Card> cards = new List<Card>();
  85:   
  86:              Card card = new Card {Value = ValueType.Acht};
  87:              Card card2 = new Card { Value = ValueType.Bube };
  88:              Card card3 = new Card { Value = ValueType.Zwei };
  89:              Card card4 = new Card { Value = ValueType.Ass };
  90:              Card card5 = new Card { Value = ValueType.Fünf };
  91:   
  92:              cards.Add(card);
  93:              cards.Add(card2);
  94:              cards.Add(card3);
  95:              cards.Add(card4);
  96:              cards.Add(card5);
  97:   
  98:              Card selectedCard = new Card {Value = ValueType.Ass};
  99:   
 100:              // Act
 101:              List<Card> sameCards = cards.GetSameValueCards(selectedCard.Value);
 102:   
 103:              // Assert
 104:              Assert.AreEqual(sameCards.Count, 1);
 105:              Assert.AreEqual(cards.Count, 4);
 106:              Assert.AreEqual(sameCards[0].Value, ValueType.Ass);
 107:          }
 108:   
 109:          [Test]
 110:          public void GetCard_GiveFiftyOneCards_ShouldBeGetOneRandomCard()
 111:          {
 112:              // Arrange
 113:              CardsGenerator cardsGenerator = new CardsGenerator();
 114:              List<Card> cards = cardsGenerator.GetCards();
 115:   
 116:              // Act
 117:              Card card = cards.GetCard();
 118:   
 119:              // Assert
 120:              Assert.AreEqual(cards.Count, 51);
 121:              Assert.NotNull(card);
 122:              Assert.AreEqual(typeof(Card), card.GetType());
 123:          }
 124:   
 125:          [Test]
 126:          public void GetTheWinner_WithMostQuartets()
 127:          {
 128:              // Arrange
 129:              List<Card> playerCardsA = new List<Card>();
 130:   
 131:              Card card = new Card { Value = ValueType.Ass };
 132:              Card card2 = new Card { Value = ValueType.Ass };
 133:              Card card3 = new Card { Value = ValueType.Ass };
 134:              Card card4 = new Card { Value = ValueType.Ass };
 135:   
 136:              playerCardsA.Add(card);
 137:              playerCardsA.Add(card2);
 138:              playerCardsA.Add(card3);
 139:              playerCardsA.Add(card4);
 140:   
 141:              Player playerA = new Player {Name="Bob", Quartets = playerCardsA};
 142:   
 143:              List<Card> playerCardsB = new List<Card>();
 144:   
 145:              Card cardB = new Card { Value = ValueType.Acht };
 146:              Card cardB2 = new Card { Value = ValueType.Acht };
 147:              Card cardB3 = new Card { Value = ValueType.Acht };
 148:              Card cardB4 = new Card { Value = ValueType.Acht };
 149:              Card cardB5 = new Card { Value = ValueType.Dame };
 150:              Card cardB6 = new Card { Value = ValueType.Dame };
 151:              Card cardB7 = new Card { Value = ValueType.Dame };
 152:              Card cardB8 = new Card { Value = ValueType.Dame };
 153:   
 154:              playerCardsB.Add(cardB);
 155:              playerCardsB.Add(cardB2);
 156:              playerCardsB.Add(cardB3);
 157:              playerCardsB.Add(cardB4);
 158:              playerCardsB.Add(cardB5);
 159:              playerCardsB.Add(cardB6);
 160:              playerCardsB.Add(cardB7);
 161:              playerCardsB.Add(cardB8);
 162:   
 163:              Player playerB = new Player {Name="Lucky Man", Quartets = playerCardsB};
 164:              Player playerC = new Player {Name = "Looser"};
 165:   
 166:              GamblingTable gamblingTable = new GamblingTable();
 167:              gamblingTable.Players.Add(playerA);
 168:              gamblingTable.Players.Add(playerB);
 169:              gamblingTable.Players.Add(playerC);
 170:   
 171:              // Act
 172:              string playername = gamblingTable.Players.GetTheWinner();
 173:   
 174:              // Assert
 175:              Assert.AreEqual("Lucky Man", playername);
 176:          }
 177:      }
 178:  }

Listing 2 – Der GameCore Test-Code.

 

Die Lösung konnte ich ideal mit Extension-Methods umsetzen. Dass die Logik auch nach den Vorgaben funktioniert, zeigt die Grüne-Welle der Unit-Tests (siehe Abbildung 12).

 

image

Abbildung 12 – Die Tests wurden alle erfolgreich erfüllt.

 

Zusammenfassung und Vorschau auf den nächsten Teil

Wir haben nun gemeinsam an den Phasen Design und Development geschnuppert. Auch wenn der Punkt Development nicht komplett abgeschlossen wurde, somit ist zumindest die Hauptlogik durch TDD implementiert worden. Das angenehme an TDD ist, dass ich derzeit noch keine Oberfläche (Frontend) für mein Spiel benötige. Ich kann jederzeit per Knopfdruck überprüfen ob alle gewünschten Anforderungen noch wie erwartet funktionieren. Parallel dazu, kann bei WPF die Oberfläche von einem professionellen Grafik-Designer entworfen werden.

Bei der Design-Phase wurden die jeweiligen Prozesse zu Komponenten abstrahiert. Aus den Komponenten entstanden die jeweiligen Schichten und Contracts (Interfaces). In der Praxis vermischen sich gerne auch Phasen untereinander. So vermischt sich oft die Analyse-Phase mit der Design-Phase.

Beim nächsten Teil werden wir die Development-Phase fortsetzen. Dazu wird jede Komponente durch ein Activity (Workflow Foundation 4) erweitert und die ersten Workflows definiert.

Viel Spaß und bis zur nächsten Folge, wenn Ihr mögt!



Wenn ihnen der Artikel gefallen hat oder er für sie hilfreich war, bitten "kicken" sie ihn.
kick it on dotnet-kicks.de

Kommentare

Kommentar schreiben


(Zeigt dein Gravatar icon)  

  Country flag

biuquote
  • Kommentar
  • Live Vorschau
Loading



Powered by BlogEngine.NET 1.4.5.0
Theme by Extensive SEO

Über den Autor

Gregor Biswanger

Microsoft MVP für Client App Dev
XING

Gregor Biswanger (Microsoft MVP für Client App Dev) ist freier Consultant, Trainer, Autor und Speaker.


Seine Schwerpunkte liegen im Bereich der .NET-Architektur, agilen Prozessen und XAML. Er veröffentlichte vor kurzem seine DVD´s mit Video-Trainings zum Thema „Meine erste Windows 8 App“, „Windows Store Apps mit XAML und C#“ und „WPF 4.5 und Silverlight 5“ bei Addison-Wesley von video2brain.


Biswanger ist auch im Auftrag von Intel GmbH als Technologieberater für die Intel Developer Zone aktiv und ist Leader bei der Ingolstädter .NET Developers Group (INdotNET). 

 

Video über mich:
http://www.youtube.com/watch?v=mx_6SiiLxjk


Basta! 2011 Speaker

CLIPer

MCTS
Windows SharePoint Services 3.0 – Application Development (MCTS)