Ankündigung

Einklappen
Keine Ankündigung bisher.

OO-programming is a synonym for procedural-programming

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • #16
    Als erstes möchte ich in diesem Post folgendes vorstellen:
    Induktion. Man hat einen Induktionsanfang und einen Induktionsschritt.
    Heißt sollte etwas im Basisfall gelten, dann sollte es auch für kompliziertere/komplexere Erweiterungen (Induktionsschritte) gelten.
    Auf dieses Prinzip werde ich später nochmal zurückkommen

    Eine Primärquelle zu Martin Fowler (und der von knotenpunkt formulierten „Rule 6“) in #1 ist dieser Artikel aus 2003: AnemicDomainModel. Darin finden sich entsprechende Aussagen (die man aber durchaus im Kontext betrachten darf), aber ich sage dazu gezielt nicht mehr, weil knotenpunkt eine ziemliche Querfront aufmacht, indem er – auch aus diesem Artikel – ableitet, dass OOP ein „Verbrechen an der Menschheit“ sei
    Was meinst du mit Querfront?
    Naja "Vebrechen an der Menschheit", kann ich aus diesem Artikel nicht ableiten, weil ich ihn vorher noch gar nicht gekannt habe^^
    Diese Ableitung habe ich selbst getroffen, aber nicht auf der Basis die du wahrscheinlich denkst.

    Ich werde es im folgenden weiter ausführen.....

    Nachdem ich den Text von Martin Fowler durchgelesen habe, kam ich zu dem Entschluss, dass wir (ich und Fowler) uns in allen Punkten bis auf einen gleicher Meinung sind!
    The anemic domain model is really just a procedural style design
    Es ist genau das, was ich oben gesagt habe, dass entity-classes (also klassen ohne verhalten) und service-classes (zustand-lose klassen mit auschließlichlichem verhalten) dem prinzip der prozeduralen programmierung folgt!


    Now object-oriented purism is all very well, but I realize that I need more fundamental arguments against this anemia. In essence the problem with anemic domain models is that they incur all of the costs of a domain model, without yielding any of the benefits. The primary cost is the awkwardness of mapping to a database, which typically results in a whole layer of O/R mapping. This is worthwhile iff you use the powerful OO techniques to organize complex logic. By pulling all the behavior out into services, however, you essentially end up with Transaction Scripts, and thus lose the advantages that the domain model can bring. As I discussed in P of EAA, Domain Models aren't always the best tool.
    Auch der Meinung bin ich, dass es eigentlich Schwachsinn ist Klassen, ORM, etc zu verwenden, wenn es im Endeffekt eh wieder prozedural ist!
    Ok die Kosten auf sich zu nehmen, des lohnt sich erst dann, wenn man wirklich von den OO-Techniken profitiert. Eine Profitierung ist ausgeschlossen, wenn Verhalten und Daten getrennt werden!

    Wie diese Profitierung aussieht, das könnt ihr mir gerne verraten!^^

    Aus dem Text von Fowler wird außerdem klar, dass es im Allgemeinen der Service Klassen (Service-Layer) dazu da ist zwischen unterschiedlichen Applikationen zu kommunizieren
    Meiner Meinung nach auch noch um Ergebnise/Anfragen anzupassen (adaptieren)
    Also bin ich der Meinung dass es sich hier wohl nur um einen weiteren Routing/Adapter-Layer handelt und nicht mehr!

    Fowler weicht aber den Service-Layer noch ein bisschen weiter auf:

    One source of confusion in all this is that many OO experts do recommend putting a layer of procedural services on top of a domain model, to form a Service Layer. But this isn't an argument to make the domain model void of behavior, indeed service layer advocates use a service layer in conjunction with a behaviorally rich domain mode
    Hier sagt er, wenn man zwischen den Zeilen liest, dass man aufjedenfall Domain-Objekte mit Verhalten verwenden soll, und aber ein zusätzlicher prozeduraler Service-Layer das ganze gut ergänzt.
    Ich denke, dass das nicht ganz richtig ist!
    Gemäß oben genannten Induktionsprinzip und Fowlers Aussage, dass man profitiert, wenn man nach oo-konzepten programmiert, dann gilt das auch für höherwertigers, also auf gut deutsch, man kann nur vollständig dann davon profitieren, wenn auch wirklich alles vollständig gemäß oo-konzepten programmiert worden ist. Und dazu gehört NICHT ein aufgeweichtes Service-Layer prinzip, dass teilweise prozedurale bzw. nicht oo-prinzipien folgende Programmierparadigmas folgt

    Von daher ist ein Service-Layer meiner Meinung nach für Routing/Adaption gut und gültig aber auch nicht für mehr!
    Hier stellt sich mir dann die Frage ob es überhaupt sinn macht in einer MVC-Architektur neben dem Controller im Model einen weiteren Routing/Adapter Layer einzuführen, aber das ist eine andere Frage.



    Zusammenfassend kann man sagen, dass OO nur dann ein Verbrechen an der Menschheit ist, wenn man es sinnlos einsetzt, um dann eh doch wieder prozedural aber mit Mehrkosten zu arbeiten.

    Und um diese Sinnlosigkeit geht es jetzt:
    Was zeichnet OO aus?
    Was sind die "benefits", von denen Fowler spricht?
    Jedoch muss ich auch feststellen, dass der Autor OOP noch nicht verstanden hat
    Dann sei doch so lieb und erklär es mir

    Wäre super, wenn mir das jemand erklären könnte.

    Negativeindrücke von oo habe ich im folgenden für euch zusammengefasst:

    Vor vielen vielen vielen Jahren hab ich mal diesen Text hier verfasst: http://www.php.de/forum/webentwicklu...hwachsinn-oder
    So alles, was ich hier behauptet habe, würde ich zwar nicht mehr unterstreichen....
    Das war auch noch vor meiner Studierenden-Zeit und von daher konnte ich das ganze noch nicht so mit Fachbegriffen ausmalen
    Hauptsächlich kritisiere ich hier die Datenstrukturkopplung und Designentscheidungen

    1. Datenstrukturkopplung:

    Wenn ich an ein anderes Objekt ein Datenobjekt/typ übergebe und dieses nicht alle Daten (bezogen auf den Typ) davon braucht, dann hab ich ne unnötige Datenstrukturkopplung
    Selbiges gilt meiner Meinung nach genauso, wenn ich eine Klasse designe und die enthaltenenen Methoden nicht jeweils IMMMER alle Daten abdecken
    heißt jede Methode sollte, wenn man eine unnötige Datenstrukturkopplung vermeiden möchte, immer alle Membervariablen verwenden.
    Außerdem wird so auch das Single-Responsibility-Prinzip eingehalten.
    Da hilft meiner Meinung nach auch nicht wenn man nach dem Prinzip der informationellen Kohesionsart vorgeht, wenn danach immer noch eine interne unnötige Datenstrukturkopplung vorliegt.

    Naja bricht man das ganze herunter auf eine Prozedur, dann ist es ja so, dass diese alle Daten bzw. Parameter von denen die Prozedur abhängt schön innerhalb verwendet (=> optimale Kopplung)
    Unterschied zu Objekten, ne Prozedur kann keinen zustand abspeichern. ok dann können wir pointer/referenzen nehmen oder aber klassen mit allerhöchstens einer Methode nehmen, wobei das ja wieder an haufen overhead erzeugen würde


    Das wäre ein pro für ne loose Funktion und ein Contra für das Verwenden einer Klasse


    2. Design-entscheidung:
    Naja ist eher weniger eine Designentscheidung sondern viel mehr auch wieder ein Grundsatzproblem


    In dem Text frage ich mich, wo das Verhalten ban_user($other_user_or_admin) hin soll
    In den User? In den admin?

    Naja um dieses Dilema aufzuheben baut man ne dritte einheit, bspw. BanManager oder BanService (ist nur vom Namen anders^^)

    und dann die Methode ban_user($from_admin,$user)
    aber wie der zweite Namen schon verlauten lässt, ist dies ein zustandsloser Service (also wieder prozedural)
    Und ich sehe für diesen Use-Case auch kein weiteres Verhalten, welches bei User oder Admin nötig wäre.


    Um das ganze allgemeiner auszudrücken:

    ich hab nen 1 Dimensionalen Fall. Methode arbeitet nur auf eigenen Daten vom Objekt (nicht aber auf weiteren oder von weiteren abhängigen Daten eines weiteren Objekts, welches via parameter übergeben worden ist) => Das finde ich soweit eigentlich ok^^ und korrekt (eventuell kann man dies auch schon als oo-programmierung bezeichnen)

    2D-Fall: Objekt arbeitet auf eigenen Daten und auf denen von einem übergebenen Objekt
    nD-Fall Objekt arbeitet auf eigenen Daten und auf denen von (n-1) übergebenen Objekten

    Und hier haben wir schon unser Problem, weil eigentlich ist dies nichts anderes als Prozedural, abgesehen vom 1D-Fall
    Um sich nacher net noch doofe Gedanken zu machen/ doofe Designentscheidungen überlegen zu müssen. packt man hier den Algorithmus am besten ein ein "drittes bzw. n+1tes" Objekt, also ein Art Service (Ist aber wie gesagt prozedural und nicht oo und schon gar nicht Fowlers Geschmack^^)


    Der 1D Fall ist meiner Meinung nach korrekt oo, aber gemäß Induktionsprinzip nicht für alle n+1te Fälle -> so kann man eigentlich auch gleich den 1D-Fall vergessen


    3. Algorithmus (nicht in dem beitrag erwähnt):

    wenn ich prozedural einen Algorithmus entwerfe bzw. einen Use-Case umsetzte dann sieht das bspw. folgendermaßen aus

    Funktion(parameter)
    {

    Anweisung 1
    Anweisung 2

    Sub-Funktionsaufruf (eventuell parameter durchreichen oder zwischenergebnisse durchreichen)
    Sub-funktionsaufruf(....)


    ...

    return Ergebnis bzw.

    //wenn transaktionale Sicherheit gegeben sein soll

    Ändere Zustände in Daten, andernfalls eventuell schon als Nebeneffekt oben bei den Anweisungen bzw. Sub-Funktionsaufrufen

    return result

    }

    Der Vorteil bei dieser Algorithmusdefinition ist, dass man ganz leicht transaktionen bauen kann, also erst mal alles abchchecken dann Änderungen setzen.
    Außerdem lassen sich Seiteneffektfreie Funktionen (also Funktionen die keinen internen Zustände ändern) sehr nice parallelisieren (nebenläufig machen).

    kann man es eigentlich auch schon als oo durchgehen lassen, wenn bspw. diese Sub-funktionsaufrufe 1D-Fälle sind, die ich oben genannt habe?



    Und ein dritter Vorteil ist die "Rohr-Sicht".
    Ich hab einen Strang (sequentiell definiertern code) und rufe von diesem aus Subfunktionen auf.
    Der Vorteil besteht darin, dass ich Ergebnisse in weiteren Aufrufen von Subfunktionen vom Hauptstrang aus weitergeben kann.
    Beim objektorientiereten hab ich nur einen linearen Strang?!?, oder eher einen linearen Strang?!?

    prozedural:
    -> Aufruf
    ->Aufruf -> Aufruf
    ->Aufruf
    ->Aufruf->Aufruf->Aufruf->Aufruf
    ->Aufruf
    ->Aufruf

    (Hauptstrang ist auf der Vertikalen)

    oo stelle ich mir eher so vor, also nur die X-Achse, die Y-Achsenkomponente fällt einfach weg


    also bspw. nur sowas: ->Aufruf->Aufruf->Aufruf->Aufruf
    eventuell liege ich da auch falsch?

    Also einiges unflexibler
    Beim prozeduralen habe ich halt eher ne globale Sicht und bin damit auch flexibler und kann meinen Algorithmus freier entwerfen


    Also das mit den Strängen und Aufrufen, da bin ich mir natürlich nicht ganz sicher ob das so für das oo zutrifft (ich meine jetzt auch nicht nur die Funktionsaufrufe, sondern auch die Ergebnisverarbeitung bzw. die Sichtweise im Allgemeinen)
    Da die Daten ja zusammengefasst mit dem Verhalten sind, ist ein Ausbruch aus dieser Sicht eigentlich nicht möglich, zumindest wenn man legitim oo sein möchte.
    Außerdem, da die wichtige Y-komponente wegfällt, ist man einigen Einschränkungen unterlegen und hat damit flexibilitätseinbußen.


    Aber überzeugt mich gerne eines besseren



    Kurz noch ein allgemeiner Einschub zu was generellerem:
    Wie seht ihr folgendes: Wäre es eigentlich besser, wenn man den return-flow nicht nur auf Grad 2 beschränken würde?
    Aktuell ist es ja so, dass ein return type1 und ein throw type2 möglich ist

    wäre es aber nicht sogar noch sinnvoller weitere return-mechanismen, am besten für den den programmierer variable N return mechanisem erlauben würde. Um nicht irgendwelche Statusflags auslesen zu müssen und hiernach zu entscheiden was passiert. Für den Error-Flag wurde ja schon throw eingeführt.... aber es gibt ja noch weitaus mehr flags die nicht ins error-handling fallen
    wie seht ihr das?, ein ausbau des return-flows wäre doch nice oder?




    Das bis jetzt sind so meine Allgemeinen negativen Eindrücke die ich von oo habe
    Eventuell kann ja jemand diese Eindrücke widerlegen. Oder aber diese sind gar nicht so falsch, aber es überwiegen doch irgendwelche anderene "benefits" von oo. vlt. könnte mir jemand diese aufzeigen.

    Mir gehts da dann doch hauptsächlich um den Algorithmusentwurf.

    Weil ein relationales DB-Design bekommt man ja leicht hin. Auch dieses in eine oo Struktur umzumünzen funktioniert ja relativ leicht. Aber welche Klassen (Services oder auch nicht Services) ich dann weiterhin hinzuzufügen habe, wo ich welches Verhalten definiere, wie ich welches Verhalten definiere, eventuell kann mir ja hier jemand tipps oder Beispiele geben
    Wäre euch sehr dankbar dafür


    Fortsetzung von "Querfront" gegen OOP folgt....
    Nein scherz^^, wie gesagt ich würde mich gerne über andere Ansichten freuen
    bin halt prozedural/relational aufgewachsen und versteh halt vor allem die Flexibiltätseinbußen/Leistungseinbußen von oo nicht so wirklich
    Also was die wirklichen benefits sind.




    lg knotenpunkt

    Kommentar


    • #17
      Zitat von knotenpunkt Beitrag anzeigen
      1. Datenstrukturkopplung:

      Wenn ich an ein anderes Objekt ein Datenobjekt/typ übergebe und dieses nicht alle Daten (bezogen auf den Typ) davon braucht, dann hab ich ne unnötige Datenstrukturkopplung
      Selbiges gilt meiner Meinung nach genauso, wenn ich eine Klasse designe und die enthaltenenen Methoden nicht jeweils IMMMER alle Daten abdecken
      heißt jede Methode sollte, wenn man eine unnötige Datenstrukturkopplung vermeiden möchte, immer alle Membervariablen verwenden.
      Außerdem wird so auch das Single-Responsibility-Prinzip eingehalten.
      Da hilft meiner Meinung nach auch nicht wenn man nach dem Prinzip der informationellen Kohesionsart vorgeht, wenn danach immer noch eine interne unnötige Datenstrukturkopplung vorliegt.

      Naja bricht man das ganze herunter auf eine Prozedur, dann ist es ja so, dass diese alle Daten bzw. Parameter von denen die Prozedur abhängt schön innerhalb verwendet (=> optimale Kopplung)
      Unterschied zu Objekten, ne Prozedur kann keinen zustand abspeichern. ok dann können wir pointer/referenzen nehmen oder aber klassen mit allerhöchstens einer Methode nehmen, wobei das ja wieder an haufen overhead erzeugen würde

      Das wäre ein pro für ne loose Funktion und ein Contra für das Verwenden einer Klasse
      Jo, das kann PHP aber nicht. Dafür musst du dann Scala oder C# nehmen. Scala kennt dazu Implicit Classes und C# nennt ein sehr ähnliches Prinzip Extension Methods. Dann gibst du aber das Information-Hiding auf. Das ist ein sehr wichtiger Punkt bei der Entwicklung komplexer Systeme. In PHP nimmst du für so was dann ganz altmodisch Prozeduren mit optionalen Zustand: Einen Service.

      Viele hier im Forum halten das auch nicht so salafistisch, wie ich es tue. Designe deine Klassen so, dass sie nur genau einen Zweck erfüllen (im Harmonie zu "Es sollte immer nur einen Grund geben, eine Klasse zu verändern"). Eine Klasse darf nur private Eigenschaften und Methoden jeder Sichtbarkeitsstufe haben (das hängt aber vor allem damit zusammen, dass PHP keine echten Getter und Setter kennt und man so keine Kontrolle darauf hat, wer welche Daten in eine Eigenschaft schreibt. Zumal PHP durch den Umstand, dass nicht existente Eigenschaften beim ersten Schreibzugriff erstellt werden, keine Sicherheit bietet, das Datendesign einer Klasse anzupassen und dabei einen Überblick zu behalten, was dann alles nicht mehr funktioniert.

      Prozedurale Programmierung kennt diese ganzen Spielweisen gar nicht. Du kannst damit nur stumpf alles Public halten und schaffst keine wohlgeformten Interfaces, deren Nutzung sich wie von selbst erklärt. Alles hat auf alles Zugriff. Du hast keine Kontrolle mehr, was nach einem Redesign noch Zugriff auf bestimmte Pointer hat und diese für was auch immer nutzt oder was alles mit irgendwelchen Array-Keys gemacht wird. Objektorientierung blüht ja da richtig auf, wo ich sehr komplexe Sachverhalten mit sehr einfachen Interaktionsmöglichkeiten bedienbar machen kann.

      Und zum Thema Pointer: Wie stellst du prozedural eigentlich Immutablilität kompakt dar? Das wäre dann etwas, wo die PHP-Welt sich den Kopf drüber zerbrechen darf, wenn PHP anfängt, Nebenläufigkeit zu beherrschen. Ich freue mich ja schon . Du wirst dann wahrscheinlich mit puren Structs rumwerfen.

      Ach noch was. DI ist immer so ein Thema für mich. Ich liebe DI. So kann ich auf absolut triviale Art und Weise sehr komplexe Programmstrukturen wie von Zauberhand überall zur Verfügung stellen, ohne das durch allen vorherigen Programmstrukturen vorher durchrouten zu müssen (dass das nicht ganz automatisch geht ist klar, hat aber die einzelnen Klassen nicht zu interessieren). In prozeduraler Programmierung kennt man dieses Prinzip auch - nur etwas anders. Da steht alles in einem globalen Namensraum zur direkt Verfügung. In der OOP-Welt würde man es unter dem Namen Singleton kennen - nur noch schlimmer.
      Nehmen wir folgendes banale Szenario, dass man in beinahe unendlich vielen Variationen auf beliebige Gebiete anwenden kann:
      Mandaten in einer großen Geschäftsanwendung mit leichten Abweichungen in der Implementation gewisser Geschäftsregeln. OOP ermöglicht das mit Interfaces relativ günstig, da ich zu einem Interface beliebige Implementationen einstecken kann. Der eine Weißt also seine Preise mit Mehrwertsteuer aus (B2C), der andere ohne (B2B). Der eine bekommt kritische Ereignisse lieber nur per Email, der andere will zusätzlich ne SMS auf Handy. Der eine loggt sich direkt über das Webinterface ein, der andere will dafür das eigene ActiveDirectory nutzen. Der eine möchte seine Bestände direkt aus der Warenwirtschaft ziehen, der andere bezieht sie aus einer CSV-Datei.

      Ich will nicht behaupten, dass man das nicht auch mit einem prozeduralen, oder gar funktionalen Modell hinbekommt - aber im Ernst. Das will kein Mensch. Vor allem, weil kein normaler Mensch so prozedural denken kann, wie er objektorientiert denken könnte.

      Dann sind da noch die IDEs, die dich bei der Entwicklung unterstützen sollen. Bei prozeduraler Programmierung hat man kaum eine Change die Zusammenhänge innerhalb einer komplexeren Anwendung nachzuvollziehen und frühzeitig Fehler zu erkennen. Bei PHP klappt das ohne größere Probleme selbst bei Projekten jenseits von 100k LOC, wenn man eine entsprechend gute IDE wie PHPStorm einsetzt und seinen Code entsprechend mit Typen auszeichnet.


      Zitat von knotenpunkt Beitrag anzeigen
      2. Design-entscheidung:
      Naja ist eher weniger eine Designentscheidung sondern viel mehr auch wieder ein Grundsatzproblem

      In dem Text frage ich mich, wo das Verhalten ban_user($other_user_or_admin) hin soll
      In den User? In den admin?
      In einen Service. Ein User kann sich nicht selbst Bannen. Ein User hat eigentlich kein richtiges Verhalten. Du kannst dem User aber Verhalten geben: Durch Erweiterungs-Klassen.

      Zitat von knotenpunkt Beitrag anzeigen
      Naja um dieses Dilema aufzuheben baut man ne dritte einheit, bspw. BanManager oder BanService (ist nur vom Namen anders^^)

      und dann die Methode ban_user($from_admin,$user)
      aber wie der zweite Namen schon verlauten lässt, ist dies ein zustandsloser Service (also wieder prozedural)
      Und ich sehe für diesen Use-Case auch kein weiteres Verhalten, welches bei User oder Admin nötig wäre.
      Ich sehe in meiner Implementation immerhin den Unterschied, dem BanUserCtrl für BannableFactory etwas zu geben, was die "Bann-Regeln" anders interpretiert oder eine andere Datenquelle zur Hilfe nimmt. Wie mache ich so was elegant prozedural?

      Zudem ist mein Code leicht zu lesen und er macht mehr, als man von außen Ahnen würde. Das ist alles ziemlich einfach gehalten und sicher kann man diese Darstellung noch diskutieren. Ich habe jetzt damit nur zeigen wollen, dass es eben Funktionalität gibt, die anderen Entwicklern nur zur Verfügung gestellt werden soll, weil sie a) jetzt noch nicht geplanten Veränderungen unterzogen werden soll, oder b) das Design aus zeitgründen etwas pfuschig ist, weswegen die (falsche) Nutzung von manchen Komponenten die Stabilität des Systems beeinträchtigen könnte.

      Zitat von knotenpunkt Beitrag anzeigen
      Um das ganze allgemeiner auszudrücken:

      ich hab nen 1 Dimensionalen Fall. Methode arbeitet nur auf eigenen Daten vom Objekt (nicht aber auf weiteren oder von weiteren abhängigen Daten eines weiteren Objekts, welches via parameter übergeben worden ist) => Das finde ich soweit eigentlich ok^^ und korrekt (eventuell kann man dies auch schon als oo-programmierung bezeichnen)

      2D-Fall: Objekt arbeitet auf eigenen Daten und auf denen von einem übergebenen Objekt
      nD-Fall Objekt arbeitet auf eigenen Daten und auf denen von (n-1) übergebenen Objekten

      Und hier haben wir schon unser Problem, weil eigentlich ist dies nichts anderes als Prozedural, abgesehen vom 1D-Fall
      Um sich nacher net noch doofe Gedanken zu machen/ doofe Designentscheidungen überlegen zu müssen. packt man hier den Algorithmus am besten ein ein "drittes bzw. n+1tes" Objekt, also ein Art Service (Ist aber wie gesagt prozedural und nicht oo und schon gar nicht Fowlers Geschmack^^)
      Der typischste Fall für ein mehrdimensionales Objektdesign ist wohl eine Baumstruktur. Also ein DOM, eine Verzeichnisstruktur, oder eine Window-Component-Lib. Es gibt gute Gründe, warum man das eher nicht prozedural macht. OpenGL war lange Prozedural. DirectX war schon immer (auch) Objektorientiert. Ein wunder: Die überwiegende Zahl der Entwickler nutzt DirectX gerne und OpenGL nicht.


      Zitat von knotenpunkt Beitrag anzeigen
      Der 1D Fall ist meiner Meinung nach korrekt oo, aber gemäß Induktionsprinzip nicht für alle n+1te Fälle -> so kann man eigentlich auch gleich den 1D-Fall vergessen
      Mehrdimensionalität ist dann die sogenannte Rekursion. Und das ist ebenfalls korrektes OO.

      Zitat von knotenpunkt Beitrag anzeigen
      3. Algorithmus (nicht in dem beitrag erwähnt):

      wenn ich prozedural einen Algorithmus entwerfe bzw. einen Use-Case umsetzte dann sieht das bspw. folgendermaßen aus

      Funktion(parameter)
      {

      Anweisung 1
      Anweisung 2

      Sub-Funktionsaufruf (eventuell parameter durchreichen oder zwischenergebnisse durchreichen)
      Sub-funktionsaufruf(....)


      ...

      return Ergebnis bzw.

      //wenn transaktionale Sicherheit gegeben sein soll

      Ändere Zustände in Daten, andernfalls eventuell schon als Nebeneffekt oben bei den Anweisungen bzw. Sub-Funktionsaufrufen

      return result

      }
      Hier verschwimmt sogar der Unterschied zwischen Prozedural (Nebeneffekte sind Teil der Natur) und Funktional (Nebeneffekte sind nicht Teil der Natur). Deine Prozedur ist hier funktional, da sie keinen äußeren Einflüssen ausgesetzt ist (abgesehen davon, dass man mit externen System wie dem Dateisystem oder einem Zufallszahlengenerator kommuniziert). Alles, was eine Prozedur kennt und kann, wird ihr in Form von Parametern durchgereicht. Der Bezug von Daten außerhalb dieses Informationsbezugs ist Nebeneffektbehaftet und traditionell die Quelle der meisten Fehler (wenn du mehr hierzu wissen willst, dann schau, was Funktionale Paradigmen unter Purismus verstehen und warum sie darauf bestehen).

      Nehmen wir jetzt an, dass du für die Berechnung einer Sache sehr viele unterschiedliche Quellen benötigst. Das macht die Parameterliste deiner Prozeduren/Funktionen tendentiell sehr lang:
      function get_delivery_method(client_id: Int, departure_country_id: String[2], destination_country_id: String[2], sub_total_net: Double, sub_total: Double, weight: Double, DimensionW: Double, DimensionH: Double, DimensionD: Double)

      Wie kann ich diese einzelnen Concerns prozedural aufteilen, so dass ich jeweils klar abgetrennte Subsysteme habe, die sich um die einzelnen Belange kümmern? Zumal diese Subsysteme dann wahrscheinlich wieder Abhängigkeiten zu anderen Komponenten haben, die dann die Anbindung zu einer Datenquelle stellen, etc. Das mündet bei prozeduraler Programmierung nur wieder in übergreifenden Funktionsaufrufen mit kreuz- und quer-referenzierten oder -kopierten Structs. Das ist von der herangehensweise ganz anders als OOP. Für einige kleine Belange mag das so besser funktionieren. Aber für komplexe Geschäftsanwendungen mit viel Code ist das einfach nichts. Und OOP eignet sich eigentlich auch für alles, was man mit prozeduralen Paradigmen erledigen kann. Warum also nicht gleich OOP?

      Zitat von knotenpunkt Beitrag anzeigen
      Der Vorteil bei dieser Algorithmusdefinition ist, dass man ganz leicht transaktionen bauen kann, also erst mal alles abchchecken dann Änderungen setzen.
      Außerdem lassen sich Seiteneffektfreie Funktionen (also Funktionen die keinen internen Zustände ändern) sehr nice parallelisieren (nebenläufig machen).

      kann man es eigentlich auch schon als oo durchgehen lassen, wenn bspw. diese Sub-funktionsaufrufe 1D-Fälle sind, die ich oben genannt habe?
      Man kann alles als OO durchgehen lassen, was OO ist. Sub-Methoden-Aufrufe sind nicht zwangsläufig OO, nur weil sie in einem Objekt stattfinden. Das kann ganz normale prozedurale Verkettung sein. Hab ich keine Schmerzen bei, wenn es das Problem fachlich korrekt, bzw. ideal löst.

      Zitat von knotenpunkt Beitrag anzeigen
      Und ein dritter Vorteil ist die "Rohr-Sicht".
      Ich hab einen Strang (sequentiell definiertern code) und rufe von diesem aus Subfunktionen auf.
      Der Vorteil besteht darin, dass ich Ergebnisse in weiteren Aufrufen von Subfunktionen vom Hauptstrang aus weitergeben kann.
      Beim objektorientiereten hab ich nur einen linearen Strang?!?, oder eher einen linearen Strang?!?

      prozedural:
      -> Aufruf
      ->Aufruf -> Aufruf
      ->Aufruf
      ->Aufruf->Aufruf->Aufruf->Aufruf
      ->Aufruf
      ->Aufruf

      (Hauptstrang ist auf der Vertikalen)

      oo stelle ich mir eher so vor, also nur die X-Achse, die Y-Achsenkomponente fällt einfach weg


      also bspw. nur sowas: ->Aufruf->Aufruf->Aufruf->Aufruf
      eventuell liege ich da auch falsch?

      Also einiges unflexibler
      Beim prozeduralen habe ich halt eher ne globale Sicht und bin damit auch flexibler und kann meinen Algorithmus freier entwerfen
      Ja, falsch. Das geht bei OOP ebenfalls genau so. Absolut kein Unterschied zu deiner Variante, bis auf, dass es diesen "globalen" Kontext nicht geben muss. Oder anders ausgedrückt: Die OOP-Experten mit dem schwarzen Gurt nutzen keine globalen Informationen und keine globalen Objekte(Singleton)/Methoden(Statisch), sehr wohl aber in Objekten verschachtelte prozedurale Ansätze.

      Zitat von knotenpunkt Beitrag anzeigen
      Also das mit den Strängen und Aufrufen, da bin ich mir natürlich nicht ganz sicher ob das so für das oo zutrifft (ich meine jetzt auch nicht nur die Funktionsaufrufe, sondern auch die Ergebnisverarbeitung bzw. die Sichtweise im Allgemeinen)
      Da die Daten ja zusammengefasst mit dem Verhalten sind, ist ein Ausbruch aus dieser Sicht eigentlich nicht möglich, zumindest wenn man legitim oo sein möchte.
      Außerdem, da die wichtige Y-komponente wegfällt, ist man einigen Einschränkungen unterlegen und hat damit flexibilitätseinbußen.
      So, ich stoppe hier mal. Das bringt alles nicht viel und kostet zu viel Zeit. Ich habe nicht das Gefühl, dass ich dir damit irgendwie helfen kann, sondern denke, dass du irgendwie versuchst, um das Thema OO herumzukommen und die Argumente dafür so streust, dass du dir und anderen dann erfolgreich einreden kannst, dass OOP nichts dolles ist. Ich habe jetzt auch nicht noch mal über den Text gelesen. Wahrscheinlich sind da noch ein paar doofe Fehler drin.

      Ganz im Gegenteil. OOP ist Prozedural 2.0. Kann alles, was prozedural kann und eben noch viel mehr, was du optional(!) nutzen kannst. Du bekommst in komplexen Umgebungen die gleichen Ergebnisse in einem Zehntel der Zeit. Wenn du es nicht willst - deine Entscheidung. Aber lass das hier beenden, das ist traurig.

      Kommentar


      • #18
        Zitat von knotenpunkt
        Was meinst du mit Querfront?
        Naja "Vebrechen an der Menschheit", kann ich aus diesem Artikel nicht ableiten, weil ich ihn vorher noch gar nicht gekannt habe^^
        Diese Ableitung habe ich selbst getroffen, aber nicht auf der Basis die du wahrscheinlich denkst.
        Okay, dann sorry, dass ich das einfach angenommen haben. Der Artikel ist jedenfalls einer der ersten Suchtreffer für „martin fowler domain driven design with service objects“, was deine Formulierung aus #1 ist. Das sollte also schon so grob das sein, was du meinst, wenn du Fowlers Standpunkt zu dem Thema Entities/Services erwähnst, auch wenn es offensichtlich nicht die Quelle ist, auf die du dich stützt. Wie auch immer: Mir ging es darum, dass Fowler das erwähnte Konzept nicht deshalb kritisiert, weil – ich formuliere es mal irgendwie plakativ – OOP überflüssig ist, sondern aus dem gegenteiligen Grund, dass das seiner Meinung nach tendenziell zu wenig (oder falsches) OOP ist. Fowler ist deshalb meines Erachtens niemand, der besonders gute Argumente liefert, um eine generelle These gegen OOP zu stützen, da er meines Wissens in jedem Fall bei den OOP-Befürwortern anzusiedeln ist. Eure Kritik ist in diesem Fall also vielleicht ähnlich, aber die Standpunkte dahinter und die Schlüsse daraus dürften dann doch sehr entgegengesetzt sein. Deshalb finde ich es für deine Position „quer“, mit Fowler zu argumentieren, weil er deinem grundsätzlichen Standpunkt gegen OOP (die „Verbrechen an der Menschheit“-Geschichte, die nun mal eine stark positionsergreifende Aussage ist) wahrscheinlich noch viel weniger abgewinnen könnte als dem Konzept von Entities und Services in DDD.

        Zitat von Martin Fowler
        Now object-oriented purism is all very well, but I realize that I need more fundamental arguments against this anemia. In essence the problem with anemic domain models is that they incur all of the costs of a domain model, without yielding any of the benefits. The primary cost is the awkwardness of mapping to a database, which typically results in a whole layer of O/R mapping. This is worthwhile iff you use the powerful OO techniques to organize complex logic. By pulling all the behavior out into services, however, you essentially end up with Transaction Scripts, and thus lose the advantages that the domain model can bring. As I discussed in P of EAA, Domain Models aren't always the best tool.
        Zitat von knotenpunkt
        Auch der Meinung bin ich, dass es eigentlich Schwachsinn ist Klassen, ORM, etc zu verwenden, wenn es im Endeffekt eh wieder prozedural ist!
        Ok die Kosten auf sich zu nehmen, des lohnt sich erst dann, wenn man wirklich von den OO-Techniken profitiert. Eine Profitierung ist ausgeschlossen, wenn Verhalten und Daten getrennt werden!

        Wie diese Profitierung aussieht, das könnt ihr mir gerne verraten!^^
        Wie gesagt, da würde ich erst mal wieder versuchen, zu definieren, was wir hier als prozedural und was wir als OOP bezeichnen. rkr hat es glaube ich in seinem letzten Beitrag auch noch mal erwähnt: In PHP sind alle Strukturen (das wären übrigens mehr oder weniger nur Arrays), die nicht auf dem syntaktischen Konstrukt einer Klasse basieren, absolut fragil und unverlässlich – weil ohne Einschränkungen modifizierbar. Es gibt in PHP kein struct/typedef wie etwa in C. Arrays sind reine Key/Value-Container. Wenn du eine Funktion schreibst, die ein Array erwartet, das einen bestimmten Aufbau hat, gibt es keinen syntaktischen Weg, diesen Aufbau auch nur ansatzweise sicherzustellen. Der Funktion kann absolut jedes Array übergeben werden. Der Compiler/Interpreter kann nicht entscheiden, ob ein Array die erwartete Struktur hat oder nicht. Das heißt, dass du, wenn du auch nur grundlegend sicherstellen willst, dass einer Funktion eine bestimmte selbstdefinierte Datenstruktur übergeben wird, darauf angewiesen bist, das syntaktische Konstrukt der Klasse zu nutzen. Und ja: Eine zumindest grundsätzliche Typverlässlichkeit halte ich für sehr profitabel. Oder umgekehrt gesagt: Ich sehe keinen Grund, darauf zu verzichten.

        Ich halte in dem Zusammenhang auch eher wenig von so Ad-hoc-Datenstrukturen, also Arrays, die gerade genau die Felder enthalten, die irgendeine spezielle Funktion benötigt. Das kann man schon mal machen, aber am Ende hat man dann im schlechten Fall 20 Dinger davon in seiner Anwendung rumfliegen, obwohl die vielleicht immer nur Variationen der gleichen zwei logischen Sinneinheiten sind („User“ und „Post“ zum Beispiel). Für mich ist das Chaos pur, weil du nie weißt, welche Funktion, die derlei Datenstrukturen generiert, dir wieso was liefert. Zudem führt das zu haufenweise Redundanz, weil aller Generierungscode für diese Datenstrukturen bis auf Nuancen identisch ist, aber wegen der Nuancen nicht zusammengefasst werden kann.

        Gemäß oben genannten Induktionsprinzip und Fowlers Aussage, dass man profitiert, wenn man nach oo-konzepten programmiert, dann gilt das auch für höherwertigers, also auf gut deutsch, man kann nur vollständig dann davon profitieren, wenn auch wirklich alles vollständig gemäß oo-konzepten programmiert worden ist. Und dazu gehört NICHT ein aufgeweichtes Service-Layer prinzip, dass teilweise prozedurale bzw. nicht oo-prinzipien folgende Programmierparadigmas folgt
        Dann profitiert man eben nur unvollständig. Was auch immer das bedeuten soll. Klingt jedenfalls besser, als gar nicht zu profitieren.

        Zusammenfassend kann man sagen, dass OO nur dann ein Verbrechen an der Menschheit ist, wenn man es sinnlos einsetzt, um dann eh doch wieder prozedural aber mit Mehrkosten zu arbeiten.
        Na ja, das klingt immerhin schon (etwas) differenzierter, auch wenn es vielleicht in Richtung stating the obvious geht. (Jedenfalls, wenn man vom letzten Nebensatz absieht, der dann wohl wieder diskutabel ist.)

        Vor vielen vielen vielen Jahren hab ich mal diesen Text hier verfasst: http://www.php.de/forum/webentwicklu...hwachsinn-oder
        Der Thread aus dem Zitat ist ein ganz guter Aufhänger für etwas, das ich hier auch noch unterbringen wollte, auch wenn es Meta-Ebene ist: Was versprichst du dir letztlich davon, hier gegen OOP anzuschreiben? Oder gegen DDD? Oder gegen Aspekte von Laravel? Ich persönlich finde solche Diskussionen durchaus nicht uninteressant – gerade auch, wenn sie Beiträge wie die von rkr hervorbringen –, aber de facto nutzt sozusagen die halbe Informatik „Objektorientierung“. (Wie gesagt: Was ist die Definiton?) Ich würde mich da schon auch mal fragen, ob du dich da nicht ein wenig in deinen Ansichten verrennst. Es ist schon quantitativ völlig unrealistisch, dass bislang noch niemandem aufgefallen ist, dass OOP nichts bringt – oder wie auch immer.

        (Ich muss hier mal aufhören. Vielleicht schreibe ich später noch was, wenn ich das alles noch mal in Ruhe gelesen habe. Noch ein vorläufiger Schlusssatz: Erklär du uns doch vielleicht umgekehrt mal anschaulich, wieso sich bei prozeduraler Programmierung immer alles – auch die Probleme von OOP, die du feststellst – in Wohlgefallen auflöst.)

        Kommentar


        • #19
          Zitat von CrocoBlack Beitrag anzeigen
          Der Sinn deines Posts war also nicht, Vorteile von OOP aufzuzeigen?

          Ich dachte schon das wäre der Sinn und du würdest einfach nur Blödsinn schreiben. Zum Glück war dem nicht der Fall.

          Wünsche einen schönen Tag.
          Seit wann ist eine professionelle Einschätzung Blödsinn? Ja danke, dir auch.

          Kommentar


          • #20
            Nachdem ich den Text von Martin Fowler durchgelesen habe, kam ich zu dem Entschluss, dass wir (ich und Fowler) uns in allen Punkten bis auf einen gleicher Meinung sind!
            The anemic domain (%) model is really just a procedural style design
            Es ist genau das, was ich oben gesagt habe, dass entity-classes (also klassen ohne verhalten) und service-classes (zustand-lose klassen mit auschließlichlichem verhalten) dem prinzip der prozeduralen programmierung folgt!

            Auch das was ein M. Fowler schreibt, kann und sollte man kritisch betrachten:

            http://blog.ralfw.de/2010/08/leiden-...-oop-2010.html

            Und viele der heute so populären OO-Techniken müssen ebenfalls nicht der Weisheit letzter Schluss sein. Es gibt viele spannende Konzepte die der "ursprünglichen" Idee von OOP möglicherweise sogar noch näher kommen:

            http://www.heise.de/developer/artike...t-1240993.html
            http://blog.ralfw.de/2010/02/event-b...r-nachste.html

            Solche und andere Modelle werden erst mit OOP vernünftig realisierbar. Stell Dir mal vor, Du wolltest so was prozedural umsetzen, Komponenten die man ähnlich wie Legosteine zusammenstecken kann


            VG
            Jack
            -

            Kommentar


            • #21
              Zitat von jack88
              http://www.heise.de/developer/artike...t-1240993.html
              http://blog.ralfw.de/2010/02/event-b...r-nachste.html

              Solche und andere Modelle werden erst mit OOP vernünftig realisierbar. Stell Dir mal vor, Du wolltest so was prozedural umsetzen, Komponenten die man ähnlich wie Legosteine zusammenstecken kann
              Ich würde zu dem "EBC"-Ansatz (der nun wahrlich nicht neu ist) nur noch kurz einwerfen, dass da ein großes Problem begraben liegt. Wenn du deine Anwendungen schwerpunktmäßig auf einem Eventsystem aufbaust, dann hast du keine Kontrolle mehr darüber, was sich alles noch für einen Event registrieren muss, wenn du ein neues Event einführst (das ist aber zum Teil auch bei StandardOO der Fall). Für TDD ist EBC toll, aber für die Entwicklung eines komplexeren Systems eher nicht. Besonders dann nicht, wenn es sich bei diesen Komponenten nicht um gut durchgetestete Standardkomponenten handelt. Finde mal den Fehler in so einem System, wenn ein Rückkanal nicht feuert. Diese Verbindung kannst du mit traditionellem OO kaum verpassen. Zumal der Verlauf von Informationen durch diese neue Abstraktionsschicht so verkompliziert wird, dass du bei der Entwicklung bald nicht mehr groß auf die Unterstützung einer IDE zählen kannst.

              Ich verstehe auch nicht, welches DI-Problem da mit EBC gelöst wird. Hast du das verstanden?

              Kommentar


              • #22
                @rkr
                Nicht alles was irgendwie mit Events zu tun hat gehört in einen Topf. Ich glaube das Prinzip hinter EBC ist hier noch nicht ganz klar geworden

                Mal angenommen wir hätten folgendes Szenario mit zwei Komponenten (ähnlich wie in dem Beispiel auf heise.de):

                Einen HalloWeltGenarator zum erzeugen des Textes „Hallo Welt!“ und eine Ausgabe-Komponente ConsoleWriter zum Schreiben auf die Standardausgabe.

                Die herkömmliche Herangehensweise könnte wahrscheinlich in etwa so aussehen:
                (Ich verzichte hier bewusst auf DI um den Vergleich so einfach wie möglich zu halten)

                PHP-Code:
                class ConsoleWriter {
                    public function 
                write($text) {
                        echo 
                $text.PHP_EOL;
                    }
                }

                class 
                HalloWeltGenerator {
                    protected 
                $consoleWriter null;
                    public function 
                __construct() {
                        
                $this->consoleWriter = new ConsoleWriter();
                    }
                    public function 
                generate() {
                        
                $text "Hallo Welt!";
                        
                $this->consoleWriter->write($text);
                    }
                }

                $halloWeltGenerator = new HalloWeltGenerator();
                $halloWeltGenerator->generate(); 
                Bereits in diesem sehr einfachen Szenario haben wir im HalloWeltGenerator schon eine erste Abhängigkeit zur ConsoleWriter.

                Mal angenommen wir möchten „Hallo Welt!“ jetzt noch in Großbuchstaben haben. Dann bräuchten wir eine weitere auf Textkonvertierungen spezialisierte Komponente, also einen TextConverter.

                Bevor wir mit der (trivialen) Implementierung des TextConverters beginnen, sollten wir uns zuerst Gedanken über die Beziehung zwischen dem TextConverter und dem HalloWeltGenerator machen.

                Soll sich der Generator eines Textkonverters bedienen um seinen Text in das gewünschte Ausgabeformat zu bringen, oder besser der Textkonverter mit Generatoren umgehen können, oder vielleicht sogar auch Beides?

                PHP-Code:
                class TextConverter {
                    protected 
                $generator null;
                }
                class 
                HalloWeltGenerator {
                    protected 
                $textConverter null;

                Egal wie wir uns entscheiden, spätestens ab dieser Stelle wird es „unschön“, wir fangen an immer mehr Komponenten voneinander abhängig zu machen. Da hilft uns auch DI nicht weiter, denn DI ändert rein gar nichts an den Abhängigkeiten, es verändert nur die Art der Kopplung.

                EBC geht hier einen anderen Weg. Abhängigkeiten zwischen einzelnen Komponenten sollen komplett vermieden werden. Natürlich müssen Komponenten miteinander kommunizieren können, aber sie sollten voneinander keine Kenntnis besitzen. Erst eine simple Board-Komponente verdrahtet mehrere Komponenten zu einem komplexeren Bauteil. Die Abhängigkeiten die wir zuvor in den Komponenten selbst hatten, sind nun in eine einfache Builder-Komponente gewandert, die lediglich dem Zweck dient mehrere Komponenten zu einem größeren Ganzen zu verdrahten (das Konzept des Boards ist in etwa so ähnlich wie wenn man auf der Kommandozeile mehrere Befehle mittels Pipe miteinander „verbindet“).

                Hier der HalloWeltGenerator wie man ihn mit EBC umsetzen könnte:

                PHP-Code:
                abstract class EbcComponent {
                    public 
                $outputPin null;
                    protected function 
                delegate() {
                        
                $args func_get_args();
                        
                $handler $this->outputPin;
                        if(
                false === is_callable($handler)) {
                            throw new 
                RuntimeException("Delegate outputPin must be a callable type!");
                        }
                        if(
                null !== $handler) {
                            
                call_user_func_array($handler$args);
                        }
                    }
                }

                class 
                HalloWeltGenerator extends EbcComponent {
                    public function 
                input() {
                        
                $text "Hallo Welt!";
                        
                $this->delegate($text);
                    }
                }

                class 
                ConsoleWriter extends EbcComponent {
                    public function 
                input($text) {
                        echo 
                $text.PHP_EOL;
                    }
                }


                class 
                HalloWeltBoard {
                    protected 
                $halloWeltGenerator null;
                    protected 
                $consoleWriter null;
                    public function 
                __construct() {
                        
                $this->halloWeltGenerator = new HalloWeltGenerator();
                        
                $this->consoleWriter = new ConsoleWriter();
                        
                // ab hier erfolgt die eigentliche Verkabelung
                        
                $this->halloWeltGenerator->outputPin= array($this->consoleWriter"input");
                    }
                    public function 
                run() {
                        
                $this->halloWeltGenerator->input();
                    }
                }

                $board = new HalloWeltBoard();
                $board->run(); 
                Was man an diesem einfachen Beispiel bereits gut erkennen kann ist, daß HalloWeltGenerator und ConsoleWriter komplett unabhängig voneinander sind! Jetzt nehmen wir noch eine weitere TextConverter-Komponen dazu:

                PHP-Code:
                class TextConverter extends EbcComponent {
                    public function 
                inputToUpper($text) {
                        
                $text strtoupper($text);
                        
                $this->delegate($text);
                    }
                    public function 
                inputToLower($text) {
                        
                $text strtoupper($text);
                        
                $this->delegate($text);
                    }

                und bauen uns einfach ein neues Board:

                PHP-Code:
                class HalloWeltBoard2 {
                    protected 
                $halloWeltGenerator null;
                    protected 
                $consoleWriter null;
                    protected 
                $textConverter null;
                    public function 
                __construct() {
                        
                $this->halloWeltGenerator = new HalloWeltGenerator();
                        
                $this->consoleWriter = new ConsoleWriter();
                        
                $this->textConverter = new TextConverter();
                        
                // ab hier erfolgt die eigentliche Verkabelung
                        
                $this->halloWeltGenerator->outputPin= array($this->textConverter"inputToUpper");
                        
                $this->textConverter->outputPin= array($this->consoleWriter"input");
                    }
                    public function 
                run() {
                        
                $this->halloWeltGenerator->input();
                    }
                }
                $board2 = new HalloWeltBoard2();
                $board2->run(); 
                Auch hier gibt es keine einzige Abhängigkeit zwischen TextConverter, ConsoleWriter und HalloWeltGenerator.

                Natürlich ist das nur ein sehr simples Beispiel und EBC ist nicht so einfach und unproblematisch wie es hier vielleicht auf den ersten Blick aussehen könnte. Aber dieses Beispiel zeigt meiner Ansicht nach ganz gut, daß OOP erstens ein sehr mächtiges Werkzeug ist und zweitens, daß das letzte Wort in Sachen OOD noch lange nicht gesprochen wurde Deshalb halte ich eine kritische Auseinandersetzung mit den heute so selbstverständlichen OOP-Techniken für mehr als angebracht.

                Zum Schluss noch ein kleine Frage Mal angenommen es gibt eine Person, diese hat einen Kopf, zwei Hände, eine Frau, Kinder, ein Haus, ein Auto und ein Boot. Wie würde/könnte man das aus OOP-Sicht modellieren?

                VG
                Jack

                Ich verstehe auch nicht, welches DI-Problem da mit EBC gelöst wird.
                Offensichtlich bringt der Heise-Autor Abhängigkeiten und lose Kopplung in Verbindung mit DI leider ein wenig durcheinander.
                -

                Kommentar


                • #23
                  Zitat von knotenpunkt Beitrag anzeigen
                  Als erstes möchte ich in diesem Post folgendes vorstellen:
                  Es ist genau das, was ich oben gesagt habe, dass entity-classes (also klassen ohne verhalten) und service-classes (zustand-lose klassen mit auschließlichlichem verhalten) dem prinzip der prozeduralen programmierung folgt!




                  Auch der Meinung bin ich, dass es eigentlich Schwachsinn ist Klassen, ORM, etc zu verwenden, wenn es im Endeffekt eh wieder prozedural ist!
                  Ok die Kosten auf sich zu nehmen, des lohnt sich erst dann, wenn man wirklich von den OO-Techniken profitiert. Eine Profitierung ist ausgeschlossen, wenn Verhalten und Daten getrennt werden!

                  Wie diese Profitierung aussieht, das könnt ihr mir gerne verraten!^^

                  Nun ja, es mag zu einem gewissen Punkt prozedural sein. Aber die Objektorientierung hat schließlich nicht das Programmieren neu erfunden, eher verbessert. Also wird es da wohl Ähnlichkeiten, sozusagen "alte Zöpfe" noch geben, die vielleicht nicht unbedingt besser sind, aber modernisiert. ORM´s müssen nicht immer "prozedural" sein. Denn auch hier, genauso wie im MVC Pattern und eigentlich wie in jeden anderen Programmier-Pattern auch, gibt es eine Grobdefinition in der Theorie und in der Praxis mehrere unterschiedliche Philosophien dazu. Das möchte ich jetzt genauer erklären: Bei ORM´s gibt es zwei Philosophien: Bei Philosophie eins hat man "aktive" Methoden, welche weitere Methoden in sich selbst wieder aufrufen (vom eigenen Model/Objekt, von anderen Models usw.), also die quasi im Model etwas Logik aus dem Controller übernehmen. Das ist aber schlecht, wenn man sich seine Models generieren lassen will. Was mich zu Philosophie zwei führt: Für die Leute, die lieber ihre Models generieren wollen, gibt es die Möglichkeit der "passiven" Methoden. Das heißt z. B. eine fetch Methode liest wirklich nur Daten aus der DB ein und tut sonst nichts weiter, keine JOINS, kein GROUP BY, kein gar nichts. Das kann man dann z. B. vor dem fetch festlegen, was der fetch noch zusätzlich tun soll. Aber ohne diese Angaben würde das im fetch nicht geschehen. Also in der Hinsicht passiv. Das heißt die Kopplungslogik der Daten steck im Controller, nicht im Model. Deswegen kann man hier Models, welche einfach nur eine Tabelle repräsentieren, auch generieren lassen und muss da nichts mehr händisch anfassen. Ich wüsste nicht, wo man das im Strukturalen so haben könnte. Mit Funktionen mag das vielleicht auch noch gehen, aber schon sehr bescheiden.

                  Ein weiteres Beispiel dazu: Das MVC,Pattern ist ja grob definiert. Jedoch lässt es auch noch genug Platz für Interpretation. Vorallem gibt es hier noch das alte leidige Thema, wo man denn am besten die Eingabeprüfungen vom Benutzer hinsteckt. Zwei Philosophien haben sich herauskristallisiert: Die einen haben es lieber in der View. Die anderen, die meinen, dass die View keine Logik enthält, haben es lieber im Controller.

                  Nur um zwei prominente Beispiel anzusprechen, die Vorteile gegenüber prozeduraler Programmierung aufzeigen. Hier ist durch eine saubere Trennung definitiv eine Profitierung gegeben. Man kann in OO Prinzipien besser auftrennen als in der prozeduralen Welt. Es gibt dort zwar Funktionen oder Subrutinen, aber letzendlich ist alles vermischt und nichts sauber getrennt.


                  MFG derwunner

                  Kommentar


                  • #24
                    Zitat von derwunner Beitrag anzeigen


                    Nun ja, es mag zu einem gewissen Punkt prozedural sein. Aber die Objektorientierung hat schließlich nicht das Programmieren neu erfunden, eher verbessert. Also wird es da wohl Ähnlichkeiten, sozusagen "alte Zöpfe" noch geben, die vielleicht nicht unbedingt besser sind, aber modernisiert. ORM´s müssen nicht immer "prozedural" sein. Denn auch hier, genauso wie im MVC Pattern und eigentlich wie in jeden anderen Programmier-Pattern auch, gibt es eine Grobdefinition in der Theorie und in der Praxis mehrere unterschiedliche Philosophien dazu. Das möchte ich jetzt genauer erklären: Bei ORM´s gibt es zwei Philosophien: Bei Philosophie eins hat man "aktive" Methoden, welche weitere Methoden in sich selbst wieder aufrufen (vom eigenen Model/Objekt, von anderen Models usw.), also die quasi im Model etwas Logik aus dem Controller übernehmen. Das ist aber schlecht, wenn man sich seine Models generieren lassen will. Was mich zu Philosophie zwei führt: Für die Leute, die lieber ihre Models generieren wollen, gibt es die Möglichkeit der "passiven" Methoden. Das heißt z. B. eine fetch Methode liest wirklich nur Daten aus der DB ein und tut sonst nichts weiter, keine JOINS, kein GROUP BY, kein gar nichts. Das kann man dann z. B. vor dem fetch festlegen, was der fetch noch zusätzlich tun soll. Aber ohne diese Angaben würde das im fetch nicht geschehen. Also in der Hinsicht passiv. Das heißt die Kopplungslogik der Daten steck im Controller, nicht im Model. Deswegen kann man hier Models, welche einfach nur eine Tabelle repräsentieren, auch generieren lassen und muss da nichts mehr händisch anfassen. Ich wüsste nicht, wo man das im Strukturalen so haben könnte. Mit Funktionen mag das vielleicht auch noch gehen, aber schon sehr bescheiden.

                    Ein weiteres Beispiel dazu: Das MVC,Pattern ist ja grob definiert. Jedoch lässt es auch noch genug Platz für Interpretation. Vorallem gibt es hier noch das alte leidige Thema, wo man denn am besten die Eingabeprüfungen vom Benutzer hinsteckt. Zwei Philosophien haben sich herauskristallisiert: Die einen haben es lieber in der View. Die anderen, die meinen, dass die View keine Logik enthält, haben es lieber im Controller.

                    Nur um zwei prominente Beispiel anzusprechen, die Vorteile gegenüber prozeduraler Programmierung aufzeigen. Hier ist durch eine saubere Trennung definitiv eine Profitierung gegeben. Man kann in OO Prinzipien besser auftrennen als in der prozeduralen Welt. Es gibt dort zwar Funktionen oder Subrutinen, aber letzendlich ist alles vermischt und nichts sauber getrennt.


                    MFG derwunner
                    Hier muss ich derwunner ausnahmsweise mal recht geben.
                    Zitat von derwunner
                    "Ein FISI ist auf gut-deutsch der Netzwerker. Das heißt Du gehst rauß zum Kunden oder auf die Straße und verlegst Leitungen" - derwunner 2015

                    Kommentar


                    • #25
                      jack88 Sorry, dass ich erst jetzt zu einer Antwort komme.

                      Zitat von jack88 Beitrag anzeigen
                      @rkr
                      Nicht alles was irgendwie mit Events zu tun hat gehört in einen Topf. Ich glaube das Prinzip hinter EBC ist hier noch nicht ganz klar geworden
                      Vielen Dank für die anschaulichen Beispiele. Das Prinzip von EBC war mir allerdings schon klar. Was mir nicht klar war ist, welches Problem damit gelöst wird, was man nicht genau so auch mit DI lösen kann...

                      Du hast jetzt eigentlich nur 1:1 wiedergegeben, was eh schon auf den von dir verlinkten Seiten stand. Nehmen wir das Beispiel mal und implementieren es mit einer ähnlich losen Kopplung in PHP nach:

                      PHP-Code:
                      <?php
                      interface TextComponent {
                          public function 
                      write(string $text);
                      }

                      class 
                      ConsoleWriter implements TextComponent {
                          public function 
                      write(string $text) {
                              
                      printf("%s: %s\n"date('Y-m-d H:i:s'), $text);
                          }
                      }

                      class 
                      TextConverter implements TextComponent {
                          
                      /** @var TextComponent */
                          
                      private $subject;

                          public function 
                      __construct(TextComponent $subject) {
                              
                      $this->subject $subject;
                          }

                          public function 
                      write(string $text) {
                              
                      $this->subject->write($text);
                          }
                      }

                      class 
                      HalloWeltGenerator {
                          
                      /** @var TextComponent */
                          
                      private $subject;

                          public function 
                      __construct(TextComponent $subject) {
                              
                      $this->subject $subject;
                          }

                          public function 
                      write() {
                              
                      $this->subject->write("Hello World");
                          }
                      }

                      /**
                       * Diese Klasse darf es in purem DI eigentlich nicht geben...
                       */
                      class HalloWeltBoard {
                          
                      /** @var HalloWeltGenerator */
                          
                      private $generator null;

                          public function 
                      __construct() {
                              
                      $writer = new ConsoleWriter();
                              
                      $converter = new TextConverter($writer);
                              
                      $this->generator = new HalloWeltGenerator($converter);
                          }

                          public function 
                      run() {
                              
                      $this->generator->write();
                          }
                      }

                      $board = new HalloWeltBoard();
                      $board->run();
                      Finde ich jetzt einfacher und klarer. Und tut exakt das Gleiche.

                      Etwa gleich viel Code, richtig? Das gleiche noch mal in Scala:

                      PHP-Code:
                      import java.text.SimpleDateFormat
                      import java
                      .util.Calendar

                      trait TextComponent {
                          
                      def invoke(textString): Unit
                      }

                      class 
                      ConsoleWriter extends TextComponent {
                          
                      def invoke(textString) = {
                              
                      val ts = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime())
                              
                      println("%s: %s".format(tstext))
                          }
                      }

                      case class 
                      TextConverter(textCompTextComponent) extends TextComponent {
                          
                      def invoke(textString) = textComp.invoke(text.toUpperCase)
                      }

                      case class 
                      HelloWorldGenerator(convTextConverter) {
                          
                      def run() = conv.invoke("Hello World")
                      }

                      val writer = new ConsoleWriter()
                      val textConverter TextConverter(writer)
                      val generator HelloWorldGenerator(textConverter)

                      generator.run(); 

                      Kommentar


                      • #26
                        Sorry, dass ich erst jetzt zu einer Antwort komme.
                        mach doch nichts, schön dass Du noch mal darauf eingegangen bis

                        Nehmen wir das Beispiel mal und implementieren es mit einer ähnlich losen Kopplung in PHP nach
                        Ok, dann schauen wir uns die Komponenten vielleicht zuerst mal etwas genauer an und definieren als Allererstes ihre Verantwortung:

                        ConsoleWriter – Schreibt Text auf die Konsole/Standardausgabe
                        TextConverter – Modifiziert Text
                        HalloWeltGenerator – Generiert den Text „Hallo Welt“

                        Drei einfache Klassen mit ganz klarer Verantwortung.

                        Nach meinem Verständnis könnten die Kontrakte der Komponenten somit wie folgt aussehen:

                        PHP-Code:
                        interface ConoleWriterInterface {
                            public function 
                        write($text);
                        }

                        interface 
                        TextConverterInterface {
                            public function 
                        convert($text);
                        }

                        interface 
                        HalloWeltGenerator {
                            public function 
                        generate();

                        Jetzt schauen wir uns mal an was Deine Komponenten so machen:

                        ConsoleWriter – schreibt Text auf Konsole, alles OK – nichts zu beanstanden
                        TextConverter – schreibt Text auf Konsole? Es wird nichts konvertiert, wahrscheinlich vergessen?
                        HalloWeltGenerator – Generiert Text + schreibt Text

                        Der Textkonverter ist akt. nur ein Wrapper für den ConsoleWriter. Der HalloWeltGenerator ist sowohl Generator als auch Writer zugleich. Also eine Klasse die mehr als eine Aufgabe hat, ergo das SRP-Prinzip verletzt.

                        Ich gehe einfach mal davon aus, daß das mit dem Konverter (der nichts konvertiert) nur ein Versehen war. Wenn dem so ist, dann würde auch dieser das SRP-Prinzip verletzen (Konverter + Writer) und der HalloWeltGenerator würde damit zusätzlich auch noch zum Konverter mutieren.

                        Abgesehen von den SRP-Problemen ist der TextConverter nun vom ConsoleWriter und der HalloWeltGenerator vom TextConverter abhängig.

                        Alles in allem kein gutes Design. Unter dem Strich haben wir jetzt zwei Komponenten die unnötig von anderen Komponenten abhängig sind und deren Verantwortung über ihre eigentliche Aufgabe hinausgeht.

                        VG
                        Jack
                        -

                        Kommentar


                        • #27
                          Zitat von jack88 Beitrag anzeigen
                          TextConverter – schreibt Text auf Konsole? Es wird nichts konvertiert, wahrscheinlich vergessen?
                          Ja, ich dachte, das wäre klar. Es ging auch nicht um das Tun an sich, sondern um die Figur:

                          PHP-Code:
                          class TextConverter implements TextComponent {
                              
                          /** @var TextComponent */
                              
                          private $subject;

                              public function 
                          __construct(TextComponent $subject) {
                                  
                          $this->subject $subject;
                              }

                              public function 
                          write(string $text) {
                                  
                          $this->subject->write(strtoupper($text));
                              }

                          Was tut diese Klasse? Die schreibt gegen ein Interface (TextComponent::write), dessen Funktion nicht weiter spezifiziert ist. Damit tut diese Klasse genau nur eine Sache. Keine direkte Kopplung. Nur eine indirekte Kopplung durch den Zwang des Interfaces TextComponent.

                          Zitat von jack88 Beitrag anzeigen
                          HalloWeltGenerator – Generiert Text + schreibt Text
                          Nein, da wird nichts "geschrieben". Da passiert nichts, außer das Text erzeugt und gegen Unbekannt weiterdelegiert wird. Auch hier wieder indirekte Kopplung an TextComponent.

                          Zitat von jack88 Beitrag anzeigen
                          Der Textkonverter ist akt. nur ein Wrapper für den ConsoleWriter.
                          Genau wie die EBC auch.

                          Zitat von jack88 Beitrag anzeigen
                          Der HalloWeltGenerator ist sowohl Generator als auch Writer zugleich.
                          Naja, bis hierher hast du die gleiche Kopplung in deinem Generator ja auch. Aber ich weiß schon, worauf du hinaus willst.

                          Zitat von jack88 Beitrag anzeigen
                          Also eine Klasse die mehr als eine Aufgabe hat, ergo das SRP-Prinzip verletzt.
                          Zitat von jack88 Beitrag anzeigen
                          Abgesehen von den SRP-Problemen ist der TextConverter nun vom ConsoleWriter und der HalloWeltGenerator vom TextConverter abhängig.
                          Hier wird es spannend: Eigentlich sind TextConverter und ConsoleWriter nur von TextComponent abhängig. Diese Abhängigkeit findet sich bei dir auch wieder, jedoch eine Ebene generischer: EbcComponent. Das ist aber eigentlich egal, wenn es geht ja im Kern um etwas anderes:

                          PHP-Code:
                          class ConsoleWriter {
                              public function 
                          write($text) {
                                  
                          printf("%s: %s\n"date('Y-m-d H:i:s'), $text);
                              }
                          }

                          class 
                          TextConverter {
                              public function 
                          convert($text) {
                                  return 
                          strtoupper($text);
                              }
                          }

                          class 
                          HelloWorldGenerator {
                              public function 
                          generate() {
                                  return 
                          "Hello World";
                              }
                          }

                          class 
                          Handler {
                              
                          /** @var callable */
                              
                          private $callback;

                              public function 
                          __construct(callable $callback) {
                                  
                          $this->callback $callback;
                              }

                              public function 
                          delegate($data null) {
                                  
                          call_user_func($this->callback$data);
                              }
                          }

                          $writer = new Handler(function ($text) {
                              (new 
                          ConsoleWriter())->write($text);
                          });

                          $converter = new Handler(function ($text) use ($writer) {
                              
                          $writer->delegate((new TextConverter())->convert($text));
                          });

                          $converter->delegate((new HelloWorldGenerator())->generate()); 
                          (Scala)

                          PHP-Code:
                          class ConsoleWriter {
                              
                          def write(textString) = println(text)
                          }

                          class 
                          TextConverter {
                              
                          def convert(textString) = text.toUpperCase
                          }

                          class 
                          HelloWorldGenerator {
                              
                          def generate() = "Hello World"
                          }

                          class 
                          Handler[T](out: (T) => Unit) {
                              
                          def delegate(inT) = out(in)
                          }

                          val writer = new Handler[String]((new ConsoleWriter).write)
                          val converter = new Handler[String](in => writer.delegate((new TextConverter).convert(in)))
                          converter.delegate((new HelloWorldGenerator).generate()) 
                          Das ist deine Variante in etwas anderer Form, mit einer zusätzlichen Containerklasse.

                          Die "Abhängigkeit" in meiner ersten Implementation besteht im Grunde in der Annahme, dass eine Komponente über ein bestimmtes Interface mit der Außenwelt kommuniziert. Das an sich ist dann nicht neutral und zwingt mein Interface in ein bestimmtes Aussehen. In dem hier gezeigten Beispiel sind die Komponenten völlig neutral (die implementieren weder ein betimmtes Interface, noch eine abstrakte Klasse und halten sich auch sonst an keine Absprachen). Allerdings ist diese Implementation völlig unnachvollziehbar. Stell dir mal vor, du hast über 1000 solcher Verknüpfungen in einer großen Applikation. Am Beispiel des TextConverters sehen wir ja, dass wir nach wie vor eine in die Tiefe gerichtete Architektur haben.

                          Ich kann allerdings Wrapperklassen um ConsoleWriter und TextConverter setzen und so die Kopplung auf eben diese Wrapper reduzieren. SRP wäre dann durch die reine Weitergabe der Daten befriedigt.

                          Kommentar


                          • #28
                            Wo mehr als eine Komponente beteiligt ist, sind Abhängigkeiten grundsätzlich unvermeidbar. Das liegt sozusagen in der Natur der Sache. Abhängigkeiten lassen sich nicht komplett eliminieren, sondern nur verlagern.

                            Die Frage die man sich stellen sollte ist also, wohin man Abhängigkeiten verlagern kann damit sie weniger störend und leichter zu händeln sind?

                            Wenn wir die drei Komponenten ConsoleWriter, TextConverter und HalloWeltGenerator betrachten, dann sehen wir dass sie erstmal nichts miteinander zu tun haben. Jede der Komponenten ist auf ihre eigene Aufgabe spezialisiert (write, convert und generate) und von den anderen vollständig unabhängig, sozusagen atomar. Es ist quasi der Idealzustand.

                            Eine Beziehung zwischen den Komponenten und die daraus resultierenden Abhängigkeiten ergeben sich erst durch die spätere Aufgabenstellung/Problemdomäne „Generiere Text -> konvertiere Text in Großbuchstaben -> schreibe Text auf die Konsole“.

                            Der Kontext der Problemdomäne ist hier die eigentliche Ursache für das Entstehen der Abhängigkeiten, nicht die Komponenten selbst!

                            In Deinem ersten Beispiel:

                            http://www.php.de/forum/webentwicklu...84#post1450584

                            hast Du HalloWeltGenerator vom TextConverter und TextConverter vom ConsoleWriter abhängig gemacht. Damit hast Du die Komponenten im Voraus für den Einsatz in einem ganz bestimmten Kontext „optimiert“ um die Anforderungen an eine bestimmte Aufgabe besser erfüllen zu können. Es liegt auf der Hand, daß sobald sich der Kontext/die Anforderungen ändern, der scheinbare Vorteil schnell zum Problem werden kann.

                            Ich würde gerne die beiden Lösungen nochmal miteinander vergleichen.

                            Eine EBC-Komponente muss im Prinzip die folgenden Bediengungen erfüllen:

                            1-* Inputkanäle
                            0-* Outputkanäle

                            Dabei können sich bei den OuputKanälen 0-* OutputHandler registrieren.

                            In dem Beispiel verwende ich nur eingeschränkte EBCs mit max. einem OutputKanal. Damit jedes EBC mehrere Outputhandler verarbeiten kann, habe ich die EBC-Basiskomponente entsprechend angepasst.

                            Hier noch einmal das vollständige HalloWelt-Beispiel:


                            PHP-Code:
                            // EBC-Basistyp
                            abstract class EBC {
                                public 
                            $outputPin = [];

                                protected function 
                            sendToOutputPin() {
                                    
                            $args func_get_args();

                                    foreach(
                            $this->outputPin as $outputHandler) {
                                        if(
                            false === is_callable($outputHandler)) {
                                            throw new 
                            RuntimeException("OutputHandler must be a callable type!");
                                        }

                                        
                            call_user_func_array($outputHandler$args);
                                    }
                                }
                            }

                            class 
                            HalloWeltGenerator extends EBC {
                                public function 
                            generate() {
                                    
                            $text "Hallo Welt!";
                                    
                            $this->sendToOutputPin($text);
                                }
                            }

                            class 
                            ConsoleWriter extends EBC {
                                public function 
                            write($text) {
                                    echo 
                            "(write)" $text.PHP_EOL;
                                }
                            }

                            class 
                            TextConverter extends EBC {
                                public function 
                            toUpper($text) {
                                    
                            $text strtoupper($text);
                                    
                            $this->sendToOutputPin($text);
                                }

                                public function 
                            toLower($text) {
                                    
                            $text strtolower($text);
                                    
                            $this->sendToOutputPin($text);
                                }
                            }

                            // Board
                            class HalloWeltBoard {
                                protected 
                            $halloWeltGenerator null;
                                protected 
                            $consoleWriter null;
                                protected 
                            $textConverter null;

                                public function 
                            __construct() {
                                    
                            $this->halloWeltGenerator = new HalloWeltGenerator();
                                    
                            $this->consoleWriter = new ConsoleWriter();
                                    
                            $this->textConverter = new TextConverter();

                                    
                            // Verkabelung / Binding
                                    
                            $this->halloWeltGenerator->outputPin[] = array($this->textConverter"toUpper");
                                    
                            $this->textConverter->outputPin[] = array($this->consoleWriter"write");
                                }

                                public function 
                            run() {
                                    
                            $this->halloWeltGenerator->generate();
                                }
                            }

                            $board = new HalloWeltBoard();
                            $board->run(); 
                            Mal angenommen die ursprünglichen Anforderungen ändern sich jetzt ein wenig. Wir sollen das Board zusätzlich mit Sprachausgabe und einem Logger erweitern:

                            Die Aufgabe sieht jetzt wie folgt aus:

                            Generiere Text -> [NEU] spreche Text -> konvertiere Text -> [NEU] logge Text -> schreibe Text

                            Wir besorgen uns also zuerst die noch fehlenden Komponenten:

                            PHP-Code:
                            class Speaker extends EBC {
                                public function 
                            say($text) {
                                    echo 
                            "(say): $text";
                                }
                            }

                            class 
                            Logger extends EBC {
                                public function 
                            log($text) {
                                    echo 
                            "(log): $text".PHP_EOL;
                                }

                            und bauen uns ein neues Board:

                            PHP-Code:
                            class HalloWeltSpeakerBoard {
                                protected 
                            $halloWeltGenerator null;
                                protected 
                            $consoleWriter null;
                                protected 
                            $textConverter null;
                                protected 
                            $speakernull;
                                protected 
                            $logger null;

                                public function 
                            __construct() {
                                    
                            $this->halloWeltGenerator = new HalloWeltGenerator();
                                    
                            $this->consoleWriter = new ConsoleWriter();
                                    
                            $this->textConverter = new TextConverter();
                                    
                            $this->speaker = new Speaker();
                                    
                            $this->logger = new Logger();

                                    
                            // Verkabelung / Binding
                                    
                            $this->halloWeltGenerator->outputPin[] = array($this->speaker"say");
                                    
                            $this->halloWeltGenerator->outputPin[] = array($this->textConverter"toUpper");
                                    
                            $this->textConverter->outputPin[] = array($this->logger"log");
                                    
                            $this->textConverter->outputPin[] = array($this->consoleWriter"write");
                                }

                                public function 
                            run() {
                                    
                            $this->halloWeltGenerator->generate();
                                }
                            }

                            $speakerBoard = new HalloWeltSpeakerBoard();
                            $speakerBoard->run(); 
                            Das wars, gerade mal zwei zusätzliche Zeilen Code, die für die Verkabelung benötigt werden

                            Jetzt versuche das Gleiche bitte auf Grundlage Deines ersten Beispiels zu implementieren.

                            VG
                            Jack

                            -

                            Kommentar


                            • #29
                              Zitat von jack88 Beitrag anzeigen
                              In Deinem ersten Beispiel ... hast Du HalloWeltGenerator vom TextConverter und TextConverter vom ConsoleWriter abhängig gemacht.
                              OT:
                              Kannst du das ausführen? Der Ansicht bin ich nämlich nicht. Ich kann Wrapper beliebig miteinander kombinieren. Und die einzelnen Komponenten sind nicht unmittelbar voneinander abhängig. Da sie das gleiche Interface implementieren sind sie quasi leicht kombinierbar.


                              Zitat von jack88 Beitrag anzeigen
                              Mal angenommen die ursprünglichen Anforderungen ändern sich jetzt ein wenig. Wir sollen das Board zusätzlich mit Sprachausgabe und einem Logger erweitern.
                              Zitat von jack88 Beitrag anzeigen
                              Jetzt versuche das Gleiche bitte auf Grundlage Deines ersten Beispiels zu implementieren.
                              Ich versuche erst einmal deine Variante so zu implementeren, dass ich da keine Methode als String referenziere. Daran störe ich mich sehr.

                              PHP-Code:
                              <?php
                              final class HalloWeltGenerator {
                                  public function 
                              generate() {
                                      return 
                              "Hallo Welt!";
                                  }
                              }

                              final class 
                              StdOutWriter {
                                  public function 
                              write($text) {
                                      
                              fwrite(STDOUTsprintf("%s [INFO]: %s\n"date('Y-m-d H:i:s'), $text));
                                  }
                              }

                              final class 
                              StdErrWriter {
                                  public function 
                              write($text) {
                                      
                              fwrite(STDERRsprintf("%s [WARN]: %s\n"date('Y-m-d H:i:s'), $text));
                                  }
                              }

                              final class 
                              TextCaseConverter {
                                  public function 
                              toUpper($text) {
                                      return 
                              strtoupper($text);
                                  }

                                  public function 
                              toLower($text) {
                                      return 
                              strtoupper($text);
                                  }
                              }

                              final class 
                              TextReplacer {
                                  private 
                              $oldSubstring;
                                  private 
                              $newSubstring;

                                  public function 
                              __construct($oldSubstring$newSubstring) {
                                      
                              $this->oldSubstring $oldSubstring;
                                      
                              $this->newSubstring $newSubstring;
                                  }

                                  public function 
                              process($text) {
                                      return 
                              strtr($text, [$this->oldSubstring => $this->newSubstring]);
                                  }
                              }

                              final class 
                              Arbiter {
                                  
                              /** @var callable[] */
                                  
                              private $listeners = [];

                                  public function 
                              addListener(callable $listener) {
                                      
                              $this->listeners[] = $listener;
                                      return 
                              $this;
                                  }

                                  public function 
                              clearListeners() {
                                      
                              $this->listeners = [];
                                      return 
                              $this;
                                  }

                                  public function 
                              pass($data null) {
                                      foreach(
                              $this->listeners as $listener) {
                                          
                              call_user_func($listener$data);
                                      }
                                      return 
                              $this;
                                  }
                              }

                              // Bootstrap
                              $stdoutWriter = new StdOutWriter();
                              $stderrWriter = new StdErrWriter();
                              $converterImpl = new TextCaseConverter();
                              $generatorImpl = new HalloWeltGenerator();
                              $replacerImpl = new TextReplacer('HALLO WELT''HELLO WORLD');
                              $caseConverterImpl = new TextCaseConverter();

                              // Figur 1: Generiere Text; Konvertiere den Text in Großbuchstaben; Gib den Text auf STDOUT aus
                              printf("%s\n"'Figur 1');

                              $writer = new Arbiter();
                              $writer->addListener(function ($data) use ($stdoutWriter) {
                                  
                              $stdoutWriter->write($data);
                              });

                              $converter = new Arbiter();
                              $converter->addListener(function ($data) use ($writer$converterImpl) {
                                  
                              $convertedData $converterImpl->toUpper($data);
                                  
                              $writer->pass($convertedData);
                              });

                              $generator = new Arbiter(); // Eigentlich nicht nötig, aber zur Vollständigkeit...
                              $generator->addListener(function () use ($converter$generatorImpl) {
                                  
                              $data $generatorImpl->generate();
                                  
                              $converter->pass($data);
                              });

                              $generator->pass();

                              // Figur 2: Füge einen Zwischenschritt ein: "Hallo Welt!" soll zu "Hello World!" werden
                              printf("%s\n"'Figur 2');

                              $converter1 = new Arbiter();
                              $converter1->addListener(function ($data) use ($writer$replacerImpl) {
                                  
                              $convertedData $replacerImpl->process($data);
                                  
                              $writer->pass($convertedData);
                              });

                              $converter2 = new Arbiter();
                              $converter2->addListener(function ($data) use ($converter1$caseConverterImpl) {
                                  
                              $convertedData $caseConverterImpl->toUpper($data);
                                  
                              $converter1->pass($convertedData);
                              });

                              $generator->clearListeners();
                              $generator->addListener(function () use ($converter2$generatorImpl) {
                                  
                              $data $generatorImpl->generate();
                                  
                              $converter2->pass($data);
                              });

                              $generator->pass();

                              // Figur 3: Gibt den Text zusätzlich zu STDOUT auch auf STDERR aus
                              printf("%s\n"'Figur 3');

                              $writer->addListener(function ($data) use ($stderrWriter) {
                                  
                              $stderrWriter->write($data);
                              });

                              $generator->pass();
                              Ich störe mich auch an dem extends EBC. Das bindet deine Variante eben auch an eine bestimmte Komponente, die unabhängige Komponenten von Drittanbietern so nicht mitbringen würden. Letztlich kann man bei dir aber das gleiche machen, wie bei mir.

                              Das ist aber alles ziemlich viel Aufwand für nichts. Zumal das auch nur in diesem Beispielszenario gut funktioniert. Besser wäre es, generische Interfaces zu schaffen, welche die ohnehin benötigte Kopplung dann projekt-unspezifische Interfaces delegieren. Psr/LoggerInterface ist da ja ein gutes Beispiel. EBC ist ganz nett, wenn man völlig unabhängige Komponenten entwickeln und verdrahten will.

                              Deiner Darstellung nach steht EBC maßgeblich für zwei Dinge:
                              - Lose Kopplung
                              - Unopinionated interfaces
                              - Verbindung zwischen Objekten änderbar (Ich kann die Verbindung zwischen Objekten nach der Konfigurationsphase neu festlegen)

                              Lass mich kurz auf die einzelnen Punkte eingehen:

                              Lose Kopplung
                              Eine lose Kopplung hat ihren Ursprung meistens ganz woanders - nämlich im Design einer zu koppelnden Komponente. Wenn diese Komponente ihr Tun ideal als Interface formuliert und dabei nicht den Kompromiss eingeht auf ein bestimmtes Zielsystem hin designt zu werden*, dann ist es zunächst nicht von belang, ob man diese Komponente mit einem Event- oder DI-System verwendet. Die lose Kopplung passiert von selbst. Ich sehe EBC hier nicht als geeigneter an, als DI. Mein Codebeispiel oben zeigt eine Kopplungsvariante, mit der ich in einem DI-Container eine ähnlich schlanke Verbindung zwischen zwei Komponenten erreichen kann. Das wäre dann der klassische Adapter der eine inkompatible Komponente für ein projektspezifisches Interface nutzbar macht.


                              Unopinionated interfaces
                              Das ist sicherlich der Hauptpunkt bei EBC. Meine Interfaces (eben so wie die inneren Abhängigkeiten der Komponenten) sind frei von Domänenbezug und damit genau so gut (oder schlecht) auch in anderen Umgebungen einsetzbar. Bei DI/IoC schleppe ich als Komponente ständig noch die Informationen mit mir rum, wie genau mit mir kommuniziert werden kann. Ich benötige weitere Hilfskonstrukte (im besten Fall Interfaces), die ich php-idiomatic eben als eigenständige Dateien mitliefere. EBC springt hier als generischer Mittler ein.


                              Verbindung zwischen Objekten änderbar
                              Der Punkt macht für mich eher wenig Sinn. Ich kann die Konfiguration der Objektabhängigkeiten in einem DI-Container komfortabel (= reduziert; übersichtlich; verständlich) festlegen. Ich habe aber in nur ganz seltenen Fällen wirklich Bedarf, dies auch nach der Konfigurationsphase zu tun. Meiner Erfahrung nach würde ich behaupten, dass es zu jeder Situation wo man etwas mit nachträglicher Konfiguration lösen könnte, eine vergleichbare (und wahrscheinlich schönere) Variante rein auf OO-basis gäbe.


                              Gleichzeitig kann ich aber jederzeit beide Paragidmen miteinander kombinieren. Ich muss eine Applikation nicht ausschließlich auf dem einen oder anderen Paradigma basieren lassen.


                              Aber ja, ich habe jetzt eine ziemlich gute Vorstellung, welches OO-"Problem" EBC zu "lösen" versucht.


                              * Man ist nie wirklich frei in der Entscheidung eines Komponentendesigns. Gerade für PHP muss man viele Kompromisse eingehen und lässt auch häufig viele Aspekte weg, im Wissen, dass ein Script nach Ausführung eines Jobs beendet wird. Anderes Beispiel: In einem System, welches Nebenläufigkeit unterstützen soll, muss man Komponenten dafür speziell denken. Es ist nicht mehr jedes denkbare Design anwendbar.

                              Kommentar


                              • #30

                                Kannst du das ausführen? Der Ansicht bin ich nämlich nicht. Ich kann Wrapper beliebig miteinander kombinieren. Und die einzelnen Komponenten sind nicht unmittelbar voneinander abhängig. Da sie das gleiche Interface implementieren sind sie quasi leicht kombinierbar.
                                Da gibt es eigentlich nicht viel zu sagen. Wenn eine Klasse eine andere Klasse referenziert, dann ist sie von dieser abhängig. Dass Du gegen ein Interface prüfst und nicht gegen einen konkreten Klassentyp und daß die Abhängigkeiten von außen injiziert werden, ändert nur den Grad der Abhängigkeit. Die Abhängigkeit selbst bleibt jedoch immer bestehen.

                                Die Abhängigkeiten sind an den jeweiligen Constructor-Methoden, als auch an den entsprechenden Klassenattributen gut erkennbar.

                                PHP-Code:
                                class TextConverter implements TextComponent {
                                    
                                /** @var TextComponent */
                                    
                                private $subject;
                                    public function 
                                __construct(TextComponent $subject) {
                                ..
                                }

                                class 
                                HalloWeltGenerator {
                                    
                                /** @var TextComponent */
                                    
                                private $subject;

                                    public function 
                                __construct(TextComponent $subject) {
                                        
                                $this->subject $subject;
                                    }
                                .

                                Ehrlich gesagt sieht auch das TextComponent-Interface mit der wrtite() Methode irgendwie „konstruiert“ aus. Ein ConsoleWriter ist doch etwas völlig anderes als ein TextConverter. Trotz unterschiedlicher Verantwortung implementieren beide Klassen den gleichen Kontrakt? Das macht keinen Sinn. Hättest Du die Komponenten von Anfang an mit einem Interface implementiert das die tatsächliche Verantwortung der Komponenten repräsentiert (write, convert, generate), dann wäre ein „TextComponent-Interface“ in der Form gar nicht erst möglich gewesen – vermute ich.

                                ...dass ich da keine Methode als String referenziere. Daran störe ich mich sehr.
                                Na ja , das ist nur ein Implementierungsdetail. Es geht ja vordergründig um das Konzept und die Mechanik die dahinter steckt, nicht um die konkrete Implementierung. Außerdem bietet PHP leider keine Möglichkeit Methoden direkt zu referenzieren. Aber man könnte es sicherlich auch anderes/besser machen.

                                Ich störe mich auch an dem extends EBC. Das bindet deine Variante eben auch an eine bestimmte Komponente, die unabhängige Komponenten von Drittanbietern so nicht mitbringen würden.
                                Jede Komponente bringt doch sein eigenes Interface mit? Das ist bei einer EBC nicht anders. Eine Basis-EBC ist auch nicht zwingend erforderlich, aber sicherlich sinnvoll um alle Komponenten dieses Typs mit der für EBC erforderlichen Basismechanik auszustatten. Du kannst selbstverständlich auch eine andere EBC-Infrastruktur in jeder EBC einzeln implementieren, aber das wäre nicht gerade im Sinne von DRY.

                                Letztlich kann man bei dir aber das gleiche machen, wie bei mir.
                                Ich bin mir nicht ganz sicher was Du mit dem letzten Beispiel eigentlich veranschaulichen willst? Ja, Du kommst auch mit anderen Mitteln zum gleichen Ergebnis, aber darum geht es doch gar nicht? Der Weg ist sozusagen das Ziel... EBC steht für „Event Based Components“, das bedeutet dass es sich hier um ein Konzept mit ereignis-basierten Komponenten handelt. Die Komponente selbst ist ereignis-basiert und nicht die Infrastruktur die diese umgibt!

                                In Deinem Beispiel ist es genau umgekehrt, die Komponenten sind nicht ereignis-basiert dafür aber die Infrastruktur in Form eines EventDispatchers, indem Du Closures als Evnthandler registrierst in die Du zuvor alle Komponenten hinein gequetscht hast um den Signalfluss abzubilden.

                                Und der Code - ganz ehrlich - ist echt nicht praxistauglich…

                                Gleichzeitig kann ich aber jederzeit beide Paragidmen miteinander kombinieren. Ich muss eine Applikation nicht ausschließlich auf dem einen oder anderen Paradigma basieren lassen.
                                Das sehe ich nicht anders. Ich betrachte EBC keinesfalls als die Nonplusultra Lösung die über allen anderen Konzepten steht. Viel mehr ist es einfach nur ein spannendes Konzept unter vielen. Die Idee gefällt mir jedoch gut. Allerdings bleiben noch viele Fragen offen.

                                Deiner Darstellung nach steht EBC maßgeblich für zwei Dinge:
                                Du hast aber drei genannt...:

                                Lose Kopplung
                                Eine lose Kopplung hat ihren Ursprung meistens ganz woanders - nämlich im Design einer zu koppelnden Komponente....Ich sehe EBC hier nicht als geeigneter an, als DI......
                                Das verstehe ich nicht, was kann z.B. der ConsoleWriter dafür wenn er von einem TextConverter verwendet wird? Richtig ist, daß das EBC-Konzept helfen soll Komponenten soweit wie möglich zu entkoppeln. Parallelen zum DI sehe ich ehrlich gesagt jetzt nicht.

                                Unopinionated interfaces
                                Das ist sicherlich der Hauptpunkt bei EBC.
                                „Unopinionated interfaces“ sind mir leider kein Begriff. Auf die Schnelle konnte ich dazu auch nichts brauchbares finden? Auch in diesem Zusammenhang ist die Rolle des DI für mich unklar?

                                Verbindung zwischen Objekten änderbar
                                Ich glaube es geht nicht um änderbar oder nicht änderbar. Es geht um mehr Flexibilität, um ein Komponenten-Konzept welches möglichst offen für Modifizierungen und Erweiterungen bleiben soll. Komponenten, die sich möglichst so flexibel und intuitiv wie Legosteine anfühlen und ähnlich einfach zusammenstecken lassen.

                                VG
                                Jack




                                -

                                Kommentar

                                Lädt...
                                X