Perché la mia classe è peggiore della gerarchia delle lezioni nel libro (OOP per principianti)?


11

Sto leggendo PHP Objects, Patterns and Practice . L'autore sta cercando di modellare una lezione in un college. L'obiettivo è produrre il tipo di lezione (lezione o seminario) e le spese per la lezione a seconda che si tratti di una lezione oraria o a prezzo fisso. Quindi l'output dovrebbe essere

Lesson charge 20. Charge type: hourly rate. Lesson type: seminar.
Lesson charge 30. Charge type: fixed rate. Lesson type: lecture.

quando l'ingresso è il seguente:

$lessons[] = new Lesson('hourly rate', 4, 'seminar');
$lessons[] = new Lesson('fixed rate', null, 'lecture');

Ho scritto questo:

class Lesson {
    private $chargeType;
    private $duration;
    private $lessonType;

    public function __construct($chargeType, $duration, $lessonType) {
        $this->chargeType = $chargeType;
        $this->duration = $duration;
        $this->lessonType = $lessonType;
    }

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

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

    public function cost() {
        if($this->chargeType == 'fixed rate') {
            return "30";
        } else {
            return $this->duration * 5;
        }
    }
}

$lessons[] = new Lesson('hourly rate', 4, 'seminar');
$lessons[] = new Lesson('fixed rate', null, 'lecture');

foreach($lessons as $lesson) {
    print "Lesson charge {$lesson->cost()}.";
    print " Charge type: {$lesson->getChargeType()}.";
    print " Lesson type: {$lesson->getLessonType()}.";
    print "<br />";
}

Ma secondo il libro, ho torto (sono abbastanza sicuro di esserlo anche io). Invece, l'autore ha fornito una grande gerarchia di classi come soluzione. In un capitolo precedente, l'autore ha dichiarato i seguenti "quattro segni" come il momento in cui dovrei considerare di cambiare la mia struttura di classe:

L'unico problema che posso vedere sono le dichiarazioni condizionali, e anche quello in modo vago - quindi perché rifattorlo? Quali problemi pensi possano sorgere in futuro che non ho previsto?

Aggiornamento : ho dimenticato di menzionare - questa è la struttura di classe che l'autore ha fornito come soluzione - il modello di strategia :

Il modello di strategia


29
Non imparare la programmazione orientata agli oggetti da un libro PHP.
giorgiosironi,

4
@giorgiosironi Ho smesso di valutare le lingue. Uso PHP quotidianamente, quindi è il più veloce per me imparare nuovi concetti in esso. Ci sono sempre persone che odiano una lingua (Java: slidesha.re/91C4pX ) - è vero che è più difficile trovare gli odiatori di Java che i dissidenti di PHP, ma comunque. Potrebbero esserci problemi con OO di PHP, ma per il momento non posso risparmiare tempo per imparare la sintassi Java, il "modo Java" e OOP. Inoltre, la programmazione desktop è estranea a me. Sono una persona web completa. Ma prima di poter accedere a JSP, dovresti conoscere Java desktop (senza citazioni, sbaglio?)
Aditya MP

Utilizzare le costanti per "tariffa fissa" / "tariffa oraria" e "seminario" / "lezione" anziché stringhe.
Tom Marthenal,

Risposte:


19

È divertente che il libro non lo affermi chiaramente, ma il motivo per cui favorisce una gerarchia di classi se le istruzioni all'interno della stessa classe sono probabilmente il principio Open / Closed Questa regola di progettazione software ampiamente nota afferma che una classe dovrebbe essere chiusa a modifica ma aperto per l'estensione.

Nel tuo esempio, aggiungere un nuovo tipo di lezione significherebbe cambiare il codice sorgente della lezione, rendendolo fragile e soggetto a regressione. Se avessi una classe base e una derivata per ogni tipo di lezione, dovresti invece semplicemente aggiungere un'altra sottoclasse, generalmente considerata più pulita.

Puoi mettere quella regola nella sezione "Dichiarazioni condizionali" se vuoi, tuttavia ritengo che il "cartello" sia un po 'vago. Se le dichiarazioni sono generalmente solo odori di codice, sintomi. Possono derivare da una vasta gamma di decisioni di progettazione sbagliate.


3
Sarebbe un'idea di gran lunga migliore utilizzare un'estensione funzionale piuttosto che l'ereditarietà.
DeadMG

6
Esistono sicuramente altri / migliori approcci al problema. Tuttavia, questo è chiaramente un libro sulla programmazione orientata agli oggetti e il principio aperto / chiuso mi ha appena colpito come il modo classico di affrontare quel tipo di dilemma progettuale in OO.
guillaume31,

Che ne dici del modo migliore per affrontare il problema ? Non ha senso insegnare una soluzione inferiore solo perché è OO.
DeadMG

16

Immagino che ciò che il libro mira a insegnare sia evitare cose come:

public function cost() {
    if($this->chargeType == 'fixed rate') {
        return "30";
    } else {
        return $this->duration * 5;
    }
}

La mia interpretazione del libro è che dovresti avere tre classi:

  • AbstractLesson
  • HourlyRateLesson
  • FixedRateLesson

È quindi necessario reimplementare la costfunzione su entrambe le sottoclassi.

La mia opinione personale

per casi semplici come quelli: no. È strano che il tuo libro favorisca una gerarchia di classe più profonda per evitare semplici dichiarazioni condizionali. Inoltre, con le tue specifiche di input, Lessondovrà essere una factory con una spedizione che è una dichiarazione condizionale. Quindi con la soluzione del libro avrai:

  • 3 lezioni invece di 1
  • 1 livello di eredità anziché 0
  • lo stesso numero di dichiarazioni condizionali

Questa è una maggiore complessità senza alcun vantaggio aggiunto.


8
In questo caso , sì, è troppa complessità. Ma in un sistema più grande, aggiungerai più lezioni, come SemesterLesson, MasterOneWeekLessonecc. Una classe root astratta, o meglio un'interfaccia, è sicuramente la strada da percorrere allora. Ma quando hai solo due casi, dipende dalla discrezione dell'autore.
Michael K,

5
Sono d'accordo con te in generale. Tuttavia, esempi educativi sono spesso inventati e sovra-progettati per illustrare un punto. Ignorate altre considerazioni per un momento per non confondere il problema a portata di mano e introducete tali considerazioni in seguito.
Karl Bielefeldt,

+1 Bella analisi dettagliata. Il commento "Questa è più complessità senza alcun vantaggio aggiunto" illustra perfettamente il concetto di "costo opportunità" nella programmazione. Teoria! = Praticità.
Evan Plaice,

7

L'esempio nel libro è piuttosto strano. Dalla tua domanda, la dichiarazione originale è:

L'obiettivo è produrre il tipo di lezione (lezione o seminario) e gli addebiti per la lezione a seconda che si tratti di una lezione oraria oa prezzo fisso.

che, nel contesto di un capitolo su OOP, significherebbe che probabilmente l'autore vuole che tu abbia la seguente struttura:

+ abstract Lesson
    - Lecture inherits Lesson
    - Seminar inherits Lesson

+ abstract Charge
    - HourlyCharge inherits Charge
    - FixedCharge inherits Charge

Ma poi arriva l'input che hai citato:

$lessons[] = new Lesson('hourly rate', 4, 'seminar');
$lessons[] = new Lesson('fixed rate', null, 'lecture');

vale a dire qualcosa che si chiama codice tipicamente stringa e che, onestamente, in questo contesto in cui il lettore è destinato a imparare OOP, è orribile.

Questo significa anche che se ho ragione sull'intenzione dell'autore del libro (cioè creando quelle classi astratte ed ereditate che ho elencato sopra), questo porterà a un codice duplicato e piuttosto illeggibile e brutto:

const string $HourlyRate = 'hourly rate';
const string $FixedRate = 'fixed rate';

// [...]

Charge $charge;
switch ($chargeType)
{
    case $HourlyRate:
        $charge = new HourlyCharge();
        break;

    case $FixedRate:
        $charge = new FixedCharge();
        break;

    default:
        throw new ParserException('The value of chargeType is unexpected.');
}

// Imagine the similar code for lecture/seminar, and the similar code for both on output.

Per quanto riguarda i "pannelli":

  • Duplicazione del codice

    Il tuo codice non ha duplicazioni. Il codice che l'autore si aspetta che tu scriva lo fa.

  • La classe che sapeva troppo del suo contesto

    La tua classe passa semplicemente le stringhe dall'input all'output e non sa nulla. Il codice previsto d'altra parte può essere criticato per sapere troppo.

  • The Jack of All Trades - Classi che cercano di fare molte cose

    Ancora una volta, stai solo passando le stringhe, niente di più.

  • Dichiarazioni condizionali

    C'è un'istruzione condizionale nel tuo codice. È leggibile e facile da capire.


Forse, in effetti, l'autore si aspettava che tu scrivessi un codice molto più refactored di quello con sei classi sopra. Ad esempio, è possibile utilizzare il modello di fabbrica per lezioni e addebiti, ecc.

+ abstract Lesson
    - Lecture inherits Lesson
    - Seminar inherits Lesson

+ abstract Charge
    - HourlyCharge inherits Charge
    - FixedCharge inherits Charge

+ LessonFactory

+ ChargeFactory

+ Parser // Uses factories to transform the input into `Lesson`.

In questo caso, finirai con nove classi, che saranno un perfetto esempio di sovrastruttura .

Invece, hai finito con un codice facile da capire e pulito, che è dieci volte più breve.


Caspita, le tue ipotesi sono precise! Guarda l'immagine che ho caricato - l'autore ha raccomandato esattamente le stesse cose.
Aditya MP,

Mi piacerebbe molto accettare questa risposta, ma mi piace anche la risposta di @ ian31 :( Oh, cosa devo fare!
MP Aditya,

5

Prima di tutto puoi cambiare "numeri magici" come 30 e 5 nella funzione cost () in variabili più significative :)

E ad essere sincero, penso che non dovresti preoccuparti di questo. Quando dovrai cambiare la classe, la cambierai. Prova prima a creare valore, poi passa alla rifattorizzazione.

E perché pensi di avere torto? Questa lezione non è "abbastanza buona" per te? Perché sei preoccupato per qualche libro;)


1
Grazie per aver risposto! In effetti, il refactoring arriverà più tardi. Tuttavia, ho scritto questo codice per l'apprendimento, cercando di risolvere il problema presentato nel libro a modo mio e vedendo come sbaglio ...
Aditya MP

2
Ma lo saprai più tardi. Quando questa classe verrà utilizzata in altre parti del sistema e dovrai aggiungere qualcosa di nuovo. Non esiste una soluzione "proiettile d'argento" :) Qualcosa può essere buono o cattivo dipende dal sistema, dai requisiti, ecc. Durante l'apprendimento di OOP devi fallire molto: P Poi con il tempo noterai alcuni problemi e schemi comuni. Questo esempio è troppo semplice per dare qualche consiglio. Oh, raccomando davvero questo post di Joel :) Gli astronauti dell'architettura rilevano joelonsoftware.com/items/2008/05/01.html
Michal Franc

@adityamenon Quindi la tua risposta è "il refactoring verrà dopo". Aggiungi le altre classi solo una volta necessarie per rendere il codice più semplice, in genere è qui che arriva il terzo caso d'uso simile. Vedi la risposta di Simon.
Izkata,

3

Non è assolutamente necessario implementare classi aggiuntive. Hai qualche MAGIC_NUMBERproblema con la tua cost()funzione, ma il gioco è fatto. Qualsiasi altra cosa è un enorme ingegnerizzazione eccessiva. Tuttavia, è purtroppo comune che vengano dati dei pessimi consigli: Circle eredita Shape, per esempio. Sicuramente non è in alcun modo efficace derivare una classe per la semplice eredità di una funzione. È possibile utilizzare un approccio funzionale per personalizzarlo invece.


1
Questo è interessante. Puoi spiegare come lo faresti con un esempio?
guillaume31,

+1, sono d'accordo, nessun motivo per progettare eccessivamente / complicare un po 'di funzionalità.
GrandmasterB

@ ian31: fare cosa, in particolare?
DeadMG

@DeadMG "usa un approccio funzionale", "usa un'estensione funzionale piuttosto che l'eredità"
guillaume31
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.