Hallo, ich möchte ein Getränke Automat in WinForms erstellen, welches im 3 Schichten Model realisiert werden soll. Im Automaten werden verschiedene Getränke zu verschiedenen Preisen angeboten, nun soll man Geld einschmeißen können, durch klicken der "Münz" Buttons (10, 20, 50 cent, 1€) soll der counter erhöht werden. Wenn ausreichend Geld eingeworfen wurde sollte das Getränk dann ausgegeben werden.
Nun wie fängt man hier an, zuerst sollte man wahrscheinlich ein Klassendiagramm erstellen!?
Was müsste in den einzelnen Schichten nun enthalten sein, also welcher Teil des Automaten kommt wohin?
Wenn ich meine Daten z.B. Preise in einer XML habe, werden diese dann doch im Data Layer verarbeitet oder?
Da ich sowas zum ersten mal mache wollte ich bei euch um Rat helfen 😃
Der Data Access Layer bietet dir nur Zugriff auf die Daten. Jegliche Verarbeitung sollte im BLL passieren. Hört sich irgendwie nach Schulaufgabe an und da solltest du dir zunächst selber Gedanken machen. Informationen gibt es ja genug. Benutze doch einfach die Forumssuche.
Hallo #coder#,
Mach doch einfach ein Klassendiagramm oder allgemein ein Designe (Klassen, Methoden, Properties, Dokumentation, etc.).
Das "fertige" Design kannst du dann hier posten und wenn etwas grob Falsch ist wird man dir mit freude helfen.
Gruß
Juy Juka
In deinem Fall eigentlich sehr einfach zu beantworten:
Präsentationsschicht:
Die Maske, da kannst du dein Getränk (per Button oder wie auch immer) auswählen. Hier wird aber nichts gemacht als die Auswahl! Einfach das ausgewählte Getränk an die nächste Schicht weiter geben.
Verarbeitungssschicht oder Buisness-Schicht:
Hier kannst du jetzt alles machen was dein Automat können soll.
Bestellungen sammeln, Preise berechnen,....
Datenzugriffsschicht:
Im wesentlichen ist das nur eine Schicht, die von der Buisnesslogik eine Anfrage bekommt und die entsprechenden Daten aus der Datenbank bzw Datenquelle liefert. Bsp: Buisness will Preis berechnen, sie fragt die Datenschicht was die Getränke kosten, die schaut nach und liefert die Preise zurück. Vorsicht hier: Summe aller Preise wäre wieder Buisness, nicht Daten! Aber das ist Geschmackssache wie genau man sich daran hält.
Sinn und Ziel der Schichten ist nur, einfach Dinge austauschen zu können.
Zb brauchst du, wenn du es gut implementierst, nicht die Logik ändern wenn du eine neue Maske erstellst, oder das ganze zb als WebApplication anbieten willst.
Wenn ich jetzt etwas falsch erklärt habe wird mich sicherlich jemand verbessern g
LG & GL
Muss ich für jede Schicht eine Library erstellen, z.b. bei Business & Data Layer ?
Im Prinzip sind es doch 3 Projekte oder?
Weil das hier gerade einigermaßen passt und den Threadersteller eventuell auch interessiert:
Wenn meine Business-Layer (BL) mehrere Klassen hat, dürfen diese Klassen innerhalb der Schicht ja soviel miteinander kommunizieren, wie ich es will. Binde ich dann in jeder Klasse der BL alle anderen Klassen der BL ein, ... sollte ich das dann via Objekte machen, oder über using... ?! Das ist mir noch nicht so ganz klar.
Danke.
Ich lasse mich gerne korrigieren! (:
Binde ich dann in jeder Klasse der BL alle anderen Klassen der BL ein, ... sollte ich das dann via Objekte machen, oder über using... ?!
Hm? Beweg dich einfach im selben Namespace. "in jede Klasse alle anderen Klassen einbinden" - was meinst du damit?
LaTino
"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)
Hallo #coder#,
müssten tust du das nicht, aber gerade wenn du neu bist, ist es schon günstig, drei Projekte zu machen, damit du klare Zuständigkeiten hast und nicht versehentlich in einer Schicht Klassen benutzt, die diese eigentlich nicht kennen darf.
herbivore
@herbivore: An genau so einer Situtation befinde ich mich nun, also ich habe 3 Projekte erstellt für die Schichten. Nun möchte ich eine Klasse befüllen mithilfe der XML Datei, die Frage die sich mir nun stellt wo kommt die Klasse hin, Business oder Data? Ich brauche das Objekt in der Business Schicht
Wie sieht die Verbindung zwischen den Schichten aus, sind das normale Klassen oder muss man Interfaces verwenden zwischen den Schichten verwenden??
Hallo #coder#,
die **Datenzugriffsschicht **kümmert sich nur darum, Daten zu lesen und Daten zu speichern. Im Falle Deiner XML-Preisliste könnte ich mir das etwa so vorstellen:
public class DataAccess
{
public SodaMachineDataSet GetItemsAndPricesFromFile(string fileName)
{
...
}
public void SaveItemsAndPricesToFile(SodaMachineDataSet dataSet, string fileName)
{
...
}
}
Das Datenmodell brauchst Du in allen drei Schichten. Deshalb solltest Du sogar noch ein viertes Visual Studio-Projekt mit dem Namen **Contracts **(zu Deutsch: Verträge) anlegen. Dort packst Du Klassen des Datenmodells und alle sonstigen Klassen rein, die in allen drei Schichten benötigt werden. Ein typisiertes DataSet eignet sich sehr gut als Datenmodell, da es standardmäßig XML speichern und lesen kann.
In der **Geschäftsschicht **muss die eigentliche Funktion des Automaten abgebildet werden. Die Prüfung, ob z.B. genug Geld eingeworfen wurde, ist z.B. ein Fall für die Geschäftslogik. Da die Geschäftslogik möglichst Statuslos sein sollte (also keine Variablen über mehrere Funktionsaufrufe - aus anderen Schichten heraus - hinweg halten!), benötigst Du noch eine Klasse, die den Status beschreibt. Diese "Status" Klasse gehört ins Geschäftsschicht-Projekt. Sie darf nur Daten enthalten, keine Logik! Daten und Logik sollten bei einer 3-Schicht Anwendung immer getrennt werden. Also ++nicht ++eine Klasse schreiben, die Daten und Logik des kompletten Getränkeautomaten verschmilzt. Der vollständig objektorientierte Ansatz passt nicht zum 3-Schichten-Modell.
So könnte eine Status-Klasse aussehen:
public class SodaMachineState
{
public int SelectedBeverageID
{
get { ... }
set { ... }
}
public int InsertedMoney
{
get { ... }
set { ... }
}
public bool CanDumped
{
get { ... }
set { ... }
}
}
Die eigentliche Geschäftslogik könnte man z.B. so ausdrücken:
public class SodaMachineLogic
{
public void InsertCoin(int coinValue, SodaMachineState state)
{
...
}
public void RequestCanDump(SodaMachineState state)
{
...
}
public void SelectBeverage(int itemID, SodaMachineState state)
{
...
}
public void Abort(SodaMachineState state)
{
...
}
}
Die Präsentationsschicht (wahrscheinlich ein Windows.Forms-Formular) hält über die Laufzeit der Anwendung hinweg das DataSet mit den Getränkearten und Preisen und zu zusätzlich eine Instanz der Status-Klasse. Wenn der Benutz nun "Geld einwirft", ruft das Formular die Geschäftsschicht auf, übergibt den Betrag und einen Verweis auf die Status-Klasse an die InsertCoin-Methode. Diese prüft, ob die eingeworfene Münze gültig ist (10, 20, 30, 100) und addiert falls ja den Wert zu InsertedMoney der Status-Klasse. So kann das Formular nach dem InsertCoin-Aufruf sofort die Anzeige fürs eingeworfene Geld aktualisieren. Wenn die Münze ungültig ist, könnte die Geschäftslogik eine Ausnahme werfen. Diese wird vom Formular gefangen und als Fehlermeldung anzeigt.
Nun solltest Du eine Vorstellung davon haben, wie man eine Anwendung in 3-Schichten-Architektur entwirft.
Hallo #coder#,
Man muss keine interfaces verwenden, es ist aber zu empfehlen.
Um mal eine Lösungen für dein Problem zu zeigen:
namespace ...Datenzugriffsschicht
{
public delegate T KonstruktionsMethode<T>();
public interface IDatenquelle
<T>
{
void IEnumerable<T> Get(KonstruktionsMethode<T> konstruktion);
}
public class DatenQuelle<T> : IDatenQuelle<T>
{
public void IEnumerable<T> Get(KonstruktionsMethode<T> konstruktion)
{
if ( konstruktion == null ) throw new ArgumentNullException("konstruktion");
foreach(DataRow dr in GetDataRowsFromXml())
{
T re = konstruktion();
foreach(PropertyInfo property in typeof(T).GetProperties())
{
// hier werden halt im endefekkt alle Properties aus dem XML in das Objekt übertragen.
property.SetValue(re,dr[re.Name],new object[]{});
}
yield return re;
}
}
}
}
Das ganze ist natürlich nur grob und ziemlich schlechter Code. 😉 Aber so ungefähr könnte man's machen.
Alternativ könnte man auf die Generics (<T>) verzichten und stadt dessen ein interface deklarieren, was jedoch eigentlich durch die Businessschicht definiert aber in der Datenzugriffsschicht benötigt wird:
namespace ...Datenzugriffsschicht
{
public delegate T KonstruktionsMethode<T>();
public interface IDatenquelle
<T>
{
void IEnumerable<T> Get(KonstruktionsMethode<T> konstruktion);
}
public class GetraenkDatenQuelle : IDatenQuelle<IGetraenk>
{
public void IEnumerable<IGetraenk> Get(KonstruktionsMethode<IGetraenk> konstruktion)
{
if ( konstruktion == null ) throw new ArgumentNullException("konstruktion");
foreach(DataRow dr in GetDataRowsFromXml())
{
IGetraenk re = konstruktion();
re.Preis = dr["preis"];
// ...
yield return re;
}
}
}
}
Ist dann natürlich nicht mehr so sauber getrennt, dafür halt einfacher.
Gruß
Juy Juka
[EDIT]War ja klar, das mal wieder wer schneller ist: Zu Rainbird's Post:
Das mit den DataSet ist ungefähr wie mit dem Interface aus meinem Vorschlag.
Das mit der State-Klasse und der getrennten Funktionsklasse will ich hier nicht ausschweifen, dazu gibts schon mindestens einen Thread [EDIT]Gefunden: 3-Schichten-Design?? Hilfe bei der Umsetzung[/EDIT]
[/EDIT]
edit: link wurde bereits gepostet.
@LaTino:
Z.B. habe ich in der Business-Layer vier Klassen im gleichen Namespace und alle public. Jede der vier Klassen hat ein Objekt der anderen drei Klassen übergeben bekommen, um deren Funktionen nutzen zu können, da es ohne Objekt trotz public nicht geht. Ich frage mich halt, ob das der richtige Weg ist, oder ob ich die anderen Klassen irgendwie über using... einbinden kann/sollte/müsste?!
Ich lasse mich gerne korrigieren! (:
Hallo eveN,
Vielleicht solltest du einen eigenen Thread eröffnen!
Als Erstes: using hat erst mal 0 und nichts mit dem Verwenden von Objekten zu tun. Über using kann man nur Klassennamen "abkürzen", z.B. Button stadt System.Windows.Forms.Button.
Das andere Problem was du ansprichst ist lose Koppelung beziehungsweise deren Fehlen. Da ist irgend wo der Wurm im Klassendesign, wenn du solche seltsamen netze von Objekten hast. Das muss man aber im Detail-Fall anschauen und kann dann mit den Passenden Werkzeugen (Entwurfsmusster / Patterns) abhilfe schaffen.
Gruß
Juy Juka
Noch eine Frage, wenn ich eine Klasse mit Daten auffüllen will z.B. Produkt, Preis aus der XML Datei, wo muss sich die Klasse befinden im Business oder Data Layer? Eigentlich müsste die in der Business Schicht enthalten sein, da ich diese dort verwenden muss.
Hallo #coder#,
Datenklassen (Produkt, Preis, etc.) werden von allen Schichten gleichermaßen verwendet. Deshalb müssen sie in einer separaten Contract-Assembly abgelegt werden. Die anderen Projekte benötigen einen Verweis auf diese Assembly. Du benötigst die Datenklassen auch in der Benutzerschicht. Woher soll denn das Formular sonst die Daten nehmen, um die Steuerelemente zu füllen?
Zugeordnet werden Datenklassen der Datenzugriffsschicht. Trotzdem dürfen sie nicht mit der eigentlichen Datenzugriffslogik in der selben Assembly liegen.
Generell ist die Vorstellung 1 Schicht = 1 Assembly, 3 Schichten = 3 Assemblies falsch.
Hallo Rainbird,
Leider muss ich deiner Aussage wiedersprechen.
Die "Datenklassen" müssen nicht in ein extra Assembly, das man überall referenziert!
Im allgemeinen wiederspreche ich ja dem Ansatz, dass man Daten und Funktion in zwei Klassen trennt.
Die Daten zu extrahieren braucht man ausschließlich wenn man Grenzen (Programm, Framework, Prozessor, etc.) überschreiten muss, im rest eines OO-Programmes sollte so etwas nur versteckt passieren...
Egal, das weicht zu weit ab, dazu haben wir ja mindestens einen anderen Thread (3-Schichten-Design?? Hilfe bei der Umsetzung).
@ #coder# : Mit etwas aufwand kann man solche Klassen in der Logik-Schicht definieren, die Datenzugriffs-Schicht muss dafür halt allgemeingültig werden. Also ich würde die Klassen in der Business-Schicht definieren.
Gruß
Juy Juka
Deshalb müssen sie in einer separaten Contract-Assembly abgelegt werden. Die anderen Projekte benötigen einen Verweis auf diese Assembly.
Kann man sicher mit einer überall referenzierten Assembly machen. Wenn, würde ich in dieser referenzierten Assembly maximal die Schnittstellendefinitionen halten. Mehr müssen die referenzierenden Schichten wirklich nicht wissen 😃.
LaTino
"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)
Wenn, würde ich in dieser referenzierten Assembly maximal die Schnittstellendefinitionen halten. Mehr müssen die referenzierenden Schichten wirklich nicht wissen 😃.
Der DataSet-Designer erzeugt keine Schnittstellen, sondern nur Klassen. Ich habe #coder# oben vorgeschlagen, typisierte DataSets zu verwenden. Außerdem benötigt man die Klassen, wenn man die Lösung einmal verteilbar machen will, denn Schnittstellen kann man nicht Serialisieren.
Deshalb macht es Sinn das so zu machen.
Wenn man keine typisierten DataSets einsetzt und alles mit POCOs und Schnittstellen macht, hast Du aber völlig recht, dann reichen die Schnittstellen (Solange man nicht verteilt!).
@rainbird hast natürlich recht, typisierte DataSets sind schon seit paar Jahren außerhalb meines geistigen Ereignishorizontes, dh vergesse immer wieder, dass es die Dinger ja auch noch gibt...
"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)
Hallo nochmal, habe mal eine UML Klassendiagramm erstellt, ist aber noch nicht fertig!
Ich habe den State Pattern verwendet, nun habe ich noch paar fragen:
Jeder Zustand ist eine Klasse in der unterschiedliche Funktionen behandelt werden oder?
Bin mir nicht so sicher bei den Zuständen ob die richtig gesetzt sind. Auch bei den Beziehungen bin ich mir nicht sicher, zudem fehlt noch die Daten Schicht, die ich noch weggelassen habe. Hoffe ihr könnt mir weiterhelfen.
EDIT: Wo werden die einzelnen Abfragen wie IsValidMoney() abgefragt, passiert es in dem jeweiligen Zustand oder im Getraenkeautomat ??