Laden...

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

Erstellt von TripleX vor 15 Jahren Letzter Beitrag vor 15 Jahren 27.120 Views
2.187 Beiträge seit 2005
vor 15 Jahren

Hallo LaTino, Hallo @All,

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

Das "definitv" würde ich nicht so stehen lassen. Für mich muss eine Adresse ein Land haben, im Zweifelsfall das Land "<nicht angegeben>"! Aber das sind Details.
Der Nebensatz in der Frage war sehr ungeschickt von mir. Er hat das Adress-Objekt wieder in einen Kontext gesetzt. Und Objekt+Kontext ist gleich wieder komplizierter und ergiebt (wie bei dir auch bei mir) wieder eine neu Klasse (in diesem Fall die Auftrags-Klasse).

Ein besseres Beispiel ist vielleicht eine Klasse, die einen EAN-Barcode darstellt: Der Barcode ist immer 13-Stellen lang. Das ist Kontext-Unabhänig und ich würde Erwarten, dass die Klasse diesen Zustand auch immer prüft.

Wie du bereits richtig erkannt hast geht es um Kapselung. Und ein Objekt kapselt etwas und kann sich natürlich nur innerhalb dieser Kapsel validieren. Liegt hier das Problem? Glaubst du ich will Prüfungen die sich auf ein anderes Objekt bzw. Objekte-Beziehungen beruhen in meine Klasse packen (z.B. " die in einem Auftrag verwendet wird")? Das will ich nämlich nicht, da geh ich ja wieder über meine Klasse/Kapselung hinaus und das ist ja falsch (da sind wir uns einig) (oder?).
Im Gegenzug verstehe ich nicht, warum man die Kapselung aufbricht, in dem man alle Funktionen eines Objektes nach außen legt und damit riskieren muss, dass die Daten falsch sind und/oder scheinbar nicht zusammen hängende Funktionen existieren, die sich gegenseitig "abschießen". (Wenn die Funktionen in der gleichne Klasse sind erkenne ich ja, das einfach ein Fehler in der Klasse gemacht wurde.)

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.

Welches Beispiel meinst du? Dein Code-Beispiel? Da seh ich nix.

Könnte jemand meine Fragen mal beantworten:* Gibt es einen großen unterschied zwischen State-Pattern und "Service-Orientierung"?

  • Sind "Datenklassen" so "weich" wie System.Data.DataRow? Kann man also alles mögliche dort rein schreiben?
  • Wo sollten "Datenklassen" verwendet werden? Überall?

Auch wenn es vielleicht nerfig ist, würde ich gern noch mal ein Beispiel machen. Könnte einer von euch ein "Servic-Orientierte" Variante dazu posten? Danke.

Es wird eine Bestandsverwaltung implementiert. Man hat sich auf ein Objekt "Bestand" geeinigt, das eine physikalische Menge eines Artikels an einem Lagerort darstellt.

public class Bestand : IBestand
{
  public virtual IArtikel Artikel
  {
    get{...}
    set
    {
      if(value==null) throw new ArgumentNullException("value"); // hier das wäre mir nichtig
      ...
    }
  }

  public virtual decimal Menge
  {
    get{...}
    set
    {
      if(value<=decimal.Zero) throw new ArgumentOutOfRangeException("value"); // hier das wäre mir nichtig
    }
  }

  public virtual ILagerort Lagerort
  {
    get;
    set;
  }

  public virtual void Zusammenführen(IBestand bestand)
  {
    ...
  }

  public virtual IBestand Trennen(decimal menge)
  {
    bool deleteThis = false;
    IBestand neu = ...;
    if(menge==this.Menge)
    {
      deleteThis = true;
    }
    else
    {
      this.Menge -= menge;
    }
    neu.Menge = menge;
    neu.Lagerort = null;
    if(deleteThis)
    {
      ...
    }
    return neu;
  }
}

Also was ist hier "verkehrt" oder nicht so gut?

  • Ein physikalischer Bestand kann nicht negativ sein! Warum sollte die Klasse dies dan zulassen?
  • Ein Bestand muss in unserem System einen Artikel haben, damit man Ihn identifizieren kann. Warum sollte also ein Bestand ohne Artikel existieren, der in keiner Funktion verwendet werden kann?
  • Die Methoden implementieren atomare Funktionen, die nur "this" betreffen? Warum sollte die eine andere Klasse machen, die dann nur zusätzlich den inneren Aufbau eines Bestandes kennen muss?

Und danke an alle fleisigen Diskusionsteilnehmer und Lesen. 🙂

Gruß
Juy Juka

5.941 Beiträge seit 2005
vor 15 Jahren

Hallo JuyJuka

Ich glaube du verstehst da was falsch.
POCOs sollten IMO Metadaten enthalten, die für die Validierung zuständig sind.
Dies kann bspw. mit Attributen geschen.

LaTino meinte wohl mit der zusätzlichen Logik das Speichern / Laden, etc...

Gruss Peter

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

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo Peter Bucher,

Gut möglich, das hier ein bischen Chaos entsteht und wir aneinander vorbei reden.
Bei gelegenheit sollte man mal ein paar Begriffsdefinitonen machen. 🙂

z.B.
Was haben POCOs jezt mit dem Thema zu tun?
POCO = plain old clr object?
Was heißt "IMO"? "in my oppinion" ?

Gruß
Juy Juka

5.941 Beiträge seit 2005
vor 15 Jahren

Hallo JuyJuka

Was haben POCOs jezt mit dem Thema zu tun?

Es war ja davon die Rede, die Funktionalität eben nicht in Datenobjekte zu packen, sondern in Service-Klassen.
Oder bin ich jetzt auf dem falschen Dampfer?

POCO = plain old clr object?
Was heißt "IMO"? "in my oppinion" ?

Jep 😃

Gruss Peter

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

N
89 Beiträge seit 2006
vor 15 Jahren

Hiho,
jetzt bringt ihr mich grad total durcheinander.
ich schreibe gerade meinen PasswortReminder neu, um besser Verschlüsselungen zu nehmen und den Code in 3 Schichten auf zu bauen.

Meine Idee war:

Class DAL:
Enthält Methoden -> speichern, edit und löschen

Class BLL:
instanziiert DAL
Enthalt Methoden -> speichern, edit und löschen
Diese Methoden machen Prüfungen und rufen die jeweilige DAL Methode auf

Class PWReminder
instanziiert BLL
Enthält Methoden ala ShowLoginWindow()...
Nutzt die Methoden von BLL

Ist das jetzt falsch?
Müsste das so aussehen:

Class DAL
Enthält Methoden -> speichern, edit und löschen

Class BLL hat DAL als Interface
Ruft methoden des Interface auf und macht Prüfungen

Class PWReminder hat BLL als Interface
...

Gruß
Nils

Je mehr ich weiß, desto mehr erkenne ich, dass ich nichts weiß.
(Albert Einstein) ...und ich kanns bestätigen 😉

3.003 Beiträge seit 2006
vor 15 Jahren

@NilsA: tut mir auch ein bisschen leid, das mit dem Durcheinanderbringen. Du siehst, den Weg gibt es nicht. Es gibt einen Weg. Und wenn der dich befähigt, dein System offen zu halten für Änderungen, die Anforderungen zu erfüllen und gleichzeitig alles relativ leicht wartbar hält, dann ist es ein richtiger Weg. Wirklich lernen wirst du aber nur, wenn du es einfach ausprobierst und dabei auch mal Fehler machst. Die von dir vorgeschlagene Aufteilung kann man so durchaus machen. Mal dir ein paar Szenarien aus, was mögliche Erweiterungen deiner Software angeht, und überlege, an wievielen Stellen du für diese Erweiterungen deine Software ändern müsstest. Indikatoren für schlechteres Design sind:
a) viele Stellen, an denen was geändert werden muss
b) viele Stellen, an denen du die Erweiterung nicht durch neue Klassen, sondern durch direkte Änderungen am Code einzelner bestehender Klassen umsetzen MUSST.

@Juy
*g* Das hast du dir ein schönes Beispiel ausgesucht mit dem Bestand, vor zwei Wochen mussten wir uns belehren lassen, dass negative Bestände offenbar bei einem Kunden fester Bestandteil des normalen Arbeitsprozesses sind. Ungewöhnlich, aber halt nicht undenkbar. Und die stellen garantiert ihre internen Abläufe um (lies: schulen ihre Mitarbeiter um), nur damit sich unsere Software so verhält, wie sie das erwarten.

Validierung ins POCO. Hm. Aus Erfahrungen wie oben dem würde ich "tendenziell nein" antworten. Ja, für feststehende mathematische oder physikalische Gegebenheiten. Nein, für Validierungen, die dem Objekt eine Semantik geben - wie zum Beispiel der Bestand oben. Eine Validierung ist abhängig von Daten und dem Kontext, in dem sie betrachtet werden. Den Kontext kann und soll die Datenklasse gar nicht kennen, sie soll auch nicht auf alle Eventualitäten vorbereitet sein (i.e., eine riesige Validierungsmethode), noch soll für jede denkbare Kombination aus Kontext und Daten eine extra Klasse geschrieben werden (-> Wartung, DRY-Prinzip). Die Lösung ist eine Serviceklasse zur Validierung.

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,

Bestand
Dachte mir schon, dass das kommt. 🙂 Darum sagte ich auch "physikalischen Bestand". Zeig mir mal einen negativen physikalischen Besten (mit Beweisfoto!).
Aber mal ernsthaft: Wenn Negatvie Bestände dazu kommen, mach ich eine zweite Klasse in folgendem Sinne:


public class VirtuellerBestand : IBestand, INegativerBestand
{
  decimal IBestand.Menge
  {
    get{return Math.Max(this.Bestand,decimal.Zero);}
    set{this.Bestand = value;}
  }
  ...
}

Oh, sehe grade einen Fehler: ein physikalischer Bestand von 0 ist möglich! Ups.

der Rest
Zeig mir mal eine Variante, wo du all deine Anforderungen umsetzt:

  • keine riesige Validierungsmethode
  • keine extra Klasse für Kombinationen von Kontext und Daten

Beispiel: Ein Auftrag mit einer Adresse.

  1. Variante: Der Auftrag muss über Adressdaten verfügen aber nicht zwingend über einen Verweis auf einen Adressstamm.
  2. Variante: Der Auftrag muss über Adressdaten verfügen und zwingend über einen Verweis auf den Adressstamm.
  3. Variante: Bei Mandant (Auftraggeber) A wie Variante 2, bei allen anderen Mandanten wie Variante 1.

Außerdem hast du nicht auf die Frage zu den Methoden geantwortet: Warum sollte eine "athomare" Methode in einer anderen Klasse implementiert sein?

@NilsA: Schnittstellen zwischen die einzelnen Schichten zu stellen ist auf keinen Fall falsch. Und über mehr machst du ja noch keine Aussagen. Naja, wie LaTino schon sagte: Try and Error. 😉

Gruß
Juy Juka

3.003 Beiträge seit 2006
vor 15 Jahren

Wenn Negatvie Bestände dazu kommen, mach ich eine zweite Klasse in folgendem Sinne...

Ja, das dachte ich mir. <kundenmode>Das machst du bitte auch für Bestände, die nicht unter 5 rutschen dürfen, oh, und bitte auch nicht mehr als 50. Und, ah, im Werk Mannheim Süd haben wir leider die Anforderung, dass die Bestände dort nur über 20, aber weniger als 80 sind. Dafür ist Mannheim-Nord aber ohne Mindestbestand und hat Platz für 140 Einheiten. Oh, und wo ich schon einmal dabei bin...irgendwas stimmt mit der Waage in Ludwigshafen nicht, wenn dort 20 Einheiten eingebucht werden, sind es eigentlich 30. Kann man das nicht gleich durch die Software korrigieren?</kundenmode>

Wie viele Klassen hätten's denn gerne? 😉

Zeig mir mal eine Variante, wo du all deine Anforderungen umsetzt:

  • keine riesige Validierungsmethode
  • keine extra Klasse für Kombinationen von Kontext und Daten

Dynamische Workflows, zur Laufzeit erzeugt je nach Kontext. Auswahl der richtigen Datenklasse, Interpretation der Daten durch Wahl der richtigen Validierung, Durchführen des Prozesses.

Außerdem hast du nicht auf die Frage zu den Methoden geantwortet: Warum sollte eine "athomare" Methode in einer anderen Klasse implementiert sein?

Das Prinzip heisst Single Responsibility, wie du weisst 😃. Es existieren keine atomaren Methoden. Zeigt die Kundenliste da oben, und da geht es nur um die Art und Weise, wie eine einzige Zahl validiert wird.

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)

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo zusammen,

ich hatte in den letzten Tagen leider zu wenig Zeit, um diesen interessanten Thread genauer zu verfolgen oder gar etwas dazu zu schreiben. Jetzt habe ich den Thread von Anfang bis Ende gelesen und auch Zeit für eine Antwort gefunden.

Ich bin schon etwas entsetzt, dass "weiche" Datenobjekte nicht als Verstoß gegen die OOP gesehen werden. Natürlich ist das ein Verstoß gegen elementare Prinzipien. JuyJuka hat das bereits weiter oben durch verschiedene Quellen belegt. Von der "Gegenseite" kamen keinerlei Belege, sondern nur die reine Behauptung, dass kein Verstoß vorliegen würde.

Natürlich ist OOP kein Selbstzweck und letztendlich stehen die Wartbarkeit und andere primäre Qualitätsmerkmale von Code und Anwendung im Vordergrund und nicht ob man sie über OOP oder eine andere Technik erreicht.

Trotzdem bin ich schon verwundert, wie ausgeprägt der Datenobjekt+Service-Ansatz hier vertreten wird und wie sehr dabei die Prinzipien der klassischen Objektorientierung in den Hintergrund treten.

Ich unterstütze auf jeden Fall ganz klar JuyJuka und teile seine Definition und seine Haltung zur Objektorientierung.

Dass neben der Objektorientierung auch die Aspektorientierung eine Rolle spielt, bestreiten ja weder er noch ich. So ist klar, dass das Speichern von Business-Objekten nicht in die Business-Objekte gehört, sondern in DAL-Klassen. Analoges gilt für die Anzeige von Business-Objekten. Und es gibt sicher weitere Aspekte, für die das gilt, z.B. das Versenden von Objekten über Netzwerkverbindungen. Das ändert aber nichts daran, dass es einen Kern gibt, der das Business-Objekt ausmacht und das sind eben mehr als seine Daten.

Sicher sind WorkflowEngines ein weiterer Aspekt, der zudem mit der Idee, dass ein Objekt seine Konsistenz (durch Kapselung und Prüfungen) selbst sicherstellen soll, in Konflikt steht. Ich hatte mir ja schon in [Artikel] Attribute zur Prüfung von Properties verwenden Gedanken darüber gemacht, wie man Prüfungen deklarativ - und damit nicht im Setter verschweißt und gleichzeitig versteckt - einbinden kann, aber eben ohne(!) darauf verzichten zu müssen, dass ebendiese Prüfungen die Konsistenz des Business-Objekts sicherstellen.

herbivore

3.728 Beiträge seit 2005
vor 15 Jahren

Ich bin schon etwas entsetzt, dass "weiche" Datenobjekte nicht als Verstoß gegen die OOP gesehen werden. Natürlich ist das ein Verstoß gegen elementare Prinzipien. JuyJuka hat das bereits weiter oben durch verschiedene Quellen belegt. Von der "Gegenseite" kamen keinerlei Belege, sondern nur die reine Behauptung, dass kein Verstoß vorliegen würde.

Das stimmt völlig! Weiche Datenobjekte sind nicht im Sinne von OOP.

Die Frage ist allerdings, ob ein striktes OOP-Design überhaupt sinnvoll für Business-Anwendungen (Um die geht es hauptsächlich in diesem Thread) ist? Das soll jetzt nicht heißen, dass man grundsätzlich keine Klassen mehr schreibt und wieder zurück zur Prozeduralen Programmierung geht. Aber Geschäftslogik, Datenzugriffslogik und den Temporären Status in einer Klasse zu verschmelzen macht aus allem einen unzertrennlichen Klumpen.

Es bedarf deshalb keines Belegs, dass weiche Datenobjekte im Sinne von OOP sind. OOP als Unverrückbares Prinzip ist nicht im Sinne von Business-Anwendungen. OOP ist eher in bestimmten Teilen der Lösung sinnvoll, aber nicht für die ganze Lösung. Auch ein statusloser Dienst (ohne Daten-Properties), kann von OOP profitieren, indem er z.B. seine Konfiguration von einer Basisklasse bezieht. Deshalb muss der Dienst aber nicht im klassischen Sinne nach OOP-Prinzipien entworfen worden sein.

Einfache Validierung und Konsistenzprüfungen finde ich allerdings in Business Objhekten sinnvoll (wenn dieser Validierungscode nicht auf Ressourcen wie Dateisystem oder Datenbanken etc. zugreift). Bei DataTables/DataSet hat man das ja auch (Constraints).

3.003 Beiträge seit 2006
vor 15 Jahren

Ich für meinen Teil habe nirgends gefordert, im Objekt selbst auf jegliche Logik zu verzichten, also alle Verhaltensweisen auszulagern. Zweitens: selbst wenn man auslagert, kann man für den konkreten Methodenabruf wunderbar noch dem Objekt ureigenste Verhaltensweisen im Objekt lassen. Ich sehe wirklich nicht, wo hier der Begriff "weiches" Objekt gerechtfertigt wäre:


class CantDoAnyThingByItself
{
   public IObjectManipulationValidator Validator { get; set; }
   protected int m_Value;
   public int Value 
   {
      get 
     { 
         if(Validator == null) throw new MissingValidatorException;
         return Validator.IsValid(m_Value) ? Validator.GetValue(m_Value) : Validator.DefaultValue;
      }
      set
      {
         if(Validator == null) throw new MissingValidatorException;
         m_Value = Validator.SetValue(value);
      }
}

Wo werden da OOP-Prinzipien verletzt? Das Objekt selbst weiss gar nichts, es kennt nur sein Datum "m_Value" und den Ansprechpartner für Validierungsfragen. Und ich bin nicht gezwungen, bei Validierungsänderungen

a) die Datenklasse umzuschreiben
b) die Validierungsklasse umzuschreiben

Alles, was zu tun ist, wäre einen neuen Validator in die Hierarchie einzuklinken und im Kontext (Workflow, wo auch immer) den neuen Validator statt des alten zuzuweisen. Fertig, aus die Maus. Und diese Möglichkeit verwehre ich mir selbst, wenn ich den Validierungscode direkt im Objekt verankere. Ich bin weder in der Lage, situationsabhängig verschiedene Validierungsarten zu benutzen, noch kann ich die Validierung ändern, ohne meine Datenklasse anzufassen. Insofern mag auch der Ansatz einer Ich-validiere-meine-Daten-selbst-Klasse objektorientiert sein. Aber er widerspricht der Absicht der OOP. Man kann so objektorientiert programmieren, dass man die Forderungen an OOP nicht mehr erfüllt, und die Gefahr dafür ist bei Klassen, denen man mehr als elementarste Aufgaben überlaesst (Validator == null darf sie ja gern machen), viel größer als bei denen, die sich eben nur auf das absolute Minimum beschränken.

Und auf den Vorwurf, ich wollte eine Art DataRow als objektorientiert verkaufen, bin ich natürlich nicht eingegangen. Das ist absurd: JuyJuka hat belegt, dass ein solches Objekt gegen OOP-Prinzipien verstossen würde. Das ist ja schön und gut, nur möchte ich gar keinen universellen Datencontainer ohne Kapselung. Ich sehe also nicht, wieso ich da irgendwas widerlegen sollte.

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)

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo Rainbird,

Es bedarf deshalb keines Belegs, dass weiche Datenobjekte im Sinne von OOP sind.

der Beleg wäre ja auch gar nicht zu erbringen. 😃

OOP als Unverrückbares Prinzip ist nicht im Sinne von Business-Anwendungen. OOP ist eher in bestimmten Teilen der Lösung sinnvoll, aber nicht für die ganze Lösung.

Nichts anders hatte ich gemeint als ich von "Natürlich ist OOP kein Selbstzweck" gesprochen und auf die primären Qualitätsmerkmale abgehoben habe. 😃

Wir sind uns da also einig!

Hallo LaTino,

Man kann so objektorientiert programmieren, dass man die Forderungen an OOP nicht mehr erfüllt,

... dass man die primären Qualitätsmerkmale nicht erreicht. So wird eine Schuh daraus. 😃 Ich habe nichts dagegen, wenn man sagt, ich programmiere an dieser oder jener Stelle bewusst nicht objektorientiert, weil dadurch die Wartbarkeit meiner Software steigt. Aber man kann eben nicht sagen, dass man dann noch objektorientiert programmiert, wenn man z.B. gegen die Kapselung verstößt.

Objektorientierung sagt: Man muss kapseln. Der Hintergrund ist, dass die Theorie und auch praktische Erfahrungen sagen, dass durch Kapselung die Abhängigkeiten zwischen Code-Teilen reduziert werden und damit die Wartbarkeit steigt. Aber natürlich hat Kapselung ihren Preis und erhöht den Programmieraufwand, um die Kapselung zu errichten und um an bestimmte Informationen zu kommen. Das ist eben die Krux. Deshalb kann es besser sein, an bestimmten Stellen auf Kapselung zu verzichten, um die Wartbarkeit zu erhöhen. Aber das ist dann nicht mehr objektorientiert. Man erfüllt dann eine Forderung der Objektorientierung nicht mehr.

Die beiden Absätze sind als reine Wenn-Dann-Aussagen gemeint. Ich will dir damit nichts unterstellen. Du sagst ja selbst, dass du keinen universellen Datencontainer ohne Kapselung willst. Ich denke, wir sind im Grunde einer Meinung.

Und diese Möglichkeit verwehre ich mir selbst, wenn ich den Validierungscode direkt im Objekt verankere.

Nichts anders hatte ich gemeint, als ich auf meinen Artikel verwiesen hatte. 😃 In dem Artikel sind in dem Beispiel zwar lowlevel Prüfungen als Beispiel verwendet, aber es ist natürlich genauso gut möglich highlevel Prüfungen zu verwenden. Also statt MaxStringLength (20) könnte man z.B. MaxStreetLength () verwenden. Der Vorteil der Attribute ist der deklarative Ansatz und die Möglichkeit, die Attribute standardisiert auslesen und somit die Prüfungen zusätzlich außerhalb des Business-Objekts verwenden zu können. Der Nachteil der Attribute ist, dass diese fest mit der Property verbunden sind. Dynamik kann man natürlich über die Implementierung der Attributklasse realisieren, aber die Zuordnung von Attribut zu Property steht eben weiterhin statisch im Code.

Hier verfolgen wir also vielleicht unterschiedliche Ansätze aber das gleiche Ziel.

herbivore

3.003 Beiträge seit 2006
vor 15 Jahren

Hm, vielleicht denke ich falsch. OOP ist für mich hauptsächlich der Aufbau einer Logik, deren Abläufe sich durch Kommunikation von Objekten untereinander ergeben. (Gegensatz: imperative Programmierung, die Logik ergibt sich durch Formulierung von Schritten eines Algorithmus; deklarativ: die Logik ergibt sich durch aus der Formulierung des Ziels). Für mich geht die Definition von OOP bedeutend weiter als "OOP ist alles, was folgende Prinzipien einhält: Kapselung, ...". Umgekehrt ergeben sich die Prinzipien vielmehr aus dem Ziel, eine kommunikative Logik zu erhalten. Btw, die Definition oben ist nicht auf meinem Mist gewachsen (ich denke, ich habe die Idee ursprünglich aus einem Papier von Stroustrup, bin aber nicht mehr sicher).

Die OOP-Prinzipien haben sich bewährt, was den Aufbau einer solchen Kommunikation angeht, aber meines Erachtens ergänzen sich die Prinzipien nur teilweise - auf anderen Gebieten stehen sie scheinbar im direkten Widerspruch:

  • Kapselung, i.e. Geheimhaltungsprinzip
  • singuläre Zuständigkeit

Ich komme zum Punkt: der scheinbare Widerspruch ergibt sich, wenn das Objekt Aufgaben wahrnehmen muss, die eigentlich nicht zu seiner Kerndefinition gehören - weil die Beschränkung auf die Kernfunktionen bedeuten würde, einige gekapselte Daten nach außen teilen zu müssen.

Mein Punkt ist: das ist kein Widerspruch. Wenn ein Objekt so aussieht, als übernähme es Aufgaben, die nicht in seinen Bereich gehören, dann ist das IMO ein sicheres Zeichen dafür, dass das Objekt falsch entworfen wurde. Das sind IMMER die Kandidaten, die später Probleme machen. Sich auf den Standpunkt zu stellen, dass man aus Gründen des Kapselungsgebots lieber das Single-Responsibility-Prinzip verletzt, wie das meiner Meinung nach beim Active Record der Fall ist, kann ich nicht nachvollziehen.

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)

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo LaTino,

ich denke, wir sind uns darüber einig, dass ein Business-Objekt nicht alle Funktionalität selbst realisieren sollte. In ein Business-Objekt gehört z.B. nicht seine Speicherung und nicht seine Anzeige. Ich denke, wir sind uns auch darüber einige, dass ein Business-Objekt bestimmte Funktionalitäten selbst realisieren sollte. Ich denke, wir sind uns einig, dass das Kapselungsprinzip nicht das Single-Responsibility-Prinzip und das Single-Responsibility-Prinzip nicht das Kapselungsprinzip verletzen sollte. Man sollte versuchen beides unter einen Hut zu bekommen. Geht beides zusammen nicht, sollte man einen guten Kompromiss finden.

Der "Streit" ist eigentlich nur ein gradueller. Welche Funktionalität gehört noch ins Business-Objekt und welche gehört schon ausgelagert. Ich denke, das werden wir nicht abschließend klären können, weil da die Einschätzungen etwas auseinander gehen.

Trotzdem fand und finde ich die Diskussion hilfreich, um den Blick zu schärfen und aus den unterschiedlichen Standpunkten Anregungen zu bekommen. Ich glaube wir haben an diesem Punkt die Standpunkte soweit herausgearbeitet, dass diese klar sind, ohne dass diese noch in dem komplementären Widerspruch stehen, wie es weiter oben den Eindruck machte.

herbivore

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo @All,

Es hat sich doch gelohnt mal 24h (okey, fast 24h) auf Forums-Entzug zu gehen. Gab ja viel neues zu Lesen:

<kundenmode>Das machst du bitte auch für Bestände, die nicht unter 5 rutschen dürfen, oh, und bitte auch nicht mehr als 50. Und, ah, im Werk Mannheim Süd haben wir leider die Anforderung, dass die Bestände dort nur über 20, aber weniger als 80 sind. Dafür ist Mannheim-Nord aber ohne Mindestbestand und hat Platz für 140 Einheiten. Oh, und wo ich schon einmal dabei bin...irgendwas stimmt mit der Waage in Ludwigshafen nicht, wenn dort 20 Einheiten eingebucht werden, sind es eigentlich 30. Kann man das nicht gleich durch die Software korrigieren?</kundenmode>

Wie viele Klassen hätten's denn gerne? 😉

Für jede Aufgabe eine:

  • Eine Klasse, die Mindestbestände überwacht, was überhaupt nichts im Bestand zu tun hat, der ja trotz Mindestbestand auf bis zu 0 sinken kann.
  • Eine Klasse die das Lager (vereinfacht) darstellt und bescheid gibt, falls (vermutlich) kein Platz mehr ist (Wenn der Kunde sagt es ist platz für 140 Einheiten, dann sagt dir der Lagermitarbeiter normalerweise: "Och was, da bekomm ich noch 20 Stück rein." 😉
  • Eine Klasse die die Waage steuert.

Aber mal wieder allgemein und im Ernst: Neue Kundenanforderungen, die über den Uhrsprünglichen Rahmen einer Klasse hinaus gehen, müssen mit einer neuen Klasse abgedeckt werden. Wenn ein Klasse immer Kundenspezifisch angepasst werden muss, sollte man diese Klasse so weit Konfigurierbar machen. Aber die Grundfunktion (Verhalten) einer Klasse sollte fest definiert sein und nicht irgend wo ungekoppelt rumliegen.

Zeig mir mal eine Variante, wo du all deine Anforderungen umsetzt:

  • keine riesige Validierungsmethode
  • keine extra Klasse für Kombinationen von Kontext und Daten
    Dynamische Workflows, zur Laufzeit erzeugt je nach Kontext. Auswahl der richtigen Datenklasse, Interpretation der Daten durch Wahl der richtigen Validierung, Durchführen des Prozesses.

Ich seh immer noch nix. wart

Es existieren keine atomaren Methoden.

Eigentlich schon: Alle Methoden die nur "this" verwenden und keine externen Resourcen.

Ich bin schon etwas entsetzt, dass "weiche" Datenobjekte nicht als Verstoß gegen die OOP gesehen werden.

Haleluja und Amen!

Die Frage ist allerdings, ob ein striktes OOP-Design überhaupt sinnvoll für Business-Anwendungen (Um die geht es hauptsächlich in diesem Thread) ist?

Die eigentliche Fragen war für mich:

  • "Sind weiche Objekt und Service-Klassen Objektorientiert?"
  • "Ist selbstvalidierung ein wichtiger/grundlegender Teil der Objektorientierung?"
    Aber das nur am Rande. Objektorientierung in "Businessanwendungen" (was auch immer das sein soll) war für mich kein Thema (aus naivität würd ich einfach JA sagen 😄 ).

Ich für meinen Teil habe nirgends gefordert, im Objekt selbst auf jegliche Logik zu verzichten, also alle Verhaltensweisen auszulagern.

So kamm es aber rüber. Gut dass das nur ein Missverständnis gewesen zu sein scheint.

  
class CantDoAnyThingByItself  
{  
   public IObjectManipulationValidator Validator { get; set; }  
   protected int m_Value;  
   public int Value  
   {  
      get  
     {  
         if(Validator == null) throw new MissingValidatorException;  
         return Validator.IsValid(m_Value) ? Validator.GetValue(m_Value) : Validator.DefaultValue;  
      }  
      set  
      {  
         if(Validator == null) throw new MissingValidatorException;  
         m_Value = Validator.SetValue(value);  
      }  
}  

...Und diese Möglichkeit verwehre ich mir selbst, wenn ich den Validierungscode direkt im Objekt verankere....

Eigentlich nicht, genau dafür gibt es virtual Beziehungsweise "Polimorphy" wenn man Sprachunabhänig bleiben will. Wenn die Validierung wirklich so flexiebel ist macht es vieleicht Sinn sie auszulagern, aber nicht Standardmäsig.

Und auf den Vorwurf, ich wollte eine Art DataRow als objektorientiert verkaufen, bin ich natürlich nicht eingegangen. Das ist absurd: JuyJuka hat belegt, dass ein solches Objekt gegen OOP-Prinzipien verstossen würde. Das ist ja schön und gut, nur möchte ich gar keinen universellen Datencontainer ohne Kapselung. Ich sehe also nicht, wieso ich da irgendwas widerlegen sollte.

Hat sich aber verdächtig so angehört. (scheinbar auch für herbivore)

Aber man kann eben nicht sagen, dass man dann noch objektorientiert programmiert, wenn man z.B. gegen die Kapselung verstößt.

Und eine Auslagerung der Validierung, so dass das Wissen über gültige und ungültige Objektzustände nicht in der Klasse liegt verstöst für mich dagegen.
Aber Achtung: durch folgenden Code liegt die Validierung ja wieder in der Klasse, auch wenn sie Austauschbar ist:

... get{this.Validater.Validate(value); ... 

Dieses Code-Konstrukt hat nie einer von der "Service-Fraktion" in bezug zu seinem Code gebracht und es ist auch aufwändig, komplex und schwer wartbar, im vergleich zu virtual, jedoch extrem flexiebler.

Sich auf den Standpunkt zu stellen, dass man aus Gründen des Kapselungsgebots lieber das Single-Responsibility-Prinzip verletzt, wie das meiner Meinung nach beim Active Record der Fall ist, kann ich nicht nachvollziehen.

Da wiedersprech ich dir nicht. Ich habe auch nicht versucht zu sagen, das Kapselung über Eine-Klasse-Eine-Aufgabe geht. Ich wollte nur, dass eine Kapsel in der anderen Steckt und nicht, dass die Kapseln alle einzeln losen im Raum herum schweben und die Daten von einer zur anderen Wandern. Wenn ich z.B. einen Bestand verwalte, dann muss nicht nur die Zahl validiert werden, sondern diese Änderung auch an alle andern Clients gehen bzw. gespeicher werden. Für mich ist das so zu sagen eine Unteraufgabe von "Bestand verwalten", die zwar von einem anderen Objekt erledigt werden muss, womit aber der "Benutzer" des Bestand-Objektes nichts am Hut haben darf. Um noch ein Beispiel zu nennen: Wenn ich eine Umlagerung durchführe, dann muss Bestand A veringert und Bestand B erhöht werden. In diesem Fall muss die Umlagern-Klasse alles zusamen machen, der "Benutzer" darf das nicht selbst wissen müssen! (blöder satz) Im Gegenzug ändert die Umlagern-Klasse den Bestand nicht selbst, sondern deligiert das ganze an die Bestands-Klasse (per Property).
Ich dachte, wir hätten Active Record schon eine ganze weile hinter uns gelasse?

Hoffentlich hab ich die Diskusion nicht unnötig neu Angefacht, da wir uns ja wirklich einem gemeinsammen ... Bereich nähern. Und viel mehr war ja auch nicht zu erwarten, da je jeder Mensch verschieden ist und man nie exakt der gleichen Meinung ist. 😃

Gruß
Juy Juka

3.728 Beiträge seit 2005
vor 15 Jahren
Business

Aber das nur am Rande. Objektorientierung in "Businessanwendungen" (was auch immer das sein soll) war für mich kein Thema (aus naivität würd ich einfach JA sagen 😄 ).

Anwendungen, die betriebswirtschaftliche Prozesse abbilden werden auch gerne als Businessanwendungen bezeichnet. Es ist ein Unterschied, ob ich z.B. eine Textverarbeitung, ein Zeichenprogramm oder eben eine Business-Anwendung schreibe. Erstere zwei werden vermutlich in einem Prozess laufen und nicht auf ein RDBMS zugreifen.

Bei einer Textverarbeitung macht 100% OOP-Design wesentlich mehr Sinn, als bei einer Business-Anwendung.

3.003 Beiträge seit 2006
vor 15 Jahren

JuyJuka,

auf das Beispiel wirst du weiter warten müssen. Vielleicht bin ich zu doof zum erklären, aber den groben Ablauf hab ich ja skizziert. (ich habe hier ein fertiges Beispiel, das ziemlich genau so funktioniert... a) NDA, b) 700k LoC, c) nicht aus dem Kontext herauszureissen, sorry 😃)

Es geht darum, verschiedene Datenklassen mit verschiedenen Serviceklassen frei kombinieren zu können. Wieso das dem Gegenkonzept per Vererbung überlegen ist, liegt auf der Hand. Bei 10 Validierungsmöglichkeiten, 10 Datenklassen und 10 Speichern-Möglichkeiten landest du bei 200 Klassen, ich bei 30. Alte Diskussion Containment vs. Polymorphie. Es ist ein Jammer, dass auf das Vererbungskonzept in der Lehre fast zu 100% eingegangen wird, als sei das das einzige Standbein der OO...dabei verlassen sich auch sehr viele Patterns auf Containment.

Soviel zu "schwer wartbar". Klassenhierarchien, die sich auf Polymorphie zur Delegierung von Aufgaben verlassen, sind extrem schwer wartbar ab einer gewissen Größe, weil für jede Klasse der Hierarchie nicht offensichtlich ist, welches Verhalten sie denn nun implementiert. Gottseidank geht wenigstens Mehrfachvererbung nicht.

Uebrigens auch schwer dokumentierbar. Und wenn du dann noch so ein Monster wie den "Microkernel" hast, dem für jede neue Klasse (und davon werden es etliche) neu beigebogen werden muss, wann er was auszuspucken hat...Albtraum.

Ich verdamme das ja nicht im ganzen. Hab' glaub ich irgendwo oben erwähnt, dass ich gerne mal eine abstrakte Fabrik für die Erzeugung von Klassenhierarchien einsetze, das verlässt sich ja nun massiv auf polymorphe Methoden. Ich halte das nur nicht für besonders geeignet für Klassen der Business-Logik. Dort sind die Klassen meiner Erfahrung nach in nahezu allen Bereichen so komplex, wie du das in deinem letzten Absatz mit der Bestandsverwaltung beschrieben hast. Je mehr Anforderungen an so eine Klasse gestellt werden, desto mehr Aufgaben werden ausgelagert in die "Bestandsverwaltung", die "Konsistenzverwaltung", und so weiter. Was übrig bleibt, ist dann tatsächlich irgendwann von außen betrachtet nur noch ein Datencontainer, scheinbar ohne eigene Logik. Bei Projekten, die absehbar eine gewisse Größe annehmen werden (einfach mal im ersten Gespräch den Kunden ein bisschen laut träumen lassen, was er sich neben dem gerade besprochenen Projekt noch so wünschen würde, wenn er die Wahl hätte...wenn man dabei "ach du Scheiße..." denkt, dann ist das ein guter Indikator 😉 ) , ist es meiner Ansicht nach deshalb sinnvoll, die Klassen von vornherein dementsprechend zu entwerfen.

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)

1.274 Beiträge seit 2005
vor 15 Jahren

Hallo zusammen,

ich halte das ganze für eine sehr interessante Diskussion. Es ist toll und wichtig, wenn man von einfachen "wie mache ich was", zu eine Ansatz kommen, der doch eher in die Architektur geht.

Ich sehe bei beiden Varianten eine Vorteil aber auch Nachteile, die beide ja genaustes Diskutiert wurden.

Verwendet ihr die Objekte auch für das Databinding?

Liebe Grüße aus dem winterlichen Österreich
LastGentleman

"Das Problem kennen ist wichtiger, als die Lösung zu finden, denn die genaue Darstellung des Problems führt automatisch zur richtigen Lösung." Albert Einstein

2.187 Beiträge seit 2005
vor 15 Jahren

Hallo LastGentleman,

Ich verwende meine Objekte auch zum DataBinding.

Gruß
Juy Juka