2010-05-19 2 views
15

Ich habe Klassen wie diese:Gibt es etwas falsch daran, sofortige Aktionen in Konstruktoren zu ergreifen?

class SomeObject 
{ 
    public function __construct($param1, $param2) 
    { 
     $this->process($param1, $param2); 
    } 
    ... 
} 

So kann ich sofort „Ruf“ es als eine Art globale Funktion wie

new SomeObject($arg1, $arg2); 

, die die Vorteile von

hat
  • bleib kurz,
  • ist leicht zu verstehen,

kann aber ungeschriebene Regeln der Semantik brechen, indem nicht gewartet wird, bis eine Methode aufgerufen wird.

Sollte ich mich wegen einer schlechten Übung weiterhin schlecht fühlen oder gibt es wirklich nichts, worüber ich mir Sorgen machen müsste?

Klarstellung:

  • ich eine Instanz der Klasse wollen.
  • Ich verwende nur interne Methoden der Klasse.
  • Ich initialisiere das Objekt im Konstruktor, aber die "wichtigen" action-taker-Methoden auch zu nennen.
  • Ich bin egoistisch im Licht dieser Sätze.

Beispiel:

Um Ihnen eine Idee zu geben, wie ich in der Regel diesen Ansatz:

new Email('[email protected]', 'Subject line', 'Body Text'); 

Ich vermeide es zu häufig zu benutzen, natürlich, aber meiner Meinung nach, das ist wirklich praktisch.

+1

Liest mehr wie Sie möchten eine statische Funktion für dieses Objekt. – AlG

+0

@ qor72 Ich möchte auf jeden Fall eine Instanz dieser Klasse, aber ich bin faul, Variablen einzugeben und die gleiche Methode immer wieder aufzurufen. – pestaa

+0

Ich verstehe in keiner Weise, warum das einfacher ist, als 'SomeObject-> process ($ param1, $ param2)' direkt aufzurufen. – fearofawhackplanet

Antwort

8

, wenn der Code im Konstruktor ist Teil der Erstellung und Initialisierung des Objekts, dann Ich würde es dort setzen, aber das ist mir persönlich, einige Leute können nicht zustimmen

jedoch sieht es aus wie das, was Sie tun, ist nicht für den Aufbau des Objekts/Klasse, sondern tun einige andere Prozess. Das ist schlecht und sollte in einer separaten Methode durchgeführt werden.

Behalten Sie den Konstruktor für die Konstruktion.

+0

Hinzugefügt ein Beispiel oben. Die Sache ist, sagen wir, "dispatch()" im Beispiel gehört nicht wirklich zur Kategorie "andere Prozesse", auch nicht zur Objekterzeugung. Was ist deine Meinung? – pestaa

+0

@ pesta - alles, was Sie gesagt haben, ist in Ordnung, außer für das Senden der E-Mail. Das gehört nicht zum Erstellen und Initialisieren und gehört definitiv in eine separate Methode. Was ist, wenn Sie das E-Mail-Objekt irgendwo weitergeben oder etwas ändern möchten, bevor Sie es senden? Sie können nicht, es sei denn, Sie haben mehr Konstruktoren. nimm den Sende raus, und du bist gut. –

5

Dies ist eine schlechte Praxis.

Die Verwendung eines Konstruktors als globale Funktion ist überhaupt nicht das, wofür er gedacht ist, und es wäre leicht zu verwechseln. Es ist überhaupt nicht leicht zu verstehen.

Wenn Sie eine globale Funktion wollen, erklären ein:

function myGlobalFunction(){ return $something; } 

Es ist wirklich nicht so schwer ist ...

und auch wenn er nicht mehr als eine Funktion verwenden, sollten Sie Verwenden Sie den Konstruktor, um genau das zu tun, konstruieren Sie ein Objekt. Wenn Sie mehr als das tun, verwenden Sie es nicht für den richtigen Zweck, und zukünftige Beitragende werden wahrscheinlich schnell verwirrt werden.

Also, Programm in einer Weise macht Sinn. Es ist wirklich nicht so schwer. Ein paar zusätzliche Tasten können Ihnen viel Verwirrung ersparen.

Wenn Sie immer bestimmte Aktionen nach einer neuen Instanz einer Klasse zu machen, versuchen, eine Fabrik nehmen müssen: für die Verwendung

class myFactory{ 
     public static function makeObject(){ 
      $obj = new Object(); 
      $obj->init1(); 
      return $obj; 
     } 
} 

$obj = myFactory::makeObject(); 
+0

Ich hatte nicht vor, es zu verwenden als globale Funktion war es eine bloße Metapher. – pestaa

+0

Ich mache das manchmal, um eine "clear()" -Funktion aufzurufen, um den gesamten Code an einer einzigen Stelle zu setzen, sonst könnte das Verhalten inkonsistent werden. – catchmeifyoutry

+0

-1: Ich stimme @pesta zu. Ihre Antwort entspricht nicht wirklich der Frage. –

4

Ockhams Razor sagt enia non sunt multiplanda praeter necessitatem, wörtlich: "Entitäten müssen nicht über die Notwendigkeit hinaus multipliziert werden". Es ist nichts falsch daran, in einem Konstruktor [*] zu arbeiten, aber Aufrufer müssen kein Objekt erstellen, das sie nicht verwenden, nur um die Nebenwirkungen ihres Konstruktors zu bekommen.

[*] Angenommen, Sie sind mit dem Mechanismus vertraut, den Ihre Programmiersprache verwendet, um den Fehler eines Konstruktors anzuzeigen. Das Zurückgeben eines Erfolgs-/Fehlercodes aus einem Konstruktor ist möglicherweise keine Option. Wenn Sie also im Fehlerfall eine Ausnahme vermeiden möchten, werden Sie möglicherweise auf "Is Usable" -Flags im Objekt reduziert, was nicht besonders gut ist für die Benutzerfreundlichkeit entweder.

+1

+1 für die Verwendung von Latein: D auch für den logischen, nicht technischen Aspekt dieser Debatte –

+0

+1 Absolut. In Ihren Worten geht es also darum, ob das Aufrufen der Essenz einer Klasse im Konstruktor ein Nebeneffekt ist oder nicht. (Fehlermanagement ist kein Problem in meiner Situation, aber Sie haben gültige Punkte gemacht, die andere vielleicht in Erwägung ziehen würden.) – pestaa

+0

Ich würde sagen, dass das "Wesen einer Klasse" die Objekte dieser Klasse sind und ihre Schnittstelle und ihr Verhalten von der Klasse. Wenn die Essenz Ihrer Klasse darin besteht, dass sie den Bildschirm (oder was auch immer) ausblendet, und kein Benutzer sich jemals darum kümmern wird, welche Objekte dazu verwendet werden, dann ist es nicht "wirklich" eine Klasse, es ist eine Routine. Einige Sprachen (Java) zwingen Sie dazu, jede Funktion in eine Klasse zu schreiben, was zur Existenz von * Klassen * führt, die keine * Typen * sind. Ich denke, dass diese Anforderung sinnlos ist, obwohl ich denke, dass es die Sprache/den Bytecode speichert, der eine Syntax/ein Format für freie Funktionen haben muss. –

0

Meiner Meinung nach hängt es davon ab, was der Zweck der Funktion ist.

Zum Beispiel könnte etwas, das ich würde völlig passend sein, am Ende eines Konstruktors aufrufen, etwas wie eine refreshCache() Funktion sein. Es ist eine echte öffentliche Funktion, da andere Objekte es möglicherweise aufrufen möchten, aber gleichzeitig wissen Sie, dass es dieselbe Logik ist, die Sie beim Einrichten Ihres Objekts zunächst anwenden möchten.

In einem allgemeinen Sinn nehme ich an, dass die Art von (öffentlichen) Funktionen, die Sie während eines Konstruktors aufrufen möchten, in der Regel idempotente Funktionen sind, die den lokalen Zustand manipulieren, also Dinge wie Cache-Population/Prefetching.

Wenn Sie ein Objekt haben, die an einem Eingang eine Art der Verarbeitung der Fall ist, würde ich auf jeden Fall

s = new MyObject(a, b) 
return s.getResult() 

für

s = new MyObject() // perhaps this is done elsewhere, even once at startup 
return s.process(a, b) 

vermeiden, wie wohl die letztere ist klarer, was ist tatsächlich passiert (vor allem, wenn Sie process() einen echten Namen geben :-)), und Sie können ein einzelnes, leichteres Objekt für die Berechnungen wiederverwenden, was wiederum die Weitergabe erleichtert.

Natürlich, wenn Sie einen ziemlich komplexen Konstruktor haben (das ist wahrscheinlich eine schlechte Sache im Allgemeinen, aber zweifellos gibt es einige Fälle, in denen es Sinn macht), dann ist es nie eine schlechte Sache, einen Block von verwandten Funktionen zu trennen in eine private Funktion für die Lesbarkeit.

+0

Andrzej, ich speichere das Objekt nicht in einer Variablen, nicht einmal eine Sekunde. Daher kann ich keine zweite Methode (über den Konstruktor hinaus) aufrufen. Zumindest nicht in PHP, da wir keine Methodenverkettung haben. Ihre Antwort ist eher eine Namenskonvention Debatte. Und ich stimme zu! – pestaa

0

Dies ist knapp, aber es ist nicht im entferntesten idiomatisch, so dass es für andere nicht einfach ist zu verstehen.

In Zukunft wird es vielleicht nicht einmal leicht für Sie sein, es zu verstehen.

Es mag Ihnen nicht wichtig sein, aber ein Problem damit ist, dass es Ihre Klassen schwieriger zu testen macht - siehe Flaw: Constructor does Real Work.

+0

Ich stimme nicht mit "Konstruktor ist echte Arbeit" überein _ immer _ ein Fehler. Ein Konstruktor muss manchmal echte Arbeit leisten - um die Umgebung für das neue Objekt einzurichten. Wenn eine solche Initialisierung aus dem ctor herausgenommen wird, wird die Konstruktion des Objekts teilweise oder unvollständig, wodurch der _caller_ daran denken muß, das Objekt richtig zu initialisieren. Zum Beispiel habe ich einen "UIPanel" -Konstruktor, der sich tatsächlich mit einem 'backing = new HotSpot (dimensions) 'bevölkert. (zum Erkennen von Mausklicks und zum Anzeigen einer Grundfarbe). – bobobobo

1

Ich denke, das ist aus zwei Gründen schlechte Praxis.

  • Es ist dem Anrufer nicht klar, dass andere Operationen, die über die minimale Initialisierung hinausgehen, stattfinden.
  • Es führt zu umständlichen Codierung Szenarien, wenn diese Operation Ausnahme auslöst.
  • In Bezug auf den ersten Punkt ... gibt es eine implizite Annahme, dass Konstruktoren nur genügend Arbeit leisten, um ein Objekt in einem definierten und konsistenten Zustand zu konstruieren. Das Platzieren von zusätzlicher Arbeit in ihnen kann zu Verwirrung führen, wenn die Arbeit lange ausgeführt wird oder IO gebunden ist. Denken Sie daran, der Konstruktor kann über seinen Namen keine Bedeutung wie Methoden vermitteln. Eine Alternative besteht darin, eine statische Factory-Methode mit einem aussagekräftigen Namen zu erstellen, die eine neue Instanz zurückgibt. Wenn der Konstruktor eine Operation enthält, die Exceptions unvorhersehbar auslöst (im Gegensatz zu Exceptions, die beispielsweise aufgrund von Parameterüberprüfungen ausgelöst wurden), wird der Code für die Behandlung von Exceptions unangenehm.

    Betrachten Sie das folgende Beispiel in C#. Beachten Sie, wie sich das gute Design eleganter anfühlt. Vergiss nicht, dass die klar benannte Methode mindestens eine Größenordnung lesbarer ist.

    public class BadDesign : IDisposable 
    { 
        public BadDesign() 
        { 
        PerformIOBoundOperation(); 
        } 
    
        private void PerformIOBoundOperation() { } 
    } 
    
    public class GoodDesign : IDisposable 
    { 
        public GoodDesign() 
        { 
    
        } 
    
        public void PerformIOBoundOperation() { } 
    } 
    
    public static void Main() 
    { 
        BadDesign bad = null; 
        try 
        { 
        bad = new BadDesign(); 
        } 
        catch 
        { 
        // 'bad' is left as null reference. There is nothing more we can do. 
        } 
        finally 
        { 
        if (bad != null) 
        { 
         bad.Dispose(); 
        } 
        } 
    
        GoodDesign good = new GoodDesign(); 
        try 
        { 
        good.PerformIOBoundOperation(); 
        } 
        catch 
        { 
        // Do something to 'good' to recover from the error. 
        } 
        finally 
        { 
        good.Dispose(); 
        } 
    } 
    
    1

    Aktivitäten betroffen sein, dieses Muster mit Bezug auf:

    • Einzel Prinzip Verantwortung
    • Umsetzung Funktionale Dekomposition Antipattern
    • Brechen Anderenfalls gemeinsamen Erwartungen der Verbraucher

    Einzel treffen Verantwortung Prinzip

    Dies ist eine geringfügige Abweichung vom ursprünglichen Prinzip, wird aber stattdessen auf Funktionen angewendet. Wenn Ihr Konstrukteur mehr als eine Sache macht (Konstruktion und Verarbeitung), erschweren Sie die Wartung dieser Methode in der Zukunft. Wenn ein Anrufer während der Konstruktion nicht arbeiten will, lässt er keine Option. Wenn die "Verarbeitung" eines Tages zusätzliche Schritte erfordert, die nicht im Konstruktor enthalten sein sollten, müssen Sie überall, wo Sie diese Methode verwenden, eine Umgestaltung vornehmen.

    Funktionale Dekomposition Antipattern

    Ohne Einzelheiten zu wissen, würde ich diesen Code betroffen sein, der tut dies, diese Antipattern implementiert. Die Objekte sind keine echten Objekte, sondern funktionale Programmiereinheiten, die in objekt-orientierte Verkleidung gehüllt sind.

    Gemeinsame Erwartungen der Verbraucher

    Würden die durchschnittlichen Anrufer dieses Verhalten des Konstrukteurs erwarten? Es kann Orte geben, an denen eine zusätzliche Verarbeitung während der Konstruktion eine kurze Handprogrammierung sein kann. Aber das sollte explizit dokumentiert und von den Anrufern gut verstanden werden.Die Gesamtnutzung des Objekts sollte in diesem Kontext noch sinnvoll sein und es sollte für den Aufrufer selbstverständlich sein, es auf diese Weise zu verwenden.

    Verstehen Sie auch, was Sie auf Ihren Verbraucher erzwingen. Wenn Sie die Verarbeitung im Konstruktor durchführen, zwingen Sie den Verbraucher, diese Verarbeitung (in Bezug auf die Verarbeitungszeit) zu bezahlen, unabhängig davon, ob sie dies wünschen oder nicht. Sie eliminieren die Möglichkeit einer "faulen" Verarbeitung oder anderer Formen der Optimierung.

    Annehmbare Verwendung?

    Nur an Orten, wo die gemeinsame Verwendung der Klasse eine Initialisierung erfordert, die zur Konstruktionszeit optional sein kann. Die Dateiklasse ist ein gutes Beispiel:

    /* This is a common usage of this class */ 
    File f; 
    f.Open(path); 
    
    /* This constructor constructs the object and puts it in the open state in one step 
    * for convenience */ 
    File f(path); 
    

    Aber auch das ist fraglich. Behält die Dateiklasse den Pfad zur Datei intern bei oder wird sie nur zum Öffnen einer Datei verwendet? Wenn es den Dateipfad speichert, haben jetzt Open() und der neue Konstruktor mehrere Verantwortlichkeiten (SetPath (p) und Open()). In diesem Fall sollte der Konstruktor "Datei (Pfad)" möglicherweise die Datei nicht öffnen, sondern nur den Pfad im Objekt festlegen.

    Es gibt viele Überlegungen basierend auf den fraglichen Objekten. Erwägen Sie, Komponententests für Ihre Objekte zu schreiben. Sie werden Ihnen helfen, viele dieser Anwendungsfälle auszuarbeiten.

    0

    Ihre Klasse ist technisch gut, und es könnte einfach auf persönliche Philosophie kommen.

    aber auf anderen dies lesen, dass einige verrückten Ideen über Konstrukteure bekommen könnte, dies lesen:

    http://www.ibm.com/developerworks/java/library/j-jtp0618.html

    Wenn Sie rund um die „diese“ Referenz zu werfen beginnen gehen, Sie können am Ende eine Menge Ärger, besonders in Multithread-Umgebungen. Nur ein FYI.

    Verwandte Themen