T-Rex: Zentrale Stelle oder Geteilte Anliegen?

Moin,

Hätte da mal eine Meinungsfrage bei der ich selbst geteilter Meinung bin und somit nicht weiter komme. Es gibt manchmal in einem System die Frage ob man eine "neue" sache mit den bisherigen Funktionen, Klassen, Objekten löst (mit dem Wissen diese eventuell zu erweitern) oder eine neue Funktion zu schaffen (mit dem Wissen einige Teile redundant im System zu haben).

Als Beispiel bei mir ist die User Ein- und Ausgabe.
Bei mir existiert bereits eine Ausgabe der Userdaten. Dabei gebe ich ein Array der Userdaten an eine Funktion. Diese bereitet die Daten so vor, dass sie ins Template übergeben werden.
Jetzt möchte ich dass der User diese Daten auch eingeben kann, also ein Formular. Um die eingegeben Daten aus zu geben könnte ich ja oben genannte Funktion benutzen. Also wieder Userdaten an eine Funktion übergeben. Da ein anderes Template angesteurt wird, können die Tempalte Daten anders verarbeitet werden. Also Variante 1 (einfache Ausgabe):
Daten -> Funktion
Template:
<div>{Username}</div>

Variante 2 (in einem Formular):
Daten -> Funktion
Template:
<input value='{Username}' />

Soweit würde ich sagen, ich brauche keine redundanten Code. Doch jetzt kann es vorkommen dass man z.B. eine Selectbox hat. Die Werte müssen auch irgendwie ins Template kommen. Variante 1 braucht diese funktionalität nicht, denn da soll einfach nur 1 Wert angezeigt werden. Das Formular hingegen braucht diese Funktionalität. Und hier fängt die Zwickmühle an. Ich sehe 3 Lösungswege.

1. Entweder gibt es eine Funktionalität die alles macht (somit auch unnütze Sachen für Variante 1)
Daten -> Funktion

2. Oder es gibt zwei Redundante Funktionen, eine für Variante 1 und eine für Variante 2
Daten -> Funktion 1 oder
Daten -> Funktion 2

3. Oder es gibt eine Grund Funktion, welche die Daten zur simplen Ausgabe vorbereitet und eine die noch die Formular speziellen Sachen dazu läd:
Daten -> Grund Funktion 1 ( und eventuell )
Daten -> Extra Funktionen
Problem hierbei sehe ich beim Aufruf. Wenn es viele Extra Funktionalitäten sind könnte das ganze eventuell irgendwann so aussehen:
Daten -> Grund Funktion 1 ( und eventuell )
Daten -> Extra Funktionen 1 ( und eventuell )
Daten -> Extra Funktionen 2 ( und eventuell )
Daten -> Extra Funktionen 3 ( und eventuell )
Daten -> Extra Funktionen 4 ( und eventuell )
Daten -> Extra Funktionen 5 ( und eventuell )
Daten -> Extra Funktionen 6 ( und eventuell )

Ich tendiere im Moment zu den Redundanten Sachen, also Lösung 1. Es sind logisch gesehen zwei getrennte Sache Ein und Ausgabe. Es gäbe auch nur eine begrenzte Anzahl von Redundanzen nämlich 2. Wenn man die Userdaten in einem anderen Zusammenhang braucht z.B. Beim Login (also z.B. Hallo Username) dann könnte man die Funktion zur Ausgabe wiederverwenden. Damit möchte ich nochmals betonen dass es nur 2 Redundanzen geben würde. Desweiteren sehe ich die Fehleranfälligkeit geringer. Wenn man etwas erweitern möchte, muss man das leider an zwei Stellen machen, dass wäre der Nachteil.
Achja mir geht es nicht um die speichern funktionalität. Die braucht es unabhängig von der Ausgabe sowieso.

Bin gespannt auf eure Meinung und vor allem auf eure Argumente.

Danke für eure Zeit!

Gruß
1 Woche Urlaub (YEAH)
T-Rex

  1. Bisschen schwer zu verstehen, was du da meinst. Du vergleichst da Eingabe und Ausgabe?!

    Es geht dir also darum ob du beispielsweise eine Funktion hast die alle Felder aus einer Datenbank liest. Jetzt überlegst du diese mitzubenutzen wenn du eigentlich nur ein Feld brauchst, oder ob du stattdessen eine neue schreibst die nur das eine Feld liest und damit in gewissem Sinn redundant ist?

    Sowas würde ich fallbezogen entscheiden, da gibts keine feste Regel. Wenn ein kompliziertes SELECT dahinter steckt mit vielen JOIN auf andere Tabellen, würd ich vielleicht was einfacheres machen wenn sich das dann lohnt.
    Wenn eine Funktion nur ein paar mehr Spalten ausliest als ich grad brauche und es nicht viele Datensätze werden, würd ich die bestehende verwenden.
    Letztendlich entscheidet sich das aus dem Gesamteindruck von Performance und Übersicht des Codes. Ein bisschen Zeit die man nirgends merkt ist für mich kein Grund um Code aufzublasen. Wenns dann performancekritisch wird, mach ich doch lieber eigene Funktionen.

  2. hi,

    Bin gespannt auf eure Meinung und vor allem auf eure Argumente.

    Es hängt vom Template-System ab. In meinem CMS ist Template eine Subklasse von CMS (CMS::Template) und das Objekt erwartet 2 Argumente:

    1 eine Referenz auf das Template
    2 eine Hashreferenz mit den Werten

    2 ist im Default $self->{STASH} und darin gibt es auch noch den Default $self-{STASH}{url} der stets auf die aktuelle Seite zeigt nach der Erstellung des Response-Objekts.

    Zum Betanken des STASH habe ich keine spezielle Methode. Bei Formulareingaben ist die an meine Class 'CMS' eine von Class 'CGI' delegierte Methode param() zuständig, Beispiel

      
    # in Class CMS, in Methods  
    $self->{STASH}{name} = $self->param('name');  
    $self->{STASH}{vname} = $self->param('vname');  
    
    

    Wenn ich Deine Frage richtig verstanden habe, überlegst Du, einen Wrapper zu bauen als Method zur Verarbeitung von Eingaben bestimmter Formular-Klassen. Das habe ich bisher nicht gebraucht, dafür bin ich mit param() sehr beweglich ;)

    O'Hotti

    1. Hi!

      In meinem CMS ist Template eine Subklasse von CMS (CMS::Template) [...]

      Warum das? Welche Eigenschaften hat denn ein CMS, die ein Template, also üblicherweise nur ein kleiner Teil vom CMS, übernehmen muss?

      Lo!

      1. hi dedlfix,

        In meinem CMS ist Template eine Subklasse von CMS (CMS::Template) [...]

        Warum das? Welche Eigenschaften hat denn ein CMS, die ein Template, also üblicherweise nur ein kleiner Teil vom CMS, übernehmen muss?

        Entdecke die Vererbung ;)

        Mein CMS ist die Klasse, über die das Response-Objekt erstellt wird. Bisher wird die Instanz über my $ro = CMS->new; erstellt und hat alle Eigenschaften/Methoden, die zur Ausgabe einer Response benötigt werden.

        Templates habe ich jedoch erst später eingeführt und bisher ist CMS::Template zwar eine Subklasse, die jedoch nicht als solche genutzt wird.

        Die Vererbung, die ich demnächst einbaue, ermöglicht es, das Response-Objekt als my $ro = CMS::Template->new; zu erstellen, ohne dass am bisherigen Code Änderungen notwendig sind.

        In Class CMS::Template siehts dann so aus:

        Die Referenz auf das Template ist entweder als Argument übergeben oder im Default der Body des Response-Objekts \$self->{body}. Das zweite Argument ist auch entweder übergeben, oder im Default ist das $self->{STASH}, das sog. Datenversteck.

        Am Code kann ich, sofern das Response-Objekt über ein Template ausgegeben wird, dann einiges vereinfachen.

        O'Hotti

        --
        Tippfehler in Kommentaren werden oft erst viel später bemerkt.
        1. Hi!

          In meinem CMS ist Template eine Subklasse von CMS (CMS::Template) [...]
          Warum das? Welche Eigenschaften hat denn ein CMS, die ein Template, also üblicherweise nur ein kleiner Teil vom CMS, übernehmen muss?
          Entdecke die Vererbung ;)

          Entdecke sinnvolle Vererbung! Eine Template-Klasse macht aus Eingabedaten und einem Template ein fertiges Dokument. Mehr braucht sie dazu nicht und muss dann gar nicht großartig noch Dinge von anderswo erben, die für ihre eigentliche Aufgabe nicht relevant sind.

          Lo!

          1. hi,

            Entdecke sinnvolle Vererbung! Eine Template-Klasse macht aus Eingabedaten und einem Template ein fertiges Dokument.

            Ja und Nein. Ein expandiertes Dokument ist da noch lange nicht fertig. Ein Dokument ist erst fertig, wenn noch einige andere Eigenschaften gegeben sind, z.B. Title-Tag, Meta-Description-Tag, Links zu Javascript-Ressourcen, Links zu CSS-Ressourcen, Author-Tag, Content-Type, Charset usw. und nicht vergessen, eine Session-ID sowie die Berechtigungs-Gruppe ;)

            Ohne Gewähr (es gibt noch mehr Eigenschaften)
            Hotti

            1. Hi!

              Entdecke sinnvolle Vererbung! Eine Template-Klasse macht aus Eingabedaten und einem Template ein fertiges Dokument.

              Ja und Nein. Ein expandiertes Dokument ist da noch lange nicht fertig.

              Dann erstellt die Klasse eben nur einen Teil eines Dokuments. Auch dann ist ihr Einsatzzweck übersichtlich klein und muss immer noch nichts zusätzliches erben.

              Lo!

              1. hi ;)

                Ja und Nein. Ein expandiertes Dokument ist da noch lange nicht fertig.

                Dann erstellt die Klasse eben nur einen Teil eines Dokuments. Auch dann ist ihr Einsatzzweck übersichtlich klein und muss immer noch nichts zusätzliches erben.

                Ja. So habe ich es bisher: Zwei Objekte, eins für die komplette Response und eins fürs Template, was ein Teil der Response ist.

                Die (geplante) Vererbung ist sinnvoll, weil der Body ein Attribut des Response-Objekts ist und auch der Stash ein Attribut des Response-Objekts ist. Bisher werden zu diesen Attributen zwei Referenzen in die Template-Klasse übergeben. Demnächst werden die Attribute nicht übergeben sondern geerbt. Der Code wird deswegen nicht unübersichtlich, der Code bleibt unverändert (schrieb ich bereits). Ich _kann_ den Code ändern, muss aber nicht. Ich kann jedoch _neuen_ Code einfacher schreiben:

                Die Erstellung des Template-Objekts mit Übergabe der Referenzen entfällt und die Method "expand" wird nunmehr mit dem Response-Objekt aufgerufen. Den Rest erledigt der Response-Handler, dessen Code habe ich vor einem dreiviertel Jahr geschrieben und bisher nicht wieder angeguckt ;)

                Eine andere möglich Variante wäre die Delegierung anstelle einer Vererbung. Hierbei würde das Response-Objekt bereits im Konstruktor als zusätzliches Attribut ein Template-Objekt bekommen, womit die Methode Template::expand delegiert werden kann. Auch in diesem Fall würde sich am bisherigen Code nichts ändern, neuer Code jedoch wird einfacher.

                O'Hotti

              2. hi,

                Dann erstellt die Klasse eben nur einen Teil eines Dokuments. Auch dann ist ihr Einsatzzweck übersichtlich klein und muss immer noch nichts zusätzliches erben.

                Es gibt noch eine andere Variante, die auf Vererbung verzichtet: Das Response-Objekt ($ro, eine Instanz der CMS-Klasse) bekommt zur Laufzeit, nämlich dann, wenn ein Template gebraucht wird, ein weiteres Attribut, das Template-Objekt. Noch vor dem Aufruf einer entsprechenden Methode liegt in $ro->{body} das Template selbst und in $ro->{STASH} liegen die Werte zum expandieren.

                  
                $ro->addTemplate;    # Template-Objekt als Attribut hinzufügen  
                $ro->expand;         # Delegation  
                print $ro->getbody;  # Ergebnis ausgeben  
                
                

                Die Methode zum Expandieren ist nur delegiert und kann somit vom $ro aufgerufen werdn. Übersichtlicher geht nicht ;)

                Hotti

            2. Also ohne zu bewerten ob dein System gut oder schlecht ist.
              Mein System hat dafür eine Extra Klasse. Die kümmert sich darum solche Angaben zu bekommen/holen sie zu verarbeiten und dann mittels get() Methode ins Template zu setzen. Somit könnte man diese Extra Klasse irgendwann durch eine andere Ersetzen (Ersetzbarkeit).
              Somit könnte man für PDFs eine Extra PDF Klasse benutzen - Faktorierung.

              Gruß
              nicht mehr ganz eine Woche Urlaub
              T-Rex

              1. hi,

                Somit könnte man für PDFs eine Extra PDF Klasse benutzen - Faktorierung.

                Jow, auch ein guter Ansatz. Es gibt halt verschiedene Wege und Entwurfsmuster. Vererbung ist auch nicht immer und überall angebracht, ich habe die Erfahrung gemacht, dass ich eine Klasse, von der ich erben möchte, sehr genau kennen sollte und das ist bei den Klassen, die ich selber schreibe der Fall. Wenn eine fremde Klasse einige Dutzend Methoden hat und von dieser Klasse letztendlich nur eine Methode gebraucht wird, ist eine Delegation sinnvoller als eine Vererbung.

                Mehrere Klasseninstanzen in der main sind auch ok, es ist eine Frage der Übersichtlichkeit und eine Frage der Wiederverwertbarkeit zum Vermeiden von redundanten Code. Beispielsweise habe ich eine Klasse 'Warenkorb' für die Methoden, die für den Warenkorb zuständig sind. Die Klasse 'Bestellung' muss jedoch auch auf den Warenkorb zugreifen und somit erbt die Klasse Bestellung von Klasse Warenkorb. Der Bestellvorgang ist eine Transaktion und eine Solche über zwei verschiedene Klasseninstanzen abzuwickeln macht den Code in der main unübersichtlich, an der Stelle nämlich sitzt der Controler für die Prüfung der Benutzereingaben mit völlig anderen Zuständigkeiten.

                Factory Patterns in Verbindung mit Singleton: Ich will keine Vorträge halten, steht mir nicht zu ;)

                Aber mal in Kürze, wie das Erstellen einer PDF bis hin zur Auslieferung an den Webserver bei mir abläuft: Allein das Response-Objekt (RO) ist dafür zuständig, die erste zu rufende Methode sendet den Content-Type-Header, wobei sich in der factory auch unterschiedliche Content-Types ergeben können, die als Attribut im RO liegen. Wenn beispielsweise Fehler aufgetreten sind, wird da kein application/pdf gesendet, sondern text/html.

                Was in der RO-Method bleibt, ist die Prüfung auf mögliche Fehlerfälle z.B. keine Daten (die Berechtigungen werden bereits weiter oben im Response-Handler geprüft), erst dann wird bei mir eine Instanz der PDF-Klasse erstellt. Das PDF wird temporär zusammengebaut und auch hier muss geprüft werden, ob dabei nichts schiefgegangen ist (Exception). Wenn alles ok, übernimmt RO den für diesen URL vorgesehenen Content-Type (application/pdf) in die Header-Method.

                Die ganze Prozedur ist in einer Methode des RO in der Response-Klasse (CMS) untergebracht. Der Name der dem URL zugeordneten Methode steht in der Konfiguration, die bei jedem Request geladen wird. Um die Verwirrung komplett zu machen: Package main{} gibt es nur im Response-Handler (im Webserver konfiguriert), der erstellt das RO und das ruft anhand des Request die entsprechenden Methoden auf. Liegen Benutzereingaben vor, steht der Name der Methode ebenfalls anhand des Request fest wobei z.B. ein Login-Formular an unterschiedlichen URLs konfiguriert sein kann, jedoch stets die gleichen namentlichen Methoden über RO aufgerufen werden.

                Soll zum URL ein Template geladen werden, steht das ebenfalls in der Website-Config. In diesem Fall gibt es bei mir wegen der Abwärtskompatibilität (Templates habe ich erst später eingeführt) drei Methoden: zwei Methoden wie bisher zur Ausgabe der Response, je nachdem ob Parameter anliegen oder nicht. Die dritte (bei Templates zusätzliche) Methode ist der Controller, der in der Website-Config namentlich genannt ist.

                Ein Template-Prozess (expand) muss immer stattfinden, sonst sind in der Response die Platzhalter zu sehen ;)

                Schönen Sonntag,
                O'Horst

                (O'Henry muss ich mal wieder lesen, ich freue mich auf lange Winterabende)

  3. Moin,
    Prinzipiell gilt für sauberen Code: Don't repeat yourself! (DRY).
    Wenn zwei Funktionen das gleiche tun, gehören sie zu einer zusammen gefasst.

    Dieses Prinzip sollte man jedoch dann verletzen, wenn man auf Biegen und Brechen zwei Dinge zusammenbringt, die eigentlich nichts miteinander zu tun haben (dieses Risiko sähe ich bei Deiner Variante 1). In diesem Fall sollte man den gemeinsamen Teil in eine Funktion auslagern, so dass die einzel-Funktionen nur noch das beinhalten, was unterschiedlich ist (entspricht Deiner 3. Variante).

    -> Ich würde tendenziell zu Deiner 3. Variante tendieren.
    Falls Du objektorientiert programmierst, kannst Du das ganze auch sehr schlank machen, ohne dass Du Bedenken hinsichtlich zu vieler "Zusatzfunktionen" haben musst:

    Beispiel PHP:

      
    public class UserDataPreparer {  
       var userData;  
       public __construct ($userData) {  
          $this->userData = $userData;  
       }  
       public processData () {  
           // Do stuff with userData  
       }  
    }  
    public class UserDataOutputPreparer extends UserDataPreparer {  
       public processData () {  
          // Do basic userData Stuff  
          parent::processData();  
          // Now do stuff exspecially for output  
       }  
    }  
    
    

    Hängt aber natürlich von Deinem Problem, Deiner Implementierung usw. ab, und lässt sich daher sicher nicht allgemeingültig beantworten.

    Viele Grüße,
    Jörg

    1. Und ich sach noch...

      Don't repeat yourself! (DRY).

      ...sach ich.

      -> Ich würde tendenziell zu Deiner 3. Variante tendieren.

      tendenziell...tendieren...
      *seufz*

      :)