Laden...

3-Schichten-Design?? [harte vs. weiche (Daten-)Objekte / OOP vs. ActiveRecords]

Erstellt von TripleX vor 15 Jahren Letzter Beitrag vor 15 Jahren 27.118 Views
TripleX Themenstarter:in
328 Beiträge seit 2006
vor 15 Jahren
3-Schichten-Design?? [harte vs. weiche (Daten-)Objekte / OOP vs. ActiveRecords]

Hallo liebe Gemeinde,

ich lese mich jetzt schon seit kurzer Zeit in das Thema des 3-Schichten-Designs ein, verstehe es auch einigermaßen aber es hapert bei mir an der Umsetzung. Also mein Ziel ist es ein kleines Kalenderprogramm zu basteln.

Folgende Komponenten werden aufjedenfall gebraucht:
Ein DataSet (dsCalender) mit einer DataTable (dtTasks) drin. Erstmal soll die Datenbank als XML-File gespeichert werden. Fürs gute Aussehen sollen noch ein paar Forms und UserControls verwendet werden.

Mein Ziel ist es jetzt, eine "Fundamentalsystem" zu bauen, welches möglichst skalierbar ist und es soll mir einen guten komfort und (wichtig) Übersichtlich sein.
Mein Ansatz bisher ist folgendermaßen: Siehe Anhang

Mein Problem liegt jetzt in der weiteren Vorgehensweise.
Was kommt alles in den BusinessLayer?
Was kommt alles in den DataAccessLayer?

Ich habe mir mal so Gedanken über das Design gemacht und ich habe folgende Fragen:

  • Der PresentationLayer kennt das DataSet ja nicht, also ist auch kein DataBinding möglich?
  • Ich hätte gerne eine Klasse Task, doch wo bringe ich sie unter und dann ist da noch das Problem dass die Klasse fast identisch ist mit der Klasse dtTasks aus dem DataSet. Ist die Klasse Task also überflüssig?
  • Wenn ich schon dabei bin: Ich hatte bisher wenig mit INterfaces zu tun. Wo könnte man diese hier einsetzen?

Vielleicht kann mir jemand anhand eines einfachen beispiels (eine datenbank mit einer tabelle) einen sinnvollen Aufbau zeigen?

Ich hoffe ihr könnt mir helfen.
MFG TripleX

Träume nicht dein Leben sondern lebe deinen Traum.
Viele Grüße, David Teck

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo TrippleX,

Die blöden Antworten

Was kommt alles in den BusinessLayer?

Die Funktionslogik deiner Software. Validierungen, Logische prüfungen, etc.

Was kommt alles in den DataAccessLayer?

Ausschließlich der Zugriff auf die Datenbasis.

Die guten Antworgen

  • Der PresentationLayer kennt das DataSet ja nicht, also ist auch kein DataBinding möglich?

DataBinding geht auch mit Objekten, im Dialog "neue Datenquelle" einfach mal Objekt stadt Datenbank auswählen. Tipp: UNBEDINGT INotifyPropertyChanged mit allen Klassen aus der Funktionsschicht implementieren (weiter hin zu Empfehlen: IDataErrorInfo, IEditable, ...).

  • Wenn ich schon dabei bin: Ich hatte bisher wenig mit INterfaces zu tun. Wo könnte man diese hier einsetzen?

Für jede deiner Klassen ein Interface aus der Funktions- und der Datenzugriffsschicht. 😃 Das ist Ernst gemeint! Tipp: FactoryPattern oder gleich einen Microkernel besorgen (z.B.: |Aisys| O/R Mapper , da ist ein Microkernel mit drin).

  • Ich hätte gerne eine Klasse Task, doch wo bringe ich sie unter und dann ist da noch das Problem dass die Klasse fast identisch ist mit der Klasse dtTasks aus dem DataSet. Ist die Klasse Task also überflüssig?

Ist Sie nicht, da Sie die Logik enhält, z.B. Validierungen, z.B. AnfangsDatum < EndDatum. (g Beispiel zum Beispiel g).
Aber deine Überlegung hatte ich auch und habe mir daher eine generische Datenzugriffschicht gebaut wo man nicht für jede Funktionsklasse eine Datenzugriffsklasse braucht (siehe |Aisys| O/R Mapper). Aber das ist ein bischen Weiterführend und du solltest erst mal bei deinem Projekt bleiben und nur einen Microkernel und jede menge Interfaces benutzen.

Gruß
Juy Juka

TripleX Themenstarter:in
328 Beiträge seit 2006
vor 15 Jahren

danke für deine Antwort,

also ich habe mal versucht mit Interfaces zu arbeiten, und mein Aufbau ist jetzt folgendermaßen: Siehe Anhang.

Eigentlich bin ich soweit recht zufrieden, doch beim testen hats nich ganz geklappt.
Also ich habe mal versucht von meiner PresentationLayer die Funktion AddTask aufzurufen. Dann bringt mir der Compiler folgende Fehlermeldung:

Der Typ "DataAccessLayer.ITasks" ist in einer nicht referenzierten Assembly definiert. Fügen Sie einen Verweis auf die Assembly "DataAccessLayer, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" hinzu.

Was die Fehlermeldung heißt, kann ich mir denken. Ich müsste im PL ein Verweis zum DAL einfügen. Aber das verstößt dann doch gehen die "Richtlinien", welche besagen dass ein Layer nur den Layer unter ihn kennt.

MFG TripleX

Träume nicht dein Leben sondern lebe deinen Traum.
Viele Grüße, David Teck

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo TrippleX,

Datenzugriffsschicht und Funktionsschicht haben natürlich zwei verschiedene Schnittstellen. Du musst in beiden je ein interface ITask definieren (jap, gleiche spielchen wie mit den beiden Klassen g), schließlich muss ITask aus der Datenzugriffsschicht ja etwas anderes leisten als ITask aus der Funktionsschicht.
Ach ja: Microsoft empfiehlt Typen nicht gleich zu benennen, auch wenn Sie durch Namespaces getrennt sind (Datenzugrifsschicht.ITask und Funktionsschicht.ITask). Des weiteren soll man auch keine Postfixe verwendne sondern den "Basisnamen" hinten anhängen (also nicht "dsCalendar" sondern "CalendarDataSet"). (Richtlinien zur Benennung)

Namens- und Strucktuvorschlag folgen als UML-Diagramm.

  1. Ich habe zwischen Funktions- und Datenschicht auf Schnittstellen verzichtet, da mit Rainbird (glaube ich) mal gesagt hat, das Interfaces mit DataSet und Co. ziemlich schwer umzusetzen ist. (Falls ich mich irre, sollte man dort natürlich sofort Schnittstellen einfügen.)
  2. Das "<<Fasade>>" ist ein weiteres Pattern. Ist für so ein Kalender-Projekt glaube ich sehr gut.

Gruß
Juy Juka

5.941 Beiträge seit 2005
vor 15 Jahren

Hallo TippleX

Schau mal hier:

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

TripleX Themenstarter:in
328 Beiträge seit 2006
vor 15 Jahren

Hallo,

danke für die Antworten (auch wenn ihr keiner meinen namen korrekt schreiben konnte 😉 ).

Also irgendwie stehe ich immernoch aufm Schlauch. Mal schauen ob ich das richtig verstanden habe bisher:

Im DataAccessLayer kommt mein DataSet hinein. Für jede Tabelle inner DB gibt es:

  • 2x Interface (einmal für die Tabelle an sich und einmal für die Einträge)
  • 1x Klasse (um die Zugriffe auf diese Tabelle zu Managen)
    (ITask, ITaskManager, TaskManager)

Im BusinessLayer kommt rein (für jede Tabelle inner DB):

  • 1x Interface (für die Einträge)
  • 2x Klassen (für die Einträge und um die Klasse zu managen)
    (ITask, Task, TaskManager)

stimmt das bisher so?

MFG TripleX

Träume nicht dein Leben sondern lebe deinen Traum.
Viele Grüße, David Teck

5.941 Beiträge seit 2005
vor 15 Jahren

Hallo TripleX

Ich habe "TrippleX" gelesen aber dann falsch geschrieben,
aber auch damit war ich falsch...
...jetzt stimmts 😉

Wo hakts jetzt noch?
Hast du dir das Video / Tutorial und die Beispielanwendung auf Code-Inside angesehen?

Ich finde Robert bringt es dort ziemlich auf den Punkt.
Zwei gleichbenannte Interfaces halte ich für Unfug.
Die ganze Architektur sollte schlussendlich auch einen Sinn ergeben.

Mal in Kürze:

DataLayer:
Abstraktion deines Datenzugriffs.
Dafür benutzt du im optimalen Fall ein Interface für den Zugriff, so sprichst du dann immer mit IUserProvider aber im Hintergrund speist du bspw. ein XmlUserProvider / SqlUserProvider ein, am Businesslayer ist das aber egal.

Dieser Layer sollte nur sehr generische Datenzugriffsarbeiten behinhalten, keine Logik und keine Filterund (Nur die Möglichkeit dazu).

BusinessLayer:
Hier benutzt du den DataLayer, aber nur das Interface davon und delegierst alle Daten im einfachsten Fall weiter.

Im anderen Fall (So dass die Schicht auch Sinn ergibt) machst du alles was im DataLayer noch fehlt, aber generelll anwendbar ist.
D.h. Filterung / Sortierung, spezielle Abfragemethoden, Caching, Logik in der Abfrage.

Ein gutes Beispiel hierbei finde ich wiederum das von Code-Inside mit dem IUserService, dort enthält der Service die Logik für den LogIn und LogOut.
Das hätte nichts im DataLayer zu suchen, so als Beispiel.

Und von deiner Benutzeroberfläche sprichst du nur mit dem Businesslayer, den DataLayer kennst du gar nicht.
Und alle Logik die du noch in der Benutzeroberfläche platzieren möchtest / automatisch machst, gehört in den meisten Fällen in die Businesssicht.
Natürlich alles was geht 😃

Jetzt fragst du dich vielleicht wo deine Task Klasse hingehört.
Nun ich halte es so, dass ich eine eigene Bibliothek mit allen Entities / Data Objects / Businessobjects anlege und diese in jeder Schicht referenziere.

Also ist deine schlussendliche Datenquelle (DataSet, Xml, SqlServer, ....) nur im DataLayer bekannt und sonst nur überall deine Businessobjekte + Interfaces.

Ich hoffe das hilft dir weiter.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo TripleX, (Haha! Richtig geschrieben!

Könnte man so machen. Aber wozu brauchst du Zugriff auf die Tabelle selber?
Folgendes müsste doch ausreichend sein:

  • das DataSet (mit allem drum und dran)
  • eine Schnittstelle für die Einträge (Row)
  • eine Klasse für den Zugriff
    (+ eine Schnittstelle für den Zugriff, auch der kann per Schnittstelle abstrahiert werden )

Auch hier vielleicht eine Schnittstelle für TaskManager. Aber anstelle eines TaskManagers würde ich gleich eine Klasse Calendar machen 😉.

Summa sumarum:


namespace TripleX.Calendar.DataAcces
{
  internal class CalendarDataSet;
  internal class ...; // rest vom DataSet halt
  public interface ITaskData; // oder so ähnlich
  public class TaskDataManage; 
  public interface ITaskDataManage; // optional, erfordert Microkernel
}

namespace TripleX.Calendar.Business
{
  public interface ITask;
  internal class Task;
  public class Calendar; // sprich: TaskManager
  public interface ICalendar; // wäre schon besser das zu haben. Dann kann Calendar auch internal werden.
}

Gruß
Juy Juka

5.941 Beiträge seit 2005
vor 15 Jahren

Hallo JuyJuka

Was hältst du vom Artikel von Robert und von meinem Text?
Bei deiner Aufzeigung hat es mir zuviel drin, das geht ja auch mit weniger genau so gut.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo Peter Bucher,

Den Artikel von Robert hab ich nur überflogen, da ich dachte, dass er sich an Einsteiger richtet und vor allem, weil ich im Büro auch noch Sachen für die Kunden erledigen muss - Nervig so was 😄 .

Bei deiner Aufzeigung hat es mir zuviel drin, das geht ja auch mit weniger genau so gut.

Es enthält nur alle nötigen Klassen und je eine Schnittstelle dazu. Worauf soll man da noch verzichten können?

Im Detail:*CalendarDataSet + Co. = Das braucht man halt alles, wenn man sich von VS die Datenzugriffsschicht erstellen lässt. Pech gehabt. *ITaskData = Nur das interface zur DataRow-Ableitung für Tasks. Schließlich wird das nach Außen gegeben und da soll ja im besten Fall kein konkreter Typ auftauchen. *TaskDataManager = Fasade für die Datenzugriffsschicht, nich mehr und nicht weniger. Irgend wo braucht der Entwickler von außen halt zugriff. *ITaskDataManager = Optional und nur ein interface für TaskDataManager. Aber nach Außen wollen wir ja keine konkreten Typen. *Task = Funktionsklasse für Tasks (Anfangsdatum vor Enddatum prüfen, etc.) *ITask = Wieder nur ein interface, diesmal für Task. *Calendar = Funktionsklasse zum Verwalten von Tasks (z.B. Verschieben, Verketten, ... was weiß ich den, ist TripleX's Projekt 😃 ) *ICalendar = Das obligatorische interface.

Aber ich glaube hier prallen mal wieder unsere Welten auf einander ("Daten- und Funkionsklassen" VS "Daten und Funktion bilden eine Einheit").
Für mich ist es 100%ig undenkbar irgend eine "Säule" mit den Daten-Klassen zu haben, paralell zu meinen Schichten. Das ist aus meiner Sicht falsch, da die Daten ja nicht öffentlich sein sollen und durch eine Klasse geschütz werden müssen und dass natürlich nur die Klasse kann, die auch die Funktion der Daten kennt.
Warst nicht du das, der sagte "Ich programmiere am liebsten Konkret."? Dann wären wir auch da wieder "Gegenpole" bei mir verwendet eine Klasse im Normallfall keine andere Klasse direkt sondern ausschließlich Schnittstellen (wo es geht auch bei den Framework-Klassen).

Gruß
Juy Juka

[EDIT]
Jetzt hab ich den Artikel von Robert doch mal gelesen - war ja garnicht so lang und ausschweifen wie ich dachte.
Guter Überblick und einfach geschrieben - sehr lobenswert.
Das beispiel ist gut gewählt, jedoch bräuchte man dringend etwas weiterführendes, da man nach ca. 1-2h am Ende mit den gelieferten Infromationen ist und dann verloren rum steht (hab ich irgend welche Link's übersehen?).
Das Beispiel ist ganz gut, jedoch geht es auch wieder richtung "Daten- und Funktionsklasse" (erkennt man nicht sofort), das ist zwar Nachvollziehbar aber für mich halt falsch/ein Fehler.
[EDIT]

5.941 Beiträge seit 2005
vor 15 Jahren

Hallo JuyJuka

Für mich ist es 100%ig undenkbar irgend eine "Säule" mit den Daten-Klassen zu haben, paralell zu meinen Schichten. Das ist aus meiner Sicht falsch, da die Daten ja nicht öffentlich sein sollen und durch eine Klasse geschütz werden müssen und dass natürlich nur die Klasse kann, die auch die Funktion der Daten kennt.

Verstehe ich nicht wirklich.
Was soll den öffentlich sein und warum nicht die Datenklassen?

Warst nicht du das, der sagte "Ich programmiere am liebsten Konkret."?

Hmm, ich wüsste nicht wo und in welchem Kontext.

bei mir verwendet eine Klasse im Normallfall keine andere Klasse direkt sondern ausschließlich Schnittstellen (wo es geht auch bei den Framework-Klassen).

Das ist eine gute Praxis ja, ich wüsste aber nicht wo ich dagegen verstosse.

Das Beispiel ist ganz gut, jedoch geht es auch wieder richtung "Daten- und Funktionsklasse" (erkennt man nicht sofort), das ist zwar Nachvollziehbar aber für mich halt falsch/ein Fehler.

Was hier falsch / Fehler ist, würde mich interessieren,
ich kann deine Aussage nicht nachvollziehen.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo Peter Bucher,

Datenklassen an sich stellen für mich schon Fehler da. Daten und Funktion müssen eine Einheit bilden.

Warst nicht du das, der sagte "Ich programmiere am liebsten Konkret."?
Hmm, ich wüsste nicht wo und in welchem Kontext.

Kann mich auch vetuen.

bei mir verwendet eine Klasse im Normallfall keine andere Klasse direkt sondern ausschließlich Schnittstellen (wo es geht auch bei den Framework-Klassen).
Das ist eine gute Praxis ja, ich wüsste aber nicht wo ich dagegen verstosse.

Hab ich nie behauptet. Falls es so rüber gekommen ist: Sorry.

Das Beispiel ist ganz gut, jedoch geht es auch wieder richtung "Daten- und Funktionsklasse" (erkennt man nicht sofort), das ist zwar Nachvollziehbar aber für mich halt falsch/ein Fehler.
Was hier falsch / Fehler ist, würde mich interessieren,
ich kann deine Aussage nicht nachvollziehen.

Das gleiche, wie ich im allgemeinen an deinem Programmierstiel als Fehler sehe (ich seh das so! vermutlich jedoch nicht jeder):
Daten und Funktion bilden eine Einheit. Von dir so genannte "Datenklassen" dürften überhaupt nicht existieren. Um mal bei Beispiel von Rober zu bleiben:*IUserServie.GetFriendsFromUser gehört für mich definitiv in die User-Klasse! *Die User-Klasse müsste eigentlich für mich in der Funktionsschicht liegen und neben einem Zustand auch alle Funktionen mit liefern.

Weichen wir zu weit vom eigentlichen Thema ab?

Gruß
Juy Juka

5.941 Beiträge seit 2005
vor 15 Jahren

Hallo JuyJuka

IUserServie.GetFriendsFromUser gehört für mich definitiv in die User-Klasse!

Die User-Klasse müsste eigentlich für mich in der Funktionsschicht liegen und neben einem Zustand auch alle Funktionen mit liefern.

Das was du beschreibst geht in Richtung Active Record Pattern.
Es gibt aber noch andere Vorgehensweisen, wenn dir bspw. POCO was sagt?

Weichen wir zu weit vom eigentlichen Thema ab?

Langsam, aber es könnte immer noch nützlich für den Threadersteller sein, wenn da ein Ergebnis rauskommt.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo Peter Bucher,

Das was du beschreibst geht in Richtung Active Record Pattern.
Es gibt aber noch andere Vorgehensweisen, wenn dir bspw. POCO was sagt?

Wieso nur geht? Das ist Activ Record - auch wenn ich nicht glaube, dass dies bereits ein Pattern ist.
Alles anderen als dieses Vorgehen verstösst meines Wissens und Verständnisses nach gegen eine der Grundideen der Objektorientierung :
Daten und Funktionen bilden eine Einheit.

Hast du auch so ein stechendes Dejavuê ?

Gruß
Juy Juka

5.941 Beiträge seit 2005
vor 15 Jahren

Hallo JuyJuka

Du hast mir leider meine Frage nicht beantwortet.
Und auch wieso der zweitere Ansatz immer stärker vertreten ist.
Also eine Klasse zu haben, die mit den Objekten arbeiten.
Und eine Klasse zu haben, die das Objekt an sich repräsentiert, ohne Logik.

Ich sehe nicht ein, inwiefern der andere Ansatz gegen die Objektorientierung verstösst.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo Peter Bucher,

Deine Frage kann/konnte ich leider nicht erkennen.
(hey! Beitrag editieren ist unfaier)

Die Grundidee ist doch schon der Wiederspruch: Klasse O enthält alle Daten und Klasse F enthält die Funktion zu den Daten.
Genau das sollte ja in der Objektorientierung nicht der Fall sein. Die Daten sollten eigentlich mit der Logik zusammen sein, damit sie sich selbst Schützen können. Die Klasse, die nur die Daten darstellt müsste ja theoretisch alles Mögliche in den Feldern zulassen. Beispiel:

class Task{public DataTime Von{get;set;} public DateTime Bis{get;set;}}

Hier dürfte keine Prüfung in den Properties erfolgen, da es ja Logik ist, das "Von" kleiner als "Bis" sein muss.

Gruß
Juy Juka

365 Beiträge seit 2004
vor 15 Jahren

Hallo miteinander,

das mit dem Objekt und den Funktionen finde ich jetzt sehr interessant und ich klinke mich hier mal ein, wenn ich darf 😉
Ich stelle mir nämlich hin und wieder auch die Frage, was ich als Funktion auf ein Objekt schreibe und was besser als Service Klasse um das Objekt herum. Ich habe für mich folgende Erklärung gefunden. Wenn ich eine Funktion habe, die direkt Einfluss auf die Daten des Objekts hat, dann packe ich diese Funktion auch direkt in das Objekt.

Einfaches kleines Beispiel:

In meinem lieblings MediaPlayer (http://banshee-project.org/) gibt ein Track Objekt, das wenn ich mich recht entsinne die Methoden IncreasePlayCount() und IncreaseSkipCount() bereitstellt. Es macht für mich absolut Sinn, diese Methoden direkt auf dem Track Objekt anwenden zu können.

Wenn die Funktion aber eher etwas mit dem Objekt macht, ohne dessen Daten zu verändern, dann packe ich die Funktion eher in eine Service Klasse.

Konkretes Beispiel:

Wir haben ein EDI Programm bei uns in der Firma, welches wir gerade überarbeiten. Kurze Erklärung zu EDI, falls es jemand nicht kennt: Belege (Rechnungen, Aufträge etc.) werden in einem speziellen Industriestandard zwischen Geschäftspartnern übertragen.

Nun ist es hier so, das unsere Kunden (Lebensmittelbranche) EDEKA, Rewe, Kaufhof so ein Belegobjekt alle ein klein wenig anders haben wollen...klar, alles EDI Standard aber der eine will, dass die Zeilen mit der Artikelnummer anfangen, der nächste will lieber mit der Menge beginnen etc.

Dafür haben wir verschiedene Implementierungen von IServiceProvider, die alle eine Convert(Document currentDocument) Methode bereitstellen.

Ich würde einen Beleg also so übertragen: IServiceProvider.Convert(myDocument).

War das jetzt totaler Stuss oder ergibt das für euch Sinn?

Gruß

Christoph

M
130 Beiträge seit 2007
vor 15 Jahren

Hallo Christoph,

das hört sich nach Contract First Design und evtl in Verbindung mit einem Microkernel an. Finde ich persönlich super. Wartbarkeit und lose Koppelung macht genau dort Sinn. Gerade das einzelne Komponenten ("Implementierungen") via Konfigurationsdateien ausgetauscht werden können vereinfacht die Wartbarkeit enform. 👍

Viele Grüße,
moq

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo bvsn, Hallo moq,

Das ist ja kein Privat-Thread von Peter Bucher und mir. Andere Meinunge/Kommentare sind gern gesehen.

Unter Umständen können solche "Service"-Klassen auftreten, stimmt. Ich finde es nur falsch, wenn man seine Klassen gleich von anfang an in Stücke reißt. Bei Funktionen, die über die eigenltiche Aufgabe der Klasse hinaus gehen, ist es manchmal wirklich sinnvoll das in einer anderen Klasse zu lösen.
Beispiel: Ein Auftragsklasse, die den Ablauf und ihren Status verwaltet, eventuell noch ein paar Daten prüft. Eine Beleg zu dieser Klasse/zu diesem Objekt zu drucken kann jetzt bereits die Aufgabe einer anderen Klasse darstellen.

Zu dem Konkreten EDI Beispiel hätte ich eine andere Lösung. Da ich auch sehr viele Schnittstellen (VDA, UPS Worldship, etc.) schreibe sehe ich die Formate der Empfänger weniger als Funkton sonder ehr als Daten. Ich verwende mein Komponente JuyJuka.Reports gebe die Objekte und das benötige Format rein und fertig:


JuyJuka.Reports.IReport report = Microkernel.Create<JuyJuka.Reports.IReport>();
report.Objekt = auftrag;
report.Format = System.IO.File.ReadAllText(Properties.Settings.ReportFile);
System.IO.File.AppendAllText(output,report.Compile());

@AuftragsNummer@;@Name@;@Strasse@;@Postleitzzahl@;@Ort@;@Land@@REPORT(ReportFile2,Positionen@
@Artikelnummer@;@Menge@

Für einen Anderen Empfänger ändere ich nur die Formatbeschreibung in ReportFile2

@Menge@;@Artikelnummer@

Gruß
Juy Juka

365 Beiträge seit 2004
vor 15 Jahren

Hallo,

@moq

Ich muss gestehen, dass mir der Begriff "Contract First Design" bisher neu war und ich diesbezüglich erstmal etwas nachlesen sollte. Dieser Link scheint recht gut zu sein:

http://weblogs.asp.net/ralfw/archive/2006/08/02/Dynamic-component-binding-made-easier-2D00-An-easy-to-use-Microkernel-to-help-reap-Contract_2D00_First_2D00_Design-benefits-in-.NET-programs.aspx

Um es mal so allgemein wie möglich zu formulieren, ich versuche in jedem Fall so zu entwickeln, dass die Komponenten leicht austauschbar und erweiterbar sind.

@JuyJuka

Ich finde es nur falsch, wenn man seine Klassen gleich von anfang an in Stücke reißt.

Da möchte ich dir voll und ganz zustimmen. Ich würde auch auf gar keinen Fall künstlich die Funktionen vom Objekt trennen. Das zerstört den schönen OOP Ansatz von Anfang an und im Grunde kann ich dann auch direkt auf einer DataRow arbeiten.

Interessanterweise, hatte ich zu Anfang einen ähnlichen Ansatz in Erwägung gezogen. Das bedeutet, dass jeder Belege eine Eigenschaft ServiceProvider (also mehr oder weniger das Format um es auf deinen Code zu projezieren) bereitstellt und diese dann auf einen IServiceProvider gesetzt werden kann. Ich hätte dann direkt auf dem Beleg Convert() aufrufen können und er hätte die Konvertierung mit dem gesetzten ServiceProvider vorgenommen.

Ich bin allerdings davon abgekommen. Unter anderem z.B. auch deshalb, weile jede EDI Datei den Header ein bisschen anders hat - dies ist auch wiederum ServiceProvider bezogen. Also stellen alle ServiceProvider noch eine GetHeader() Methode bereit. Dies hat nun ganz eindeutig nichts mehr mit dem Beleg zu tun und insofern, finde ich, dass die Benutzung so irgendwie straighter ist:

Eine EDI Datei wird nun also so vorbereitet (stark vereinfacht):

IServiceProvider.GetHeader();
IServiceProvider.Convert(doc1);
IServiceProvider.Convert(doc2);
IServiceProvider.Convert(doc3);
...(natürlich im Schleifchen)

Gruß

Christoph

R
115 Beiträge seit 2006
vor 15 Jahren

Die Diskussion ist sehr interessant - genau sowas hab ich auch schon mit mehreren Kollegen geführt.
Ich find die Idee von "Service"/"Manager" Klassen besser - sodass man eine klare Trennung zwischen Daten- und Funktionsklasse hat.

Ich hatte bei einem sehr alten Projekt in die User-Klasse alles reingeschmissen (Stichwort Active Record). Allerdings wird dadurch die User Klasse immer größer. Auch wenn man nun die pure größe einer Datei durch partial etc. "verringern" kann, macht es das ja nicht gleich schön 😉
Ein andere Problem hat sich dann später ergeben: Manche Klassen hatten plötzlich "Insert,Update..." oder andere Logiken drin und manche halten einfach nur Daten. Am Ende hatte keiner mehr genau durchgesehen. Unit Tests oder Dependency Injection waren da für mich auch noch ein Fremdwort.

Aus pragmatischer Sicht find ich daher das mit den Service/Manager praktischer: Klare Zuständigkeiten. Dependency Injection ist wesentlich "durchschaubarer" und das testen ist IMHO einfacher.
Ich hab auf der einen Seite dumme POCOs, auf der anderen Seite intelligente Services.

Ob das klar nach "OOP" geht oder nicht, ist aus meiner Sicht dann garnicht so wichtig. Es gibt auch viele andere Design Prinzipien die man beherzigen kann. Bei der vermischung von Daten- und Funktionsklassen hat man nämlich sehr schnell das Single Responsible Principle (SRP) verletzt. Auch wenn man hier die Definition von "Verantwortlichkeit" sich so zurecht legen kann, wie es gerade passt.

Momentan hab ich bei einem Projekt die ganzen Useraktion sogar in getrennten Services:

  • RegistrationService
  • ActivationService
  • ProfileService
  • ...
    ... weil mir nämlich der "UserService" zu ungenau und groß war und das testen langsam unangenehm.

Solange das Team eine einheitliche Linie findet und es klare Zuständigkeiten gibt und auch die Testbarkeit gegeben ist, kann man das jedoch machen wie man möchte - beide Varianten kann ich verstehen. Insbesondere bei Desktopprogrammen mit den INotifyPropertyChanged Events etc. hat vielleicht die Variante von Juy auch etwas mehr Charm. Ich sprech aus der Sicht eines Webentwicklers.

@Juy: Danke zudem für deine Lob und deine Kritik an dem Artikel den Peter von mir verlinkt hat 😃

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo Reman,

Du hast recht, eine Klasse muss ihre Grenzen kennen aber man sollte die eine Aufgabe einer Klasse auch nicht zu klein Fassen.
Wenn jetzt jede Klasse nur die Aufgabe einer Methode übernimmt (z.B. Benutzer Registrieren, Benutzer Aktivieren, etc.), kann ich auch gleich wieder prodzedural Programmieren, weil alle Klassen vollen Zugriff auf die Daten brauchen und jeder Klasse genau wissen muss, was die anderen Klassen tuen (bzw. der Entwickler muss es wissen, wärend er die Klasse schreibt).
Genau das beschriebene Szenario sollte durch die Objektorientierung (durch Kapselung und die Einheit von Daten und Funktion) verhindert werden.
Das fett Angezeigte soll nur die kritischen Stellen hervor heben.

Um beim Beispiel zu bleiben: Eine Aufgabe wäre für mich das Verwalten eines Benutzers. Also Anlegen, Löschen, Aktivieren, Deaktivieren, Datenvollständigkeit prüfen, etc.
Nicht die Aufgabe der Benutzer-Klasse wäre es:

  • Den Benutzer an- und abzumelden.
  • Belege für den Benutzer drucken (z.B. Mitgliedsausweis).
  • Den Benutzer einer Gruppe zu zuordnen.

Das große Kunststück ist es eben, zu sagen: Was ist die Aufgabe der Klasse und was nicht.
Jeder einzelne Funktion (sry, mir fällt keine bessere Bezeichnung ein) in eine seperate Klasse zu packen ist auf jeden fall zu ... fein.

Es gibt auch viele andere Design Prinzipien die man beherzigen kann.

Tja, nur ist Objektorientierung (mit Aspektorientierung) nicht um sonst der "neuste Schrei". Die Anderen Paradigmen/Prinzipien haben auch Ihre vorteile, doch werden diese im Normalfall in der Objektorientierung vereint.

Gruß
Juy Juka

3.003 Beiträge seit 2006
vor 15 Jahren

Noch ein kurzer Kommentar von mir:

Active Record bzw. gemeinsame Kapselung von Funktion und Eigenschaften mag hin und wieder sinnvoll sein, ist es jedoch nicht immer. Im Gegenteil kann es sehr sinnvoll sein, Funktionen an andere Klassen auszulagern.

-> Verhalten einer Klasse zur Laufzeit ändern
-> u.U. schlechte Erweiterbarkeit der Funktionalität

Methoden, i.e. das Verhalten von Klassen in extra Verhaltensobjekte zu kapseln, ist oft eine sehr gute Idee und widerspricht in keinem Fall der Objektorientierung. Stichwort Strategie-Muster.

Active Record ist eine Idee, die sich anbietet, wenn man Daten modelliert. Nur, deshalb ist es weder die einzige objektorientierte Herangehensweise, noch in jedem Fall die beste (rein gefühlsmäßig würde ich sogar behaupten - je komplexer das Szenario, desto weniger gut geeignet...).

Was die fetten Stellen im Text oben angeht: wieso zum Teufel verlangst du eigentlich sonst immer, dass gegen Schnittstellen programmiert wird (was ja richtig ist), aber bei diesem Ansatz lässt du komplett hinten runterfallen, dass man auch Verhaltensklassen gegen Schnittstellen programmieren kann. Alles, was den Daten- oder Controllerklassen dann bekannt sein muss, ist das Interface.

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)

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo LaTino,

Hab mich schon gefragt, wann endlich das richtige Pattern fällt 😃 Glückwunsch.
Der große Unterschied zwischen diesem "Service"-Zeug und dem Strategie-Pattern ist, dass die "Strategie" beim Pattern in der Klasse enthalten ist und die Klasse troz allem gekapselt bleibt. Die Methode(n) werden mit diesem Pattern einfach nur als Daten behandelt, was auch sinnvoll ist und im normalfall keine Regeln der Objektorientierung bricht.

Das "Service"-Zeug hingegen steth (so weit mein verständniss für Objektorientierung geht) in konkretem und exakten Gegensatz zu einer der Hauptregel der Objektorientierung (Daten+Fukion=Eine Einheit).

rein gefühlsmäßig würde ich sogar behaupten - je komplexer das Szenario, desto weniger gut geeignet...

Die Objektorientierung wurde genau für's einfacher Modelieren/Programmieren von komplexen Systemen entwickelt. Und ich empfinde es auch oder gar speziell bei komplexen Sachverhalten einfacher, wenn jedes "Objekt" seine Aufgaben auch selber erledigen kann.

Nur um Missverständnissen vorzubeugen: Nicht zu sehr auf "Activ Record" versteifen. Ein Objekt kann auch eine Liste darstellen und die Aufgaben der Liste übernehmen. "Active Record" wird ja (zumindest von mir) sehr stark mit EINEM Objekt verbunden, stimmt aber in dem Kontext wie hier der Name "Activ Record" verwendet wird nicht.

[EDIT]
Gleichzeitig Schreiben ist doof 😃

Was die fetten Stellen im Text oben angeht: wieso zum Teufel verlangst du eigentlich sonst immer, dass gegen Schnittstellen programmiert wird (was ja richtig ist), aber bei diesem Ansatz lässt du komplett hinten runterfallen, dass man auch Verhaltensklassen gegen Schnittstellen programmieren kann. Alles, was den Daten- oder Controllerklassen dann bekannt sein muss, ist das Interface.

Ob man jetzt bei den fetten Stellen Schnittstellen hat oder nicht ändert nichts. Ob ich jetzt über eine Schnittstelle oder einen konkrtetn Typ ":) hab dich lieb" in das Postleitzahl-Property schreibe macht für den armen Postboten auch keinen Unterschied mehr.
[/EDIT]

Gruß
Juy Juka

3.003 Beiträge seit 2006
vor 15 Jahren

Das "Service"-Zeug hingegen steth (so weit mein verständniss für Objektorientierung geht) in konkretem und exakten Gegensatz zu einer der Hauptregel der Objektorientierung (Daten+Fukion=Eine Einheit).

Das ist deine Regel, keine der Objektorientierung 😉. Der Unterschied Service<->Strategie ist, wenn die Services richtig implementiert und angesprochen werden, keiner. Wenn überhaupt, ist die Orientierung hin zu Services die konsequente Weiterentwicklung in der Kapselung von Daten, und zwar eine rein räumliche, keine logische.

Und ich empfinde es auch oder gar speziell bei komplexen Sachverhalten einfacher, wenn jedes "Objekt" seine Aufgaben auch selber erledigen kann.

Das mag sein, aber wenn du jeder Klasse überlässt, wie sie ihre Daten manipuliert, bekommst du ein massives Problem mit der Wartbarkeit. In komplexen Klassenhierarchien müsstest du dann bei einer Änderung der Datenbehandlung ganz genau wissen, welche Stellen geändert werden müssen - zonk, sozusagen. Genau der Grund, wieso ich Active Record bei komplexen Szenarien für weniger geeignet halte. Eine Extrahierarchie für die Datenmanipulation ist naturgemäß leichter veränderbar und wartbar.

Du machst da noch einige Aussagen, bei denen sich mir einfach die Nackenhaare aufstellen - würde aber den Rahmen hier leider sprengen 😄. Nur eines:

Die Anderen Paradigmen/Prinzipien haben auch Ihre vorteile, doch werden diese im Normalfall in der Objektorientierung vereint.

Falsch, falsch, FALSCH. Das beweist dir jeder PROLOG-Programmierer in 5 Sekunden. There is no silver bullet. OO ist NICHT das Allheilmittel, das alle anderen Paradigmen ablöst, weil es Probleme besser/schneller/effizienter löst. OO ist ein Werkzeug wie jedes andere. Ich mein's nicht böse, aber ich habe ein wenig den Eindruck, du argumentierst aus der Sichtweise: "Wenn man nur einen Hammer hat, sieht alles wie ein Nagel aus."

Gruß,

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)

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo LaTino,

Das ist deine Regel, keine der Objektorientierung

Definitiv nicht.

Objekte definieren sich durch ihren Zustand (die Attribute) und ihr Verhalten (Methoden).

Die Objektorientierung, kurz OO, ist ein Ansatz zur Entwicklung von Software, der darauf beruht, die zu verarbeitenden Daten anhand ihrer Eigenschaften und der möglichen Operationen zu klassifizieren.
...
Als Weiterentwicklung von Datenstrukturen (aus mehreren Komponenten - auch unterschiedlichen Datentyps - zusammengesetzte Variablen) können Objekte auch noch über Eigenschaften und Methoden verfügen, die durch Programmcode in der Klasse festgelegt sind.
...

Blöd dass ich grad kein besseres Buch da habe.

Dies ermöglicht IHnen Klassen zu definieren, in denen das Verhalten (Methoden) und die Attribute (die Daten) eines Objekts zusammen gefasst sind.

Okey, ich muss hier ja nicht den Klotz-Kopf spielen. Es ist keine der grundlegensten Regeln der Objektorientieren. Wenn man sich aber nicht daran hält, verstöst man im normalfall gegen die Regel der Kapselung (an der wird hir ja wohl niemand zweifeln, ne?).

... die Orientierung hin zu Services die konsequente Weiterentwicklung in der Kapselung von Daten...

Was soll da eine Weiterentwicklung sein? Dadurch werden höchstens einfer von zwei Effekte erziehlt:

  • Die Kapselung der Daten wird aufgebrochen.
  • Die Logik (primär die Datenvalidierung) wird auf zwei Klassen (die Daten-Klasse und die Funktions-Klasse) aufgeteilt.
    Also ich seh da keine Weiterentwicklung oder bin ich blind?

Eine Extrahierarchie für die Datenmanipulation ist naturgemäß leichter veränderbar und wartbar.

Wie soll so eine "Extrahierarchie" aussehen? Wie soll man die Zuordnung machen? Wer prüft, dass die "Extrahirarchie" auch aufgerufen wird? Was soll einfacher Wartbar sein?

.. jeder Klasse überlässt, wie sie ihre Daten manipuliert ...

Naja, nicht jede Klasse. z.B. Die Gültigkeit einer Auftrags-Position würde ich (in bestimmten Szenarien) im dazu gehörigen Auftrags-Kopf prüfen. Man muss und kann nicht jede Aussage 100%ig nehmen, es giebt immer seltene und dumme Fälle, in denen man nicht drum rum kommt, die eine oder andere Regel zu verletzen. Man sollte sich dabei einfach nur Schuldig fühlen.:)

There is no silver bullet.

Du kannst mich gern immer mal wieder zurück hohlen. Ich bin ja auch nicht Perfekt und kann sonst wo hin abdriften. 😃

Gruß
Juy Juka

4.207 Beiträge seit 2003
vor 15 Jahren

Hallo Peter Bucher,
[...]
Daten und Funktionen bilden eine Einheit.

NEIN!

Jedenfalls nicht zwingend. Gerade zB bei Businessobjekten macht es IMHO nicht zwingend immer Sinn, Daten und Funktionen in eine Klasse zu kapseln. Andernfalls erschwert man sich die Versionerung unter Umständen enorm.

Korrekter wäre IMHO, hier zu sagen: Die Datenklassen bieten das Interface (!) für die Funktionen an, leiten diese Aufrufe intern aber an ein Service-Objekt weiter, das die konkrete Implementierung bietet.

Wissensvermittler und Technologieberater
für .NET, Codequalität und agile Methoden

www.goloroden.de
www.des-eisbaeren-blog.de

3.003 Beiträge seit 2006
vor 15 Jahren

Ich zitier mal nicht alles einzeln, wird - fürchte ich - zu unübersichtlich.
Also: eine Klasse besteht aus drei Teilen:

a) ihre Daten
b) ihr Verhalten
c) ihre Schnittstellen

EDIT: damn you , Golo! Ich schreib's trotzdem nochmal 😉

Einverstanden soweit? Ich fordere ja nicht (eigentlich forder ich gar nix^^), dass die Verhaltensweisen (sprich: Methoden) grundsätzlich aus der Klasse rausgeworfen werden sollen. Aber IMO spricht absolut nichts dagegen, die nicht-privaten (protected, internal, public) Methoden durch eigene Klassen zu realiseren. Beispiel aus dem Leben: eine "meiner" Applikationen hier benutzt eine relativ komplexe abstrakte Fabrik, um Klassen der Modellschicht zu erzeugen. Vereinfacht gesagt besitzen die erzeugten Modellklassen sämtlich Schnittstellen für die CRUD-Operationen. Die jedoch werden (-> Strategy) durch Klassen einer anderen Familie durchgeführt. Die abstrakte Fabrik hat die Kontrolle und entscheidet, welche Klasse welche Methoden bekommt - die Klasse selbst weiß davon nichts, und soll das auch nicht wissen. Die Klassen im Beispiel bestehen natürlich immer noch aus den drei Komponenten oben, aber der wesentliche Kern ihrer Methoden ist ausgelagert. Und wieso? Weil mir das erst die Freiheit gegeben hat, die CRUD-Funktionalität komplett auszulagern in einen Service, der eben nur dafür zuständig ist. Dem sind die Daten wiederum völlig egal, er tut, was die Verhaltensklassen ihm sagen.

Der Vorteil von einem Service liegt nicht in der Architektur, das hätte man so auch ohne Service machen können. Aber der kann vn mehreren Applikationen angesprochen werden, lässt sich in bestehende Prozesse anbinden, auch unternehmensübergreifend, und ist einzeln leichter wartbar. Von Spielereien wie extrem einfacher Lastverteilung etc. mal abgesehen. Wie ich schrieb, die Vorteile liegen eher in der räumlichen Trennung, die logische Trennung geht genauso auch ohne Service.

In Sachen Wartung und Erweiterbarkeit ist dieses System jedem System, indem jede der Modellklassen ihre eigene Implementierung der CRUD-Funktionen (und sei es nur ein Serviceaufruf) hat, himmelweit überlegen. Ein anderer Entwickler kann die kompletten Zugriffsmethoden überarbeiten, ohne auch nur den Hauch einer Ahnung zu haben, wie die Klassen konkret aussehen. Er muss nur die Schnittstellen kennen (tatsächlich läuft das auch so).

Die konsequente Weiterentwicklung besteht darin, dass man (freigegebene) Methoden von Klassen als das erkennt, was sie sind: wiederum Familien von Objekten, und das man das auch entsprechend modelliert. Wenn man sich auf dieser Ebene befindet, sollte man sich nicht schuldig fühlen, sondern sich bewusst werden, dass man kein Prinzip der OO verletzt hat, sondern sie entsprechend ihrer Stärken eingesetzt hat. Pragmatismus gehört dazu 😃. Wenn es funktioniert, Objekte verwendet, leichter wartbar und übersichtlicher ist als die herkömmliche Herangehensweise, dann ist es die bessere OO-Praktik.

Das meinte ich: OO ist ein Werkzeug. Es ist nicht starr, sondern soll helfen, in bestimmten Bahnen zu denken. Und Patterns helfen eine Ebene höher, zu erkennen, in welchen Bahnen man denkt, und zu reflektieren, welche protenziellen Probleme damit zusammenhängen können. Wenn man aber das konkrete Problem, das man hat, an OO und Patterns anpasst, damit man es besser lösen kann - falsche Herangehensweise. (ist ja schön, dass dann alles wie aus dem Lehrbuch ist. Leider löst es nicht mehr das Problem, das man ursprünglich hatte...)

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)

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo LaTino, Hallo Golo Roden,

Gegen Strategie-Pattern und interne Weiterleitung und so sag ich garnichts. Ist ja nicht umsonst ein Pattern.
Es geht mir nur um wirklich getrennte Daten- und Funktionsklassen, in folgendem Sinne:


MeineNeueKomischeServicKlasse.ManipuliereMeinObjekt(obj);
MeineNormaleServiceKlasse.VerarbeiteMeinObjekt(obj); // fliegt raus, weil die neue Komische Klasse die Daten "kaputt" gemacht hat und obj sich nicht dagegen wehrt.

Was ich bis jetzt an Service-Orientierung geshen/gelesen/verstanden habe, läuft das immer so.

Wenn Service-Orientierung ehr wie folgt gemeint ist:


obj.Manipuliere();


public class ...
{
  public Manipuliere()
  {
    this.Property = Service.Verarbeite(this);
  }
}

Hab ich irgend wo was falsch Verstanden.

Weil Servic-Orientierung und Datenobjekte für mich heißt (vielleicht bald "hieß"), das die Datenklassen ungefähr so gekapselt sind wie eine System.Data.DataTable.

@LaTino: An den Praktiker, ich bin jetzt einige Jahre im "realen Leben" angekommen und aus der Schule/Theori draußen. Und bis jetzt ist mir nur aufgefallen, dass es nur ein Problem beim umsetzen der Theorie in die Praxis giebt:
Den Programmiere.
Wenn man es nicht versucht/macht, klappt es nicht. Wenn man es aber macht (machen will), sehen echte Anwendungen auch aus wie aus dem Lehrbuch.
Natürlich hat man verloren, wenn man ca. 2GB Quellcode im "Theorie ist unmöglich"-Stiel gegenüber steht. Aber wenn man daran arbeitet wird auch der Berg mit den Jahren kleiner.

Naja, falls wir da weiter Diskotieren wollen, sollten wir wohl lieber in einen anderen Thread umziehen.

Ach ja, bevor ich's vergesse: Danke für die Diskusion. Endlich mal jemand, der "antwortet" 😃

Gruß
Juy Juka

3.003 Beiträge seit 2006
vor 15 Jahren

Denkbar zum Bleistift:


interface IDataUpdater 
{
  void Update(DataObject o);
}
abstract class DataObject 
{ 
  public abstract IDataUpdater Updater { get; set; }
  abstract bool Synchronized { get; }
  public abstract void MarkSynchronized();

  public Update()
  {
      if(!this.Synchronized)
      {
        Updater.Update(this);
        this.MarkSynchronized();
      }
   }
}
//service, wird vom in diesem Bsp vom Kontext aufgerufen
[ServiceContract]
public void UpdateAddress(DataObject myAddress)
{
   //Service entscheidet über die konkrete Updatemethode
   myAddress.Updater = MethodFactory.GetUpdateMethod(myAddress.GetType());
   myAdress.Update();
}

Ich denke mal, du hast ein Problem damit, dass die Methoden u.U. mehr Informationen brauchen, als die Klasse sonst öffentlich machen müsste. Ist das der Fall, stimmt aber schon etwas anderes nicht: das würde bedeuten, dass der Datenzugriff abhängig ist von internen Strukturen der Klasse. Mit anderen Worten: die Datenzugriffsschicht ist nicht entkoppelt vom Datenmodell.

Entkopplung geht einher mit der Veröffentlichung von Schnittstellen, so ist das nunmal. Obiges Beispiel kann man sicher noch auf ein halbes Dutzend bessere Varianten umschreiben, soll nur zeigen, dass es legitim sein kann, bestimmte Methoden nicht nur in andere Klassen, sondern auch auf Services auszulagern.

Ich halte nicht viel davon, der Datenklasse neben den Informationen, die sie haben muss, auch noch eine Servicereferenz zu geben. Das sollte Aufgabe des Kontextes (lies: Controllers) sein.

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)

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo LaTino,

Hab deinen Beitrag jetzt 5mal gelesen ... Ich blicks nicht! Weder das Beispiel, noch was du sagen willst. am Kopf kraz

Gruß
Juy Juka

3.003 Beiträge seit 2006
vor 15 Jahren

Hoffentlich weiß ich noch, was ich sagen wollte 😉.

Also. Das Beispiel sollte eigentlich klar sein, wenn nicht, auch nicht schlimm. Im Wesentlichen referenziert die abstrakte Datenklasse zusätzlich zu den eigentlichen Daten (Name, Vorname, etc.) nur ein Objekt einer Klasse, die die Update-Schnittstelle implementiert (IDataUpdater). Den Update-Vorgang gibt sie für alle Derivate vor (zentrale Stelle, um bspw. Metainformationen über den Objektzustand zu speichern). Möchte ich an dieser Stelle noch ein Logging einbauen - bittesehr. Eine Art rudimentärer Aspekt.

Der Service entscheidet anhand dem konkreten Objekt (im Beispiel eine Instanz einer offenbar von DataObject abgeleiteten konkreten Adressenklasse), welcher Updater zu verwenden sein soll. Er nutzt dafür eine Factory, die das entscheidet. Dann wird der öffentliche Updatemechanismus abgeschossen.

Vorteil: das Objekt hat keine Ahnung, wie es gespeichert wird.

Meine Annahme ist, dass du etwas dagegen hast, dass das Objekt bspw. seine ID öffentlich machen muss, damit der Updater das Ding vernünftig aktualisieren kann. Falls die ID sonst nirgends gebraucht wird, wäre das aus deiner Sicht vermutlich eine unnötige Verletzung des Geheimhaltungsgebots. Wenn du das so siehst: falsch, da die ID zum speichern benötigt wird, sollte sie das Objekt auch veröffentlichen und lediglich den Zugriff darauf selbstständig kontrollieren (via getter/setter). Die Lösung kann nicht sein, statt der kontrollierten Veröffentlichung eines Members der Klasse lieber eine eigene Methode zum Speichern hinzuzufügen.

Anders: die Methode zum Speichern hat in der Klasse nichts verloren. Sie wird woanders implementiert und durch die Klasse nur aufgerufen, anhand eines wohldefinierten Interfaces. Die Klasse selbst verwaltet nur ihre Daten, und die Information darüber, ob ein Speichern notwendig ist, und welche Dinge damit noch verbunden sind.

Beim Beispiel oben kannst du drei, vier Programmierer beschäftigen, die nur die Schnittstellenbeschreibungen der Klassen kennen.

Einen, der die DataObject-Implementierungen macht.
Einen, der die IDataUpdater-Implementierungen macht.
Einen, der die Services schreibt.
Einen, der den Kontext schreibt.

Und keiner davon muss von den inneren Abläufen beim jeweils anderen etwas wissen.

LaTino
*g* Mir fällt grad auf: Programmierer Nr. 5 könnte die Factory schreiben für die Updater. Und der muss auch nicht wissen, wie die anderen Klassen aussehen.

"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)

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo LaTino,

Das Beispiel ist echt unpassend. Wenn du den Update-Vorgang anhand des Konkreten-Typs der Ableitung einer abstraken Klasse definierst (die den allgemeinen Vorgang vorgiebt), ist das Strategy-Pattern ... ungünstig, das Template-Pattern wäre um ein vielfaches Einfacher und würde weniger Probleme bereiten.

Aber ich glaube zu verstehen, was du ausdrücken willst.

Ich versuch noch mal zusammen zu fassen, was mich an Service+Daten-Klassen stört:
Die Datenklassen können in so einem Szenario ihre Konsistenz nicht prüfen, da ja die Service-Klassen die Logik enthalten. Wenn Sie es doch tun, muss die Logik in n Klassen paralell geschrieben werden. (Wie soll man das Warten?)
Und wenn die Daten-Klasse ihre Konsitenz nicht prüft, dann haben wir ja jeglichen Vorteil durch Kapselung verloren.

Zurück zum Beispiel:
Also ich würde es ungefähr wie folgt implementieren. Vereint das beide Ansäzte? Ich glaube schon.


public abstract class DataObject
{
  [IOC] // Entweder hier über IOC den "Service" anfordern oder ...
  public abstract IDataUpdater Updater { get; private set; }
  public decimal Id{get; private set;}

  public Update()
  {
      if(!this.Synchronized)
      {
        if(this.Update==null) this.Update = Microkernel.Create<IDataUpdater>(this); // ... oder den "Service" hier über Microkernel anfordern.
        this.Id = this.Updater.Update(this);
        this.MarkSynchronized();
      }
   }
}

Gruß
Juy Juka

3.003 Beiträge seit 2006
vor 15 Jahren

Die Datenklassen halten Daten. Punkt, Ende, Aus 😉. Ob die Daten korrekt sind, wie sie gespeichert werden, geladen werden, gelöscht werden, persistent gehalten werden - das geht die Datenklasse nichts an. Man kann ihr, wenn es sein muss, mitteilen, wen sie zu fragen hat, wenn eine derartige Anfrage kommt.

Wichtig ist dann nur, dass sie alle Daten, die für Laden, Speichern, Löschen, Persistenzprüfung etc. pp. notwendig sind, gefälligst zu veröffentlichen hat. Das kann man aber gern und ohne schlechtes Gewissen an eine Basisklasse delegieren.

LaTino
EDIT: zu deinem Beispiel: nein, die Datenklasse sollte nichts von der Factory wissen (wer kam eigentlich auf die unglaublich /%!=! Idee, das Ding Microkernel zu nennen^^). Sie kennt ihre Daten. Sonst nix.

"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)

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo LaTino,

Da braucht man aber keine Klassen, da kann man eine Klasse => System.Data.DatRow verwenden. Und System.Data.DataRow ist eben so sicher, wie eine Scheune, bei der man das Tor abmontiert hat. 8)

So hat man eben wieder die ungekapselten, ungeschützten Daten wie bei nicht-objektorientierter Programmierung (C-struckt zum Beispiel). Und die Polimorphie und Vererbung hat man sich im endefekt mit teuren und komplizierten Mitteln drum rum gebaut (Siehe:

[ServiceContract]
public void UpdateAddress(DataObject myAddress)
{
   //Service entscheidet über die konkrete Updatemethode
   myAddress.Updater = MethodFactory.GetUpdateMethod(myAddress.GetType());
   myAdress.Update();
} 

).

[EDIT]Die "Daten-Klassen" (in meinem Fall ausschließlich System.Data.DataRow)sollen wirklich nichts vom Microkernel wissen. Microkernel ist übrigens ein Architektur-Pattern, das man so zu sagen als Großen Bruder vom Design-Pattern "Factory" bezeichnen kann (Archtektur>Design). Und IOC ist so zu sagen der verrückte Zwilling vom Microkernel 8) .[/EDIT]

Gruß
Juy Juka

3.003 Beiträge seit 2006
vor 15 Jahren

Wenn du mir erklärst, wie du in eine DataRow so etwas wie eine Statusüberwachung, logging und so weiter und so fort einbaust, gerne 😉. Die Datenklasse kann gern noch interne Abläufe haben. Aber alle Informationen, die für über Datenhaltung hinausgehende Funktionen notwendig sind, müssen veröffentlicht werden. Wenn ich einen Banker damit beauftrage, meine Geldanlagen zu führen, dann sollte ich ihm auch den Zugriff auf die notwendigen Ressourcen geben.

Die Sicherheit in OOP kommt nicht durch die Kapselung der Daten oder das Geheimhaltungsprinzip.

LaTino
PS: Microkernel ist für mich eine Betriebssystemarchitektur. Ich weiß, was du damit meinst: eine ich-kann-alles-FactoryMethod. Wenn man sich anschaut, welche Einsatzgebiete für FactoryMethod eigentlich vorgesehen sind, weiß man auch, wieso ich was gegen a) die Bezeichnung und b) den Einsatz habe.

"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)

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo LaTino,

Die DataRow soll eben keine Logik enthalten, dann hätte ich die Logik ja doppel in meiner Daten(zugriffsschicht)-Klassen und meinen Funktions-Schichtklassen.

Aber alle Informationen, die für über Datenhaltung hinausgehende Funktionen notwendig sind, müssen veröffentlicht werden.

Eigentlich sollten alle Informationen (also der gesammte Zustand eines Objekts) lesbar sein, jedoch nicht schreibbar. Für den Schreibzugriff reicht oft private und/oder protected.

Die Sicherheit in OOP kommt nicht durch die Kapselung der Daten oder das Geheimhaltungsprinzip.

Doch, eigentlich genau durch das Prinzip der Kapselung.

Gruß
Juy Juka

3.003 Beiträge seit 2006
vor 15 Jahren

Die Sicherheit in OOP kommt nicht durch die Kapselung der Daten oder das Geheimhaltungsprinzip.
Doch, eigentlich genau durch das Prinzip der Kapselung.

Öhm. Nein, seh ich anders. Kapselung dient der Verschleierung interner Vorgänge - aber nicht der Sicherheit. Ich vermute auch, das ist der springende Punkt, an dem sich die Geister scheiden. Für mich ist Sicherheit Aufgabe der Architektur (insb. der Kommunikation zwischen Bestandteilen der Architektur), nicht Aufgabe der Architekturbestandteile, und die öffentliche/interne/private Deklaration dient der klaren Trennung der Schnittstellen von internen Vorgängen, also die Zwischenschicht zwischen rein internen Verarbeitungen und Präsentation der internen Zustände nach außen. Nur hat das nichts mit Sicherheit zu tun (sondern eben mit Kapselung, i.e. Zuständigkeitstrennung).

LaTino
EDIT: sry, ich weiß die Edits sind doof. Zum Banker zurück: wenn ich will, dass er mein Konto verwaltet, braucht er Schreibzugriff. Die Sicherheit entsteht durch die Art und Weise, wie ich und er auf mein Konto zugreifen. Nicht dadurch, dass ich alles geheim halte und den Kram lieber selber mache und Banker lerne.

"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)

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo LaTino,

Das mit den Edits klappt schon.

Kapselung:

Kapselung ist auch ein wichtiges Konzept der objektorientierten Programmierung. Als Kapselung bezeichnet man den kontrollierten Zugriff auf Methoden bzw. Attribute von Klassen. Klassen können den internen Zustand anderer Klassen nicht in unerwarteter Weise lesen oder ändern. http://de.wikipedia.org/wiki/Datenkapselung_(Programmierung)

Kapselung und Architektur gehen hier Hand in Hand. Die Architektur kann (imo) nur manuell geprüft werden. Jedem Mensch kann ein Fehler unterlaufen und dann verstößt er (oder der desen Code er prüfen sollte) gegen die Architektur und/oder das Design. Wenn die Klassen das jetzt nicht abfangen, kann dieser Fehler sich weiter und weiter Ausbreiten (also die korrumpierten Daten meine ich, nicht den falschen Quellcode). Klar, jetzt kann man sagen: Dafür hab ich Unit-Tests. Aber auch Unit-Tests sind wieder von Hand erstellt und es ist schwer bis unmöglich alles damit Abzuprüfen. Außerdem werden die für ungeprüfte Daten zu schreibenden Unit-Tests exponenzial mehr und irgend wann schaft man diesen Aufwand einfach nicht mehr. Wenn man aber nur einen begrenzten Satz an "Zuständen" hat und die Objekte davon auch nciht abweichen, wird die anzahl der Unit-Test viel geringer und somit auch die Testbarkeit besser.

[EDIT]Verflixt, jetzt hab ich auch eins. Zum Banker: Ja, der Banker bekommt Zugriff auf meine Daten aber eben nur der Banker. Ich sage nicht: Hier Welt, meine Kontodaten und die Geheimzahlen - Einzugsermächtigung für [platzhalter] ist erteilt.[/EDIT]

Gruß
Juy Juka

3.003 Beiträge seit 2006
vor 15 Jahren

Dein Argument ist also (zugespitzt formuliert):

weil ich, wenn ich die Kommunikation zwischen Klassen sicher gestalten möchte, jede einzelne denkbare Kommunikation sicher gestalten müsste, und ich dabei Fehler machen kann, deshalb implementiere ich lieber Sicherungen in jeder einzelnen Klasse, die ich jetzt und in Zukunft schreiben werde, verlasse mich darauf, dass auch Fremdklassen diese Sicherungen haben, ändere, wenn ich einen anderen Sicherungsmechanismus implementieren möchte, jede einzelne meine Klassen, denn Fehler können bei internen Sicherungen nicht passieren.

Hm. Da hab' ich so meine Zweifel. Insbesondere was Wartbarkeit, Erweiterbarkeit und sonstige Tugenden der OOP angeht. Unit Tests für Interaktionen zwischen Klassen lassen sich übrigend super automatisieren.

WP widerspricht meiner Aussage nicht. Kapselung bildet die Zwischenschicht zwischen Zustand (interne Daten) und Zugriff (Schnittstelle). Da steht nichts von Sicherheit 😉.

Ich denke mal, da kommen wir auf keinen gemeinsamen Nenner. Ich glaube, dass jede Klasse ihre Aufgabe hat, und das war's. Du gibst einer Klasse lieber ein paar Aufgaben mehr, um mehr Kontrolle zu haben. In keinem der beiden Fälle werden irgendwo OOP-Prinzipien verletzt. Die Frage ist, ab welcher Komplexität sich welche Lösung anbietet - bei meinen RoR-Anwendungen arbeite ich auch nach dem Active-Record-Prinzip, aber da muss ich auch so gut wie nix abbilden an Funktion.

Ich denke, man kann einer Klasse auch zu viele Aufgaben zumuten und dadurch den Code unwartbar und fehleranfällig machen. Ich hab' Kollegen, die zu verteilten Code für undurchschaubar und fehleranfällig halten.

Mich gruselt jedenfalls schon, wenn ich nur


public static T Create<T>(object o);

lese. Von wegen Aufgabenverteilung und so.

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)

F
10.010 Beiträge seit 2004
vor 15 Jahren

Ihr beiden seit da anscheinend in verschiedenen Welten zu Hause.

In einem Windowsprogramm, das keine echte Schichtenarchitektur "benötigt",
das eh alles selber macht, ist es manchmal nicht notwendig sich um solche
echten Architekturfeinheiten zu kümmern, aber sobald man aus dem Dorf
( Singleprogram/ SmartClient ) herauskommt, wird es dann wichtig.

Weshalb gibt es denn POCO's, DTO's und CO ( absicht das mit den Abkürzungen )?
Damit man BusinessRulesEngines damit beauftragen kann z.b. die Validierung zu übernehmen.
Damit man per Webservice und CO die Daten stateless/stateful übertragen kann.
Damit man eben nicht von evtl im object vorhandenen evtl in manchen Situationen
hinderlichen Validierungen "gestört wird.
Per WorkFlowengines ganze abläufe zu automatisieren, die der Entwickler der Daten
garnicht vorsehen kann.

Aber das braucht man auch alles nicht, wenn man eine Singleuser oder SmartClient
Software hat, die sich um Domainübergänge und Co nicht scheren muss.

Ist zwischen euch die gleiche Diskusion wie bei AppServern.
Die sind in EnterpriseSoftware nicht wegzudenken, aber eine MP3 Verwaltung
damit auszustatten wäre nicht sinnvoll.

Active Record
macht nur bei solchen Lokalen sachen Sinn.

3.003 Beiträge seit 2006
vor 15 Jahren

Offenbar ein bisschen Tunnelblick bei uns beiden.

Danke fürs Geraderücken der Perspektive.

LaTino
<kindergarten>Ich hätt auch gar nix gesagt, aber der da hat behauptet, ich würd gegen OOP-Prinzipien verstossen!</kindergarten> 😉

"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)

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo,

weil ich, wenn ich die Kommunikation zwischen Klassen sicher gestalten möchte, jede einzelne denkbare Kommunikation sicher gestalten müsste, und ich dabei Fehler machen kann, deshalb implementiere ich lieber Sicherungen in jeder einzelnen Klasse, die ich jetzt und in Zukunft schreiben werde, verlasse mich darauf, dass auch Fremdklassen diese Sicherungen haben, ändere, wenn ich einen anderen Sicherungsmechanismus implementieren möchte, jede einzelne meine Klassen, denn Fehler können bei internen Sicherungen nicht passieren.

Ich weiß jetzt nicht wo rauf du "Sicherheit" und so was beziehst. Ich rede hier ausschließlich von Datensicherheit und Datenintegrität.
Beispiel: Darf das Land-Property einer Adresse leer sein, die in einem Auftrag verwendet wird?


public Adresse Empfaenger
{
  get{return this._Empfaenger;}
  set
  {
    if(value==null) throw new ArgumentNullException("value");
    if(value.Land==null && this.FrachtFuehrer.IsKurierdienst) throw new ArgumentNullException("value.Land");
    VerursacePropertyChanged("Empfaenger",ref this._Empfaenger, ref value);
  }
}

Authentifizierung, Rechteverwaltung, etc. sind ein ganz anderes Thema (=> Aspektorientierung).

Klassen können den internen Zustand anderer Klassen nicht in unerwarteter Weise lesen oder ändern.

Es wiederspricht nicht deiner Aussage, das Kapselung zwischen Interna und Externa vermittelt, es sagt aber eben auch genau, dass Kapselung das unerwartete Lesen und (vorallem) Ändern verhindert!

Ich denke, man kann einer Klasse auch zu viele Aufgaben zumuten und dadurch den Code unwartbar und fehleranfällig machen. Ich hab' Kollegen, die zu verteilten Code für undurchschaubar und fehleranfällig halten.

Da stimm ich dir zu. Jedoch gehört das Prüfen der Daten und das ausführen von Aktionen auf diesen in eine Klasse, da beide vom Wissen her direkt in einander über gehen. (z.B. würde ich bei einem State-Pattern auch die Validierungen der Properties mit in die State-Klassen packen (grob gesagt)).

public static T Create<T>(params object o);   

Das nennt sich Microkernel-Architektur. Die einzige aufgabe dieser Klasse ist es einer übergebenen Schnittstelle (mit absicht nicht interface, kann auch ein beliebiger anderer Typ sein) eine Implementation zuzuordnen. Hierbei soll/darf es keine unzählingen Factories geben, sondern nur den zentralen Microkernel. (Naja, hinter der Implemenation darf sich natürlich noch eine Schnittstellen-Spezifische-Factory verbergen 8). )

In einem Windowsprogramm, das keine echte Schichtenarchitektur "benötigt",
das eh alles selber macht, ist es manchmal nicht notwendig sich um solche
echten Architekturfeinheiten zu kümmern, aber sobald man aus dem Dorf
( Singleprogram/ SmartClient ) herauskommt, wird es dann wichtig.

Danke für die dreifache, indirekte Beleidigung. grummel
Also:
Auch Desktopanwendungen sollten eine Schichten-Architektur haben! (Meine haben das auch!) Die Schichten sind einfach nur nicht auf verschiedenen Systemen verteilt. Verteilung und Schichten sind zwei Dinge, die oft mal vermischt werden und "schlamipige" Programmierer bei nicht-Verteilten-Anwendungen zusammen unter den Tisch fallen lassen. Diese Vorgehen ist jedoch total daneben.
Auch Desktopanwendungen können Workflows und abläufe enthalten, die man uhrsprünglich nie vorgesehen hat. Jedoch macht es lange nicht erforderlich, dass alle "Objekte" jeden Müll als Zustand zulassen.

Weshalb gibt es denn POCO's, DTO's und CO ( absicht das mit den Abkürzungen )?
Damit man BusinessRulesEngines damit beauftragen kann z.b. die Validierung zu übernehmen.
Damit man per Webservice und CO die Daten stateless/stateful übertragen kann.
Damit man eben nicht von evtl im object vorhandenen evtl in manchen Situationen
hinderlichen Validierungen "gestört wird.

Wenn man Gernzen überschreiten muss, braucht man andere Objekte um die Daten zu transportieren. Da stimme ich ohne Zögern zu! Jedoch ist das kein Grund die ganze Anwendung mit solchen "weichen" Objekten zu bauen. Wenn ich Grenzen überschreite (Wenn wir grade dabei sind: Das ist meine Hauptaufgabe in unserer Firma), werden die Daten aus den Objekten (mit Validierung und Co.) in das Transport-Format (Objekt, Text, XML, was auch immer) überführt und versendet, die "echten" Objekte sind hier zu "klobig", "ecikg" und "kantig". Wenn ich jetzt jedoch Daten empfange, validiere ich diese nicht (besser fast nicht), sondern überführe diese direkt in die "echten" Objekte übertragen und die Standard-Validierung greift - diese ist ja ab genau dieser Stelle auch wieder gültig.

Per WorkFlowengines ganze abläufe zu automatisieren, die der Entwickler der Daten garnicht vorsehen kann.

Wenn eine WorkFlowengiene "gut" gebaut ist, dann kann man auch die Kapselung gut beibehalten. Hier finde ich vor allem wichtig, dass man trotzt WorkFlow die Methode am "gesteuerten" Objekt findet, ca.:

auftrag.DoNextWorkFlowStep();

(naja, nur grob)
Außerdem greift hier wieder das, was ich über State-Pattern gesagt habe: "...die Validierungen der Properties mit in die State-Klassen packen...", was aber immer noch bedeutet, dass eine Objekt einen Fehler meldet, wenn man versucht es in einen falschen Zustand zu setzen (halt abhänig vom Status).
Außerdem gilt dies in den Programmen die ich schreibe für 2 von ca. 200 Klassen und so weit ich das hier mit bekomme soll es bei euch ja der Standard sein.

Noch mal für "Blöde": * Gibt es einen großen unterschied zwischen State-Pattern und "Service-Orientierung"?
> Gegen das State-Pattern bzw. WorkFlowEnginers sag ich nichts, die Technologie ist gut und im Normalfall kann man diese auch "hinter" einem interface verbergen.

  • Sind "Datenklassen" so "weich" wie System.Data.DataRow? Kann man also alles mögliche dort rein schreiben?
    > Das würde ich für absolut untragbar halten.

  • Wo sollten "Datenklassen" verwendet werden? Überall?
    > Beim überschreiten von Grenzen ist es Sinnvoll solche Klassen zu verwenden, da beim "Gernzübertritt" ohne hin alles Passieren kann und die eingehenden Daten so oder so komplett neu Validiert werden müssen. Im normalen Programmablauf sind solche Klassen jedoch einfach nur gefährlich und zeitraubend (weil man ja andauernd alles selber validieren muss).

Ist zwischen euch die gleiche Diskusion wie bei AppServern.

Ich hab auch schon seit einer ganzen weile so ein übelstes Dejavue. ( 🙁 unter garantie falsch geschrieben )

Active Record

Hier gehts nicht nur um Active Record. Das fällt auch mit in das Themen gebiet, ist aber nicht der Dreh- und Angelpunkt. Schlieslich kann man auch "sich-selbst-validierende Klassen" ohne Activ Record schreiben.

Trotz allem auf jeden Fall danke an FZelle für's zurück Schubsen, so dass man wieder den Überblick bekommt.
Ich gebe zu: "Datenklassen" haben ihre Berechtigung, wenn System-/Prozess-/Softwar- und andern Grenzen im Spiel sind, bin aber auch der Meinung, dass sie im normalen Programm(-ablauf) nichts zu suchen haben.

Gruß
Juy Juka

F
10.010 Beiträge seit 2004
vor 15 Jahren

Das sollte keine Beleidigung sein, eher ein Anstoss, das Du auch über den Tellerrand schauen solltest.

Vielleicht schaust Du noch mal ein gutes Buch zu Architektur durch, um festzustellen,
das ihr zwar beide irgendwie recht habt, aber LaTino eben ein bischen mehr.

Und der Link zu Active Record sollte eine Antwort auf deine Aussage sein, das das wohl
noch kein echtes Pattern ist.

3.728 Beiträge seit 2005
vor 15 Jahren
Nachdenklich

Hallo JuyJuka,

stimmt Dich so viel Widerstand und konstruktive Kritik nicht nachdenklich?
Haben aktive Datenobjekte nicht vielleicht doch einen kleinen Haken?
Ist es wirklich nötig aktive Datenobjekte (was eigentlich sehr fein granulare Gebilde sind) über einen Microkernel zu laden?

Über einen Microkernel würde ich eher ganze Programmodule oder Plug-Ins laden.

Ist das Mapping und das Validieren bei jeder Eigenschaftszuweisung denn überhaupt schnell genug?

Wie viel Prozent des Codes ist am Ende Infrastrukturcode, und wie viel Code wirklich Geschäftslogik?

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo @All,

stimmt Dich so viel Widerstand und konstruktive Kritik nicht nachdenklich? Haben aktive Datenobjekte nicht vielleicht doch einen kleinen Haken?

Türlich stimmt mich so viel "Wiederstand" nachdenklich. Sonst würde ich ja garnicht mehr posten. Wenn ich die ultimative Lösung hätte, würd ich nich mehr Diskutieren 😃. Aber ich bin halt Hartnäckig, so lange meine Zweifel nicht beseitigt wurden, schluck ich den Köder nicht. 😉

Ist es wirklich nötig aktive Datenobjekte (was eigentlich sehr fein granulare Gebilde sind) über einen Microkernel zu laden?

Ja, aus zwei gründen:

  • Uniformität des Quellcodes. Wenn immer alles über Schema X läuft, kann man nix falsch machen. (Ich lade z.B. auch IList<string> über den Microkernel.)
  • Man kann nie planen, was man anpassen muss. Wenn ich für eine kleine, kundenspezifische Änderung das ganze Projekt kopieren, ändern und neu compilieren muss, hab ich was falsch gemacht.
    Keine Ahnung, wie es bei euch läuft. Aber bei uns beziehen sich Änderungen(Kundenvariationen) gerne mal auf ein einzelnes Property oder noch weniger.

Ist das Mapping und das Validieren bei jeder Eigenschaftszuweisung denn überhaupt schnell genug?

Mapping? Keine Ahnung, was du meinst. Validierung ist normal schon schnell genug.
In einer Desktopanwendung erwartet man eigentlich eine sofortige Reaktion und in einer Web-Anwendung macht man das ganze "Daten-an-Objekt" eh beim PostBack und von der Zeit her hat man sowieso verloren. Hab ich was vergessen?

Wie viel Prozent des Codes ist am Ende Infrastrukturcode, und wie viel Code wirklich Geschäftslogik?

Kann ich aus mehrern Gründen nicht sagen:

  • Die Trennung ist fliesend, so bald Kunden-Sepifische-Anpassungen dazu kommen.
  • Ich programmiere sowieso fast nur Programme, bei denen die "Geschäftslogik" in der Konfiguration landen (Exportieren Objekt XYZ in Textdatei mit Format "...").
  • (leider) Liegt noch der Großteil unseres Codes in einem uralten verf*** FoxPro-Nachempfunden C#-Code vor!! heul
  • Unsere Software enthält nicht sonderlich viel Geschäftslogik. Wir haben im Endefekt nur zwei Prozesse, die maximal ein bischen für einen Kunden variiert werden. (Varierende Ausdrucke/Belege, Export-Formate und Auswertungen machen bei uns den Großteil der Software aus.)
    Neben bei: Was hat das mit dem Thema zu tun? Wo soll sich da "Service+Daten" einen Unterschied zu "aktiven Datenobjekten" haben?

Das sollte keine Beleidigung sein, eher ein Anstoss, das Du auch über den Tellerrand schauen solltest.

Ja, ich hab "Datenobjekte" ein bischen zu sehr verteufelt. Es gibt genügen Stellen, wo man besser darauf zurück greift (Schnittstellen, nicht-.Net-Sprachen,

etc.).
Aber trotzdem war dein erster Post in diesem Thread ein hübscher Stich mit dem Dolch. Wunde reib
(Achja: Hab schon Bücher über Architektur gelesen, zwei an der Zahl. Könnte mehr sein, aber dazu reicht die Zeit nicht. 😦 Ob es jetzt die besten waren, kann

ich nicht sagen. )

Und der Link zu Active Record sollte eine Antwort auf deine Aussage sein, das das wohl noch kein echtes Pattern ist.

Ach so! Naja, egal, ist (zum Glück) nicht Thema dieses Threads, dafür gibts schon welche in Datentechnologie. 😉

Aber zurück zum Thema:
Alle Aussagen über Datenobjekte und Servic-Klassen gingen für mich halt infolgende Richtung:


public class Datenklasse
{
  public string Postleitzahl{get;set;} // keine Prüfungen
  public string Name{get;set;} // keine Prüfungen
  public bool BriefGesendet{get;set;} // keine Prüfungen
  ...
}

public class SendeBriefService
{
  public void Senden(Datenklasse daten)
  {
    if(!daten.BriefGesendet)
    {
      ...
      daten.BriefGesendet = true;
    }
  }
}

public static class Programm
{
  public static void Main()
  {
    Datenklasse dk = ErzeugeDatenklasse();
    dk.Postleitzahl = "Schmarn";
    dk.Name = ":-(";
    ...
    ErzeugeService().Senden(dk);
    dk.BriefGesendet = false;
    ErzeugeService().Senden(dk);
    ...
  }
}

Ich seh da halt keinen großen Unterschied zu folgendem Code:


typedef struct
{
  char[] Name;
  char[] Postleitzahl;
  bool BriefGesendet;
  ...
} Daten;

void BriefSenden(Daten daten)
{
  if(daten.BriefGesendet)
  {
    ...
    daten.BriefGesendet = true;
  }
}

int Main(int argn, char** args)
{
  Daten dk = new Daten();
  dk.Postleitzahl = "Schmarn";
  dk.Name = ":-(";
  ...
  BriefSenden(dk);
  dk.BriefGesendet = false;
  BriefSenden.Senden(dk);
  ...
}

(( Sorry, fals da Syntax-Fehler drin sind. C hab ich schon ewig nicht gemacht. ))

Folgende Varianten haben aus meiner Sicht den gleichen Effekt wie die "Servic"-Version, weniger Aufwand und mehr Datensicherheit:


publci class Brief
{
  public virtual string Name{get;set;}
  public virtual string Postleitzahl
  {
    get
    {
      return this._Postleitzahl;
    }
    set
    {
      if(!this.Land.PruefePostleitzahl(value))
      {
        throw new ...
      }
      this._Postleitzahl = value;
    }
  }
  public bool BriefGesendet{get;protected set;}
  ...
  public virtual void Senden()
  {
    if(!this.BriefGesendet)
    {
      ...
      this.BriefGesendet = true;
    }
  }
}

In Variante eins habe ich mir Polimorphie müsam mit "ErzeugeDatenklasse" und "ErzeugeService" erkauft. Und keinerlei Sicherheit was die Werte des übergebenen

Objekts angeht.
Über Variante zwei brauchen wir nicht reden, oder?
In Variante 3 habe ich alles schön zusammen, kann immmer sagen "Postleitzahl ist gültig." und kann/muss gleich die ganze Klasse variieren. Wenn ich jetzt

plötzlich große Varianzen in "Senden()" habe, kann ich immer noch per Polimorphie ein State-Pattern unterjubeln.

Das große Problem, welches mir halt immer und immer wieder Kopfzerbrechen bereitet ist, die "weichheit" der Datenklassen. Wenn ich bei "meine" Klassen etwas

an den Daten ändern muss, kann ich das geziehlt machen und "gerade so viel aufweichen" wie unbedingt nötig ist.

Ach ja: Antworte jemand auf meine 3 Fragen? (sieh vorheriger Post von mir)

Gruß
Juy Juka

M
130 Beiträge seit 2007
vor 15 Jahren

Hallo JuyJuka,

ich geh erst mal auf eine Sache von dir ein - der Rest kommt morgen. 😃

Das große Problem, welches mir halt immer und immer wieder Kopfzerbrechen bereitet ist, die "weichheit" der Datenklassen. Wenn ich bei "meine" Klassen etwas

an den Daten ändern muss, kann ich das geziehlt machen und "gerade so viel aufweichen" wie unbedingt nötig ist.

Das Problem was ich eher sehe ist, dass du immer direkte Anpassung an den Business Objekten machen musst. Ich sehe das produktiv als schwerwiegend Test + Wartungsbedürftig an. Es kann sich immer bei einer "kleinen" Änderung ein schwerwiegender Fehler im Gesamtsystem entstehen. Daher müssen alle Teile die deine Klasse nutzt per Unit Tests abgesichert sein.

Wenn ich allerdings an meinen "SendeBriefService" etwas ändere könnte ich allerhöchstens den Service ruinieren - nicht das Gesamtsystem. Ich mag in der Hinsicht etwas pingelig sein, jedoch finde ich es besser die Business Objekte dezent darzustellen ohne zu viel Logik die über den Tellerrand hinausgehen.

Viele Grüße,
moq

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo moq,

Warum solltest du nur den Service ruinieren? Wer schützt den Rest?
Okey, das Beispiel ist blöd, mit "SendeBrief" kann man (wenn man nicht ganz doof ist) nicht viel schaden andrichten. Wenn du eine Funktion in einem Funktions-Objekt änderst oder eine Funktion änderst, die vollen Zugriff auf das Funktions-Objekt hat, giebt sich nicht wirklich etwas, oder? (Jetzt mal unabhänig davon, wo die Funktion steht.)

Aber Morgen hört sich gut an. Gute Nacht. 😃

Gruß
Juy Juka

R
115 Beiträge seit 2006
vor 15 Jahren

@Juy:
Die Servicevariante hat meiner Meinung nach den Vorteil, dass man im Service jetzt sehr einfach Dependency Injection, Logging, Security etc. features einbauen kann.

Im Service könnte ich z.B. einfach über einen Konstruktor die Dependencies hineinwerfen und er nutzt diese dann oder das Logging springt an, sobald jemand den Service nutzen möchte.
Die ganzen Sachen kann man natürlich auch in dem "intelligenten" Objekt selber haben, aber es ist meiner Meinung dann etwas unschön.

Wie bereits in meinem ersten Post hier gesagt: Die Diskussion hatte ich auch schon mit Arbeitskollegen - man kann es so oder so machen, ich für meinen Geschmack halte jedoch die Service Variante für "Wartbarer", weil man eine Zentrale Stelle hat zum Schauen und nicht X-Stellen bei den Business-Objekten.

3.003 Beiträge seit 2006
vor 15 Jahren

Ich weiß jetzt nicht wo rauf du "Sicherheit" und so was beziehst. Ich rede hier ausschließlich von Datensicherheit und Datenintegrität.
Beispiel: Darf das Land-Property einer Adresse leer sein, die in einem Auftrag verwendet wird?

Ja, definitiv. Dieselbe Klasse findet schließlich auch für die Stammkundenverwaltung, und das CRM-Modul Verwendung, und die brauchen keine Länderkennung. Du verstehst meinen Punkt? Der Auftrag prüft, ob er alles hat (bevorzugt laesst er eine Prüfklasse das tun, die unterschiedliche Massstaebe ansetzt, die von Datenklasse und Einsatzzweck abhaengen).

Authentifizierung, Rechteverwaltung, etc. sind ein ganz anderes Thema (=> Aspektorientierung).

Das Thema Aspektorientierung kann man getrost weglassen. Zum einen ist es immer noch in der Forschungsphase, zum anderen existiert in "unserer" Sprache C# keine native Aspektunterstützung. Selbst Vorbereitung von echter aspektorientierter Programmierung ist schlicht nicht möglich. Andere Baustelle.

Da stimm ich dir zu. Jedoch gehört das Prüfen der Daten und das ausführen von Aktionen auf diesen in eine Klasse, da beide vom Wissen her direkt in einander über gehen. (z.B. würde ich bei einem State-Pattern auch die Validierungen der Properties mit in die State-Klassen packen (grob gesagt)).

Wie du am Beispiel oben siehst, ich nicht. Da siehst du auch, wieso nicht.

Das nennt sich Microkernel-Architektur.

Nein, äääh, eigentlich nicht. Jedenfalls hab ich diesen Begriff in diesem Forum (und auch nur von dir) das erste Mal für inversion of control-Systeme (DI/IoC) gelesen und wüsste auch nicht, dass er in Verwendung ist. Microkernel bezieht sich eigentlich auf eine Betriebssystemarchitektur. Egal, schon wieder andere Baustelle. Obwohl ich an dieser Stelle noch jede Menge wettern könnte über Architekturen, die auf Teufel komm raus FactoryMethod verwenden, wenn oft genug als Gegenanzeige eigentlich eine Abstrakte Fabrik angesagt wäre.

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)