Vantaggi di più metodi rispetto a Switch


12

Oggi ho ricevuto una recensione del codice da uno sviluppatore senior che mi chiedeva "A proposito, qual è la tua obiezione alle funzioni di invio tramite un'istruzione switch?" Ho letto in molti posti su come pompare un argomento attraverso il passaggio ai metodi di chiamata è un OOP negativo, non così estensibile, ecc. Tuttavia, non riesco davvero a trovare una risposta definitiva per lui. Vorrei risolverlo una volta per tutte.

Ecco i nostri suggerimenti sul codice concorrenti (php usato come esempio, ma può essere applicato in modo più universale):

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));
   }
}

Parte del suo argomento era "It (switch method) ha un modo molto più pulito di gestire i casi predefiniti rispetto a quello che hai nel metodo magico __call () generico".

Non sono d'accordo sulla pulizia e in effetti preferisco chiamare, ma mi piacerebbe sentire quello che gli altri hanno da dire.

Argomenti che posso trarre a sostegno dello Oopschema:

  • Un po 'più pulito in termini di codice che devi scrivere (meno, più facile da leggere, meno parole chiave da considerare)
  • Non tutte le azioni delegate a un singolo metodo. Non c'è molta differenza nell'esecuzione qui, ma almeno il testo è più compartimentato.
  • Allo stesso modo, un altro metodo può essere aggiunto in qualsiasi punto della classe anziché in uno spot specifico.
  • I metodi sono spaziati, il che è carino.
  • Non si applica qui, ma considera un caso in cui Switch::go()operato su un membro piuttosto che su un parametro. Dovresti prima cambiare il membro, quindi chiamare il metodo. Perché Ooppuoi chiamare i metodi in modo indipendente in qualsiasi momento.

Argomenti che posso trarre a sostegno dello Switchschema:

  • Per ragioni di argomento, un metodo più pulito per gestire una richiesta predefinita (sconosciuta)
  • Sembra meno magico, il che potrebbe far sentire gli sviluppatori sconosciuti a proprio agio

Qualcuno ha qualcosa da aggiungere per entrambe le parti? Mi piacerebbe avere una buona risposta per lui.


@Justin Satyr Ci ho pensato, ma penso che questa domanda riguardi più specificamente il codice e la ricerca di una soluzione ottimale ed è quindi appropriata per StackOverflow. E come dice @ yes123, è probabile che più persone rispondano qui.

__call è male. Uccide completamente le prestazioni e puoi usarlo per chiamare un metodo che dovrebbe essere privato per i chiamanti esterni.

Ooppermette di avere phpdoc per descrivere ogni metodo, che può essere analizzato da alcuni IDE (es. NetBeans).
binaryLV

fluffycat.com/PHP-Design-Patterns/… .. apparentemente Switch non è poi così efficiente. -1

@GordonM: cosa succede se la classe in questione non ha metodi privati?
JAB,

Risposte:


10

Un interruttore è considerato non OOP perché spesso il polimorfismo può fare il trucco.

Nel tuo caso, un'implementazione OOP potrebbe essere questa:

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();

1
Il polimorfismo è la risposta corretta. +1
Rein Henrichs,

2
Questo è positivo, ma il rovescio della medaglia è una proliferazione eccessiva di codice. Devi avere una classe per ogni metodo e questo è più codice per gestire .. e più memoria. Non c'è un mezzo felice?
Explosion Pills,

8

per questo esempio:

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

OK, sto scherzando solo parzialmente qui. L'argomento a favore / contro l'uso di un'istruzione switch non può essere fortemente espresso con un esempio così banale, poiché il lato OOP dipende dalla semantica coinvolta, non semplicemente dal meccanismo di invio .

Le istruzioni switch sono spesso un'indicazione di classi o classificazioni mancanti, ma non necessariamente. A volte un'istruzione switch è solo un'istruzione switch .


+1 per "semantica, non meccanismi"
Javier,

1

Forse non è una risposta, ma nel caso del codice non-switch, sembra che questa sarebbe una corrispondenza migliore:

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";
  }
}

Una grande parte del puzzle da valutare è cosa succede quando è necessario creare NewSwitcho NewOop. I tuoi programmatori devono saltare attraverso i cerchi con un metodo o l'altro? Cosa succede quando cambiano le tue regole, ecc.


Mi piace la tua risposta - avrebbe pubblicato lo stesso, ma sei stato ninja da te. Penso che dovresti cambiare method_exists in is_callable () per evitare problemi con i metodi protetti ereditati e puoi cambiare call_user_func in $ this -> {'go _'. $ Arg} () per rendere il tuo codice più leggibile. Un'altra cosa - maby aggiunge un commento sul perché il metodo magico __call è male - rovina la funzionalità is_callable () su quell'oggetto o istanza di esso perché restituirà sempre VERO.

Ho aggiornato is_callable, ma ho lasciato call_user_func poiché (non parte di questa domanda), potrebbero esserci altri argomenti da passare. Ma ora che questo è passato ai programmatori, sono decisamente d'accordo sul fatto che la risposta di @ Andrea sia molto migliore :)
Rob,

0

Invece di mettere semplicemente il tuo codice di esecuzione in funzioni, implementa un modello di comando completo e posiziona ognuno nella propria classe che implementa un'interfaccia comune. Questo metodo consente di utilizzare IOC / DI per collegare i diversi "casi" della classe e consente di aggiungere e rimuovere facilmente i casi dal codice nel tempo. Ti dà anche un buon po 'di codice che non viola i principi di programmazione SOLID.


0

Penso che l'esempio sia negativo. Se esiste una funzione go () che accetta l'argomento $ where, if è perfettamente valido per usare switch nella funzione. La funzione single go () semplifica la modifica del comportamento di tutti gli scenari go_where (). Inoltre, manterrai l'interfaccia della classe: se usi vari metodi, stai modificando l'interfaccia della classe con ogni nuova destinazione.

In realtà il cambio non dovrebbe essere sostituito con un insieme di metodi, ma con un insieme di classi: questo è polimorfismo. Quindi la destinazione verrà gestita da ciascuna sottoclasse e ci sarà un singolo metodo go () per tutti. Sostituire condizionale con polimorfismo è uno dei refactoring di base, descritto da Martin Fowler. Ma potresti non aver bisogno del polimorfismo e cambiare è la strada da percorrere.


Se non si cambia l'interfaccia e una sottoclasse ne ha una propria implementazione go(), può violare facilmente il principio di sostituzione di Liskov. Se si sta aggiungendo ulteriori funzionalità a go(), si fa desidera modificare l'interfaccia come quanto mi concerend.
Explosion Pills,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.