Vorteile für mehrere Methoden gegenüber Switch

12

Ich habe heute eine Codeüberprüfung von einem leitenden Entwickler erhalten, in der gefragt wurde: "Was halten Sie übrigens davon, Funktionen über eine switch-Anweisung zu verteilen?" Ich habe an vielen Stellen gelesen, wie schlecht es ist, ein Argument durch die Methode zum Umschalten auf einen Aufruf zu pumpen. OOP, nicht so erweiterbar usw. Eine endgültige Antwort kann ich ihm jedoch nicht geben. Ich würde das gerne ein für alle Mal für mich selbst regeln.

Hier sind unsere konkurrierenden Codevorschläge (PHP wird als Beispiel verwendet, kann aber universeller angewendet werden):

class Switch {
   public function go($arg) {
      switch ($arg) {
         case "one":
            echo "one\n";
         break;
         case "two":
            echo "two\n";
         break;
         case "three":
            echo "three\n";
         break;
         default:
            throw new Exception("Unknown call: $arg");
         break;
      }
   }
}

class Oop {
   public function go_one() {
      echo "one\n";
   }
   public function go_two() {
      echo "two\n";
   }
   public function go_three() {
      echo "three\n";
   }
   public function __call($_, $__) {
      throw new Exception("Unknown call $_ with arguments: " . print_r($__, true));
   }
}

Ein Teil seines Arguments lautete: "Es (Switch-Methode) behandelt Standardfälle viel sauberer als die generische Methode __call () magic."

Ich bin nicht einverstanden mit der Sauberkeit und bevorzuge in der Tat einen Anruf, aber ich würde gerne hören, was andere zu sagen haben.

Argumente, die ich zur Unterstützung des OopSchemas vorbringen kann:

  • Etwas sauberer in Bezug auf den Code, den Sie schreiben müssen (weniger, leichter zu lesen, weniger zu berücksichtigende Schlüsselwörter)
  • Nicht alle Aktionen wurden an eine einzelne Methode delegiert. Hier gibt es keinen großen Unterschied in der Ausführung, aber zumindest ist der Text mehr unterteilt.
  • Auf die gleiche Weise kann an einer beliebigen Stelle in der Klasse eine andere Methode anstelle einer bestimmten Stelle hinzugefügt werden.
  • Methoden haben einen Namensraum, was sehr gut ist.
  • Gilt hier nicht, aber es wird ein Fall betrachtet, in dem Switch::go()ein Element und kein Parameter bearbeitet wird. Sie müssten zuerst das Mitglied ändern und dann die Methode aufrufen. Denn OopSie können die Methoden jederzeit eigenständig aufrufen.

Argumente, die ich zur Unterstützung des SwitchSchemas vorbringen kann:

  • Der Einfachheit halber eine sauberere Methode für die Behandlung einer (unbekannten) Standardanforderung
  • Scheint weniger magisch, was dazu führen könnte, dass sich unbekannte Entwickler wohler fühlen

Hat jemand für beide Seiten etwas hinzuzufügen? Ich hätte gerne eine gute Antwort für ihn.

Explosionspillen
quelle
@Justin Satyr Ich habe darüber nachgedacht, aber ich denke, dass es bei dieser Frage speziell um Code und die Suche nach einer optimalen Lösung geht und sich daher für den Stackoverflow eignet. Und wie @ yes123 sagt, werden hier wahrscheinlich mehr Leute antworten.
__anruf ist schlecht. Dadurch wird die Leistung vollständig beeinträchtigt, und Sie können damit eine Methode aufrufen, die für externe Anrufer privat sein soll.
Ooperlaubt phpdoc zur Beschreibung jeder Methode, die von einigen IDEs (zB NetBeans) analysiert werden kann.
binaryLV
fluffycat.com/PHP-Design-Patterns/… .. anscheinend ist Switch nicht so effizient. -1
@ GordonM: Was ist, wenn die betreffende Klasse keine privaten Methoden hat?
JAB

Antworten:

10

Ein Schalter wird nicht als OOP betrachtet, da häufig Polymorphismus den Trick bewirken kann.

In Ihrem Fall könnte eine OOP-Implementierung folgendermaßen aussehen:

class Oop 
{
  protected $goer;

  public function __construct($goer)
  {
    $this->goer = $goer;
  }

  public function go()
  {
    return $this->goer->go();
  }
}

class Goer
{
  public function go()
  {
    //...
  }
}

class GoerA extends Goer
{
  public function go()
  {
    //...
  }
}

class GoerB extends Goer
{
  public function go()
  {
    //...
  }
}

class GoerC extends Goer
{
  public function go()
  {
    //...
  }
}


$oop = new Oop(new GoerB());
$oop->go();
Ando
quelle
1
Polymorphismus ist die richtige Antwort. +1
Rein Henrichs
2
Das ist gut, aber der Nachteil ist eine übermäßige Verbreitung von Code. Sie müssen für jede Methode eine Klasse haben, und das ist mehr Code für den Umgang mit ... und mehr Speicher. Gibt es nicht ein glückliches Medium?
Explosion Pills
8

für dieses Beispiel:

class Switch
{
    public function go($arg)
    {
        echo "$arg\n";
    }
}

OK, hier nur teilweise ein Scherz. Das Argument für / gegen die Verwendung einer switch-Anweisung kann mit einem solchen trivialen Beispiel nicht eindeutig formuliert werden , da die OOP-Seite von der Semantik abhängt, nicht nur vom Versandmechanismus .

Switch-Anweisungen sind oft ein Hinweis auf fehlende Klassen oder Klassifizierungen, aber nicht unbedingt. Manchmal ist eine switch-Anweisung nur eine switch-Anweisung .

Steven A. Lowe
quelle
+1 für "Semantik, nicht Mechanismen"
Javier
1

Vielleicht keine Antwort, aber im Falle des Nicht-Switch-Codes scheint dies eine bessere Übereinstimmung zu sein:

class Oop {
  /**
   * User calls $oop->go('one') then this function will determine if the class has a 
   * method 'go_one' and call that. If it doesn't, then you get your error.
   * 
   * Subclasses of Oop can either overwrite the existing methods or add new ones.
   */
  public function go($arg){

    if(is_callable(array($this, 'go_'. $arg))){
      return call_user_func(array($this, 'go_'. $arg));
    }

    throw new Exception("Unknown call: $arg");
  }

  public function go_one() {
    echo "one\n";
  }
  public function go_two() {
    echo "two\n";
  }
  public function go_three() {
    echo "three\n";
  }
}

Ein großer Teil des zu bewertenden Puzzles ist, was passiert, wenn Sie NewSwitchoder erstellen müssen NewOop. Müssen Ihre Programmierer nach der einen oder anderen Methode durch die Rahmen springen? Was passiert, wenn sich Ihre Regeln usw. ändern?

rauben
quelle
Ich mag deine Antwort - wollte das gleiche posten, wurde aber von dir ninjaed. Ich denke, Sie sollten method_exists in is_callable () ändern, um Probleme mit geerbten geschützten Methoden zu vermeiden, und Sie können call_user_func in $ this -> {'go _'. $ Arg} () ändern, um Ihren Code lesbarer zu machen. Eine andere Sache - vielleicht einen Kommentar hinzufügen, warum die magische Methode __call schlecht ist - sie ruiniert die Funktionalität von is_callable () für dieses Objekt oder dessen Instanz, da sie immer TRUE zurückgibt.
Ich habe auf aktualisiert is_callable, aber call_user_func verlassen, da (nicht Teil dieser Frage) möglicherweise andere Argumente weitergegeben werden müssen. Aber jetzt, da dies auf Programmierer übergegangen ist, stimme ich definitiv zu, dass die Antwort von @ Andrea viel besser ist :)
Rob
0

Anstatt nur den ausgeführten Code in Funktionen zu integrieren, implementieren Sie ein vollständiges Befehlsmuster und ordnen Sie sie jeweils einer eigenen Klasse zu, die eine gemeinsame Schnittstelle implementiert. Mit dieser Methode können Sie mithilfe von IOC / DI die verschiedenen "Fälle" Ihrer Klasse verkabeln und im Laufe der Zeit problemlos Fälle zu Ihrem Code hinzufügen und daraus entfernen. Es gibt Ihnen auch ein gutes Stück Code, das nicht gegen SOLID-Programmierprinzipien verstößt.

P. Roe
quelle
0

Ich finde das Beispiel schlecht. Wenn es eine go () - Funktion gibt, die das Argument $ where akzeptiert, ist es vollkommen gültig, switch in der Funktion zu verwenden. Die Funktion single go () erleichtert das Ändern des Verhaltens aller Szenarien in go_where (). Darüber hinaus wird die Klassenschnittstelle beibehalten. Wenn Sie verschiedene Methoden verwenden, ändern Sie die Schnittstelle der Klasse mit jedem neuen Ziel.

Eigentlich sollte switch nicht durch eine Reihe von Methoden ersetzt werden, sondern durch eine Reihe von Klassen - das ist Polymorphismus. Dann wird das Ziel von jeder Unterklasse behandelt, und es gibt für alle eine einzelne go () -Methode. Bedingt durch Polymorphismus zu ersetzen , ist eine der grundlegenden Umgestaltungen, die von Martin Fowler beschrieben wurden. Aber Sie brauchen möglicherweise keinen Polymorphismus, und der Wechsel ist der richtige Weg.

Maxim Krizhanovsky
quelle
Wenn Sie die Schnittstelle nicht ändern und eine Unterklasse über eine eigene Implementierung verfügt go(), kann dies leicht gegen das Liskov-Substitutionsprinzip verstoßen. Wenn Sie mehr Funktionalität hinzugefügt go(), Sie tun mögen die Schnittstelle so weit ändern , wie ich bin concerend.
Explosion Pills