Warum ist meine Klasse schlechter als die Hierarchie der Klassen im Buch (Anfänger-OOP)?

11

Ich lese PHP-Objekte, Muster und Übungen . Der Autor versucht, eine Lektion in einem College zu modellieren. Ziel ist es, den Unterrichtstyp (Vorlesung oder Seminar) und die Gebühren für den Unterricht auszugeben, je nachdem, ob es sich um einen Stunden- oder einen Festpreisunterricht handelt. So sollte die Ausgabe sein

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

wenn die Eingabe wie folgt ist:

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

Ich habe das geschrieben:

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

Aber laut dem Buch irre ich mich (ich bin mir ziemlich sicher, dass ich es auch bin). Stattdessen gab der Autor eine große Hierarchie von Klassen als Lösung an. In einem früheren Kapitel hat der Autor die folgenden "vier Wegweiser" als den Zeitpunkt angegeben, zu dem ich eine Änderung meiner Klassenstruktur in Betracht ziehen sollte:

  • Codeduplizierung
  • Die Klasse, die zu viel über ihren Kontext wusste
  • Der Alleskönner - Klassen, die versuchen, viele Dinge zu tun
  • Bedingte Anweisungen

Das einzige Problem, das ich sehen kann, sind bedingte Aussagen, und das auch auf vage Weise - warum also umgestalten? Welche Probleme könnten Ihrer Meinung nach in Zukunft auftreten, die ich nicht vorausgesehen habe?

Update : Ich habe vergessen zu erwähnen - dies ist die Klassenstruktur, die der Autor als Lösung bereitgestellt hat - das Strategiemuster :

Das Strategiemuster

Aditya MP
quelle
29
Lernen Sie keine objektorientierte Programmierung aus einem PHP-Buch.
Giorgiosironi
4
@giorgiosironi Ich habe es aufgegeben, Sprachen zu bewerten. Ich benutze PHP täglich und daher ist es für mich am schnellsten, neue Konzepte darin zu lernen. Es gibt immer Leute, die eine Sprache hassen (Java: slideha.re/91C4pX ) - zugegebenermaßen ist es schwieriger, die Java-Hasser zu finden als die PHP-Dissidenten, aber immer noch. Es mag Probleme mit dem OO von PHP geben, aber im Moment kann ich mir nicht die Zeit nehmen, um die Java-Syntax, den "Java-Weg" und OOP zu lernen . Außerdem ist mir die Desktop-Programmierung fremd. Ich bin eine komplette Web-Person. Aber bevor Sie zu JSP gelangen können, müssen Sie angeblich Desktop-Java kennen (Keine Zitate, irre ich mich?)
Aditya MP
Verwenden Sie Konstanten für "Festpreis" / "Stundensatz" und "Seminar" / "Vorlesung" anstelle von Zeichenfolgen.
Tom Marthenal

Antworten:

19

Es ist lustig, dass das Buch es nicht klar ausdrückt, aber der Grund, warum es eine Klassenhierarchie gegenüber bevorzugt, wenn Anweisungen innerhalb derselben Klasse wahrscheinlich das Open / Closed-Prinzip sind Diese allgemein bekannte Software-Design-Regel besagt, dass eine Klasse geschlossen werden sollte Änderung, aber offen für Erweiterung.

In Ihrem Beispiel würde das Hinzufügen eines neuen Unterrichtstyps bedeuten, dass der Quellcode der Unterrichtsklasse geändert wird, wodurch sie anfällig und anfällig für Regressionen wird. Wenn Sie eine Basisklasse und eine Ableitung für jeden Lektionstyp hätten, müssten Sie stattdessen nur eine weitere Unterklasse hinzufügen, die im Allgemeinen als sauberer angesehen wird.

Sie können diese Regel unter den Abschnitt "Bedingte Anweisungen" stellen, wenn Sie so wollen. Ich halte diesen "Wegweiser" jedoch für etwas vage. Wenn Aussagen im Allgemeinen nur Code-Gerüche sind, Symptome. Sie können aus einer Vielzahl von schlechten Entwurfsentscheidungen resultieren.

guillaume31
quelle
3
Es wäre eine weitaus bessere Idee, eine funktionale Erweiterung anstelle einer Vererbung zu verwenden.
DeadMG
6
Es gibt sicherlich andere / bessere Ansätze für das Problem. Dies ist jedoch eindeutig ein Buch über objektorientierte Programmierung, und das Open / Closed-Prinzip erschien mir gerade als die klassische Art, sich einem solchen Design-Dilemma in OO zu nähern.
Guillaume31
Wie wäre es mit dem besten Weg, um das Problem anzugehen ? Es macht keinen Sinn, eine minderwertige Lösung zu lehren, nur weil es OO ist.
DeadMG
16

Ich denke, dass das Buch darauf abzielt, Dinge zu vermeiden wie:

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

Meine Interpretation des Buches ist, dass Sie drei Klassen haben sollten:

  • AbstractLesson
  • HourlyRateLesson
  • FixedRateLesson

Sie sollten dann die costFunktion in beiden Unterklassen erneut implementieren .

Meine persönliche Meinung

für einfache Fälle wie diesen: nicht. Es ist seltsam, dass Ihr Buch eine tiefere Klassenhierarchie bevorzugt, um einfache bedingte Anweisungen zu vermeiden. Darüber hinaus muss es sich bei Ihrer Eingabespezifikation Lessonum eine Fabrik mit einem Versand handeln, bei dem es sich um eine bedingte Anweisung handelt. Mit der Buchlösung haben Sie also:

  • 3 Klassen statt 1
  • 1 Vererbungsstufe statt 0
  • die gleiche Anzahl von bedingten Anweisungen

Das ist mehr Komplexität ohne zusätzlichen Nutzen.

Simon Bergot
quelle
8
In diesem Fall ist es ja zu komplex. Aber in einem größeren System, werden Sie mehr Unterricht des Hinzufügen, wie SemesterLesson, MasterOneWeekLessonetc. Eine abstrakte Wurzelklasse, oder besser noch eine Schnittstelle, ist definitiv der Weg dann zu gehen. Wenn Sie jedoch nur zwei Fälle haben, liegt es im Ermessen des Autors.
Michael K
5
Ich stimme Ihnen im Allgemeinen zu. Bildungsbeispiele werden jedoch häufig erfunden und überarbeitet, um einen Punkt zu veranschaulichen. Sie ignorieren andere Überlegungen für einen Moment, um das vorliegende Problem nicht zu verwirren, und führen diese Überlegungen später ein.
Karl Bielefeldt
+1 Schöne gründliche Aufschlüsselung. Der Kommentar "Das ist mehr Komplexität ohne zusätzlichen Nutzen" veranschaulicht das Konzept der Opportunitätskosten bei der Programmierung perfekt. Theorie! = Praktikabilität.
Evan Plaice
7

Das Beispiel im Buch ist ziemlich seltsam. Aus Ihrer Frage ergibt sich die ursprüngliche Aussage:

Ziel ist es, den Unterrichtstyp (Vorlesung oder Seminar) und die Gebühren für den Unterricht auszugeben, je nachdem, ob es sich um einen Stunden- oder einen Festpreisunterricht handelt.

Dies würde im Kontext eines Kapitels über OOP bedeuten, dass der Autor wahrscheinlich möchte, dass Sie folgende Struktur haben:

+ abstract Lesson
    - Lecture inherits Lesson
    - Seminar inherits Lesson

+ abstract Charge
    - HourlyCharge inherits Charge
    - FixedCharge inherits Charge

Aber dann kommt die Eingabe, die Sie zitiert haben:

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

dh etwas, das als streng typisierter Code bezeichnet wird und das ehrlich gesagt in diesem Zusammenhang, wenn der Leser OOP lernen soll, schrecklich ist.

Dies bedeutet auch, dass, wenn ich mit der Absicht des Buchautors Recht habe (dh die oben aufgeführten abstrakten und geerbten Klassen zu erstellen), dies zu doppeltem und ziemlich unlesbarem und hässlichem Code führt:

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.

Wie für die "Wegweiser":

  • Codeduplizierung

    Ihr Code ist nicht dupliziert. Der Code, den der Autor von Ihnen erwartet, funktioniert.

  • Die Klasse, die zu viel über seinen Kontext wusste

    Ihre Klasse übergibt nur Zeichenfolgen von der Eingabe zur Ausgabe und weiß überhaupt nichts. Der erwartete Code hingegen kann dafür kritisiert werden, dass er zu viel weiß.

  • Der Alleskönner - Klassen, die versuchen, viele Dinge zu tun

    Auch hier übergeben Sie nur Zeichenfolgen, nichts weiter.

  • Bedingte Anweisungen

    Ihr Code enthält eine bedingte Anweisung. Es ist lesbar und leicht zu verstehen.


Vielleicht hat der Autor sogar erwartet, dass Sie einen viel überarbeiteten Code schreiben als den mit sechs Klassen oben. Zum Beispiel könnten Sie das Fabrikmuster für Unterricht und Gebühren usw. verwenden.

+ 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 diesem Fall erhalten Sie neun Klassen, was ein perfektes Beispiel für eine Überarchitektur ist .

Stattdessen haben Sie einen leicht verständlichen, sauberen Code erhalten, der zehnmal kürzer ist.

Arseni Mourzenko
quelle
Wow, deine Vermutungen sind präzise! Schauen Sie sich das Bild an, das ich hochgeladen habe - der Autor hat genau das Gleiche empfohlen.
Aditya MP
Ich würde diese Antwort so gerne akzeptieren, aber ich mag auch die Antwort von @ ian31 :( Oh, was mache ich!
Aditya MP
5

Zunächst können Sie "magische Zahlen" wie 30 und 5 in der Funktion cost () in aussagekräftigere Variablen ändern :)

Und um ehrlich zu sein, denke ich, dass Sie sich darüber keine Sorgen machen sollten. Wenn Sie die Klasse ändern müssen, ändern Sie sie. Versuchen Sie zuerst, Wert zu schaffen, und wechseln Sie dann zur Refactorisierung.

Und warum denkst du, dass du falsch liegst? Ist diese Klasse nicht "gut genug" für dich? Warum machst du dir Sorgen um ein Buch?

Michal Franc
quelle
1
Danke für die Antwort! In der Tat wird das Refactoring später erfolgen. Ich habe diesen Code jedoch zum Lernen geschrieben, um das in dem Buch vorgestellte Problem auf meine eigene Weise zu lösen und um zu sehen, wie ich falsch
liege
2
Aber das wirst du später wissen. Wenn diese Klasse in anderen Teilen des Systems verwendet wird und Sie etwas Neues hinzufügen müssen. Es gibt keine "Silberkugel" -Lösung :) Etwas kann gut oder schlecht sein, es hängt vom System, den Anforderungen usw. ab. Während Sie OOP lernen, müssen Sie viel versagen: P Dann werden Sie mit der Zeit einige häufige Probleme und Muster bemerken. Dieses Beispiel ist zu einfach, um Ratschläge zu geben. Ohh, ich empfehle diesen Beitrag von Joel wirklich :) Architekturastronauten übernehmen joelonsoftware.com/items/2008/05/01.html
Michal Franc
@adityamenon Dann lautet Ihre Antwort "Refactoring wird später kommen". Fügen Sie die anderen Klassen erst hinzu, wenn sie zur Vereinfachung des Codes benötigt werden. In der Regel wird dann der dritte ähnliche Anwendungsfall eingeführt. Siehe Simons Antwort.
Izkata
3

Es ist absolut nicht erforderlich, zusätzliche Klassen zu implementieren. Sie haben ein kleines MAGIC_NUMBERProblem in Ihrer cost()Funktion, aber das war's. Alles andere ist eine massive Überentwicklung. Leider ist es jedoch üblich, dass sehr schlechte Ratschläge erteilt werden. Circle erbt beispielsweise Shape. Es ist definitiv in keiner Weise effizient, eine Klasse für die einfache Vererbung einer Funktion abzuleiten. Sie können stattdessen einen funktionalen Ansatz verwenden, um ihn anzupassen.

DeadMG
quelle
1
Das ist interessant. Können Sie anhand eines Beispiels erklären, wie Sie das machen würden?
Guillaume31
+1, ich stimme zu, kein Grund, ein so kleines Stück Funktionalität zu überentwickeln / zu komplizieren.
Großmeister
@ ian31: Was genau tun?
DeadMG
@DeadMG "Verwenden Sie einen funktionalen Ansatz", "Verwenden Sie eine funktionale Erweiterung anstelle der Vererbung"
guillaume31