Чому мій клас гірший за ієрархію класів у книзі (початківець ООП)?


11

Я читаю об'єкти, шаблони та практику PHP . Автор намагається моделювати урок у коледжі. Мета - вивести тип уроку (лекція чи семінар), а також оплата за урок залежно від того, чи це уроки щогодини або з фіксованою ціною. Тож вихід повинен бути

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

коли вхід такий:

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

Я написав це:

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

Але згідно з книгою, я помиляюся (я майже впевнений, що і я). Натомість автор подав велику ієрархію класів як рішення. У попередній главі автор вказав такі "чотири покажчики" як час, коли я повинен розглянути можливість зміни своєї класової структури:

Єдина проблема, яку я можу бачити, - це умовні твердження, і це теж у невизначеному вигляді - то чому це рефактор? Які проблеми, на вашу думку, можуть виникнути в майбутньому, які я не передбачив?

Оновлення : я забув згадати - це структура класу, яку автор запропонував як рішення - шаблон стратегії :

Стратегія


29
Не вивчайте об’єктно-орієнтоване програмування з книги PHP.
giorgiosironi

4
@giorgiosironi Я відмовився від оцінки мов. Я використовую PHP щодня, і тому для мене найшвидше дізнатися нові поняття. Завжди є люди, які ненавидять мову (Java: sliesha.re/91C4pX ) - правда, важче знайти ненависників Java, ніж PHP-дизери, але все ж. Можливо, можуть виникнути проблеми з ОО PHP, але на даний момент я не можу витратити час на вивчення синтаксису Java, "шляху Java" та OOP. Крім того, настільне програмування мені чуже. Я повноцінна людина в Інтернеті. Але перш ніж ви зможете потрапити на JSP, вам нібито потрібно знати настільну Java (Без цитат, я помиляюся?)
Aditya MP

Використовуйте константи для "фіксованої ставки" / "погодинної ставки" та "семінару" / "лекції" замість рядків.
Том Мартенал

Відповіді:


19

Смішно, що в книзі це не викладено чітко, але причина, чому вона надає перевагу ієрархії класів, якщо висловлення всередині одного класу, ймовірно, є принципом відкритого / закритого типу. Це широко відоме правило розробки програмного забезпечення говорить, що клас слід закрити модифікація, але відкрита для розширення.

У вашому прикладі додавання нового типу уроку означало б зміну вихідного коду класу уроку, зробивши його крихким та схильним до регресії. Якщо у вас був базовий клас та одна похідна для кожного типу уроку, вам слід просто додати ще один підклас, який, як правило, вважається чистішим.

Ви можете помістити це правило в розділ "Умовні виписки", якщо хочете, проте я вважаю, що "покажчик" трохи розпливчастий. Якщо твердження, як правило, просто кодує запахи, симптоми. Вони можуть бути результатом найрізноманітніших поганих дизайнерських рішень.


3
Було б набагато кращою ідеєю скористатися функціональним розширенням, а не успадкуванням.
DeadMG

6
Звичайно, існують інші / кращі підходи до проблеми. Тим НЕ менше, ясно , що це книга про об'єктно-орієнтованому програмуванні і відкритому / закритому принципі просто вразив мене , як у класичному способі наближення такого роду дизайн дилеми в ГО.
guillaume31

Як щодо найкращого способу підійти до проблеми ? Немає сенсу навчати неповноцінному рішенню лише тому, що це ОО.
DeadMG

16

Я здогадуюсь, що те, що книга спрямована на навчання - це уникати таких речей, як:

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

Моє тлумачення книги полягає в тому, що ви повинні мати три класи:

  • Анотація уроку
  • Щогодинний урок
  • FixedRateLesson

Потім слід повторно реалізувати costфункцію на обох підкласах.

Моя особиста думка

для простих випадків: Дивно, що ваша книга надає перевагу більш глибокій ієрархії класів, щоб уникнути простих умовних тверджень. Більше того, з вашими вхідними специфікаціями Lessonповинен бути завод з відправленням, що є умовною заявою. Тож із рішенням книги ви матимете:

  • 3 класи замість 1
  • 1 рівень спадкування замість 0
  • однакова кількість умовних висловлювань

Це більше складність без додаткової вигоди.


8
У цьому випадку так, це занадто велика складність. Але в більш широкій системі ви будете додавати більше уроків, наприклад SemesterLesson, MasterOneWeekLessonі т. Д. Абстрактний корінний клас, а ще краще інтерфейс - це безумовно шлях. Але коли у вас є лише два випадки, це залежить від розсуду автора.
Майкл К

5
Я згоден з вами загалом. Однак навчальні приклади часто надумані і надмірно архітектурні, щоб проілюструвати точку. На хвилину ви ігноруєте інші міркування, щоб не плутати проблему, і вводите ці міркування пізніше.
Карл Білефельдт

+1 Приємна ретельна поломка. У коментарі "Більше складність без додаткової вигоди" ідеально ілюструється концепція "можливої ​​вартості" в програмуванні. Теорія! = Практичність.
Еван Плейс

7

Приклад у книзі досить дивний. З вашого запитання оригінальне твердження:

Мета - вивести тип уроку (лекція чи семінар) та збори за урок залежно від того, чи це уроки щогодини або з фіксованою ціною.

що в контексті глави про ООП означало б, що автор, ймовірно, хоче, щоб ви мали таку структуру:

+ abstract Lesson
    - Lecture inherits Lesson
    - Seminar inherits Lesson

+ abstract Charge
    - HourlyCharge inherits Charge
    - FixedCharge inherits Charge

Але потім йде вхід, який ви цитували:

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

тобто щось, що називається строго набраним кодом, і, якщо чесно, в цьому контексті, коли читач має намір вивчити OOP, жахливо.

Це також означає, що якщо я маю рацію щодо наміру автора книги (тобто створення тих абстрактних та успадкованих класів, які я перераховував вище), це призведе до дублювання та досить нечитабельного та некрасивого коду:

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.

Щодо "покажчиків":

  • Копіювання коду

    Ваш код не має дублювання. Код, який очікує, що ви пишете автор.

  • Клас, який занадто багато знав про свій контекст

    Ваш клас просто передає рядки від входу до виводу, і нічого не знає взагалі. З іншого боку, очікуваний код можна критикувати за те, що він занадто багато знає.

  • The Jack of All Trades - класи, які намагаються зробити багато речей

    Знову ж таки, ви просто передаєте струни, нічого більше.

  • Умовні заяви

    У вашому коді є одна умовна заява. Це читабельно і легко зрозуміти.


Можливо, насправді автор очікував, що ви напишете набагато більше реконструйований код, ніж той, який має шість класів вище. Наприклад, ви можете використовувати заводський зразок для уроків та зборів тощо.

+ 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`.

У цьому випадку ви закінчите дев’ять класів, які стануть прекрасним прикладом над архітектури .

Натомість ви закінчилися легким для розуміння чистим кодом, який удесятеро коротший.


Вау, ваші здогадки точні! Подивіться на зображення, яке я завантажив - автор рекомендував саме такі речі.
Адітя МП

Мені б дуже хотілося прийняти цю відповідь, але мені також подобається відповідь @ ian31 :( О, що мені робити!
депутат Aditya

5

Перш за все, ви можете змінити "магічні числа" на зразок 30 і 5 у вартості () функції на більш значущі змінні :)

І якщо чесно кажучи, я вважаю, що не варто з цього приводу хвилюватися. Коли вам потрібно буде змінити клас, то ви його зміните. Спробуйте спочатку створити значення, а потім перейдіть до рефакторизації.

І чому ви думаєте, що помиляєтесь? Чи не для вас цей клас "достатньо хороший"? Чому ти переживаєш за якусь книгу;)


1
Дякуємо за відповідь! Дійсно, рефакторинг настане пізніше. Однак я написав цей код для навчання, намагаючись вирішити проблему, представлену в книзі, по-своєму, бачачи, як я помиляюся ...
Адіта МП

2
Але про це ви дізнаєтесь пізніше. Коли цей клас буде використовуватися в інших частинах системи, вам доведеться додати щось нове. Немає рішення «срібна куля» :) Щось може бути добре чи погано, це залежить від системи, вимог тощо. Під час навчання OOP вам доведеться багато відмовити: P Тоді з часом ви помітите деякі поширені проблеми та закономірності. Цей приклад занадто простий, щоб дати кілька порад. О, я дійсно рекомендую це повідомлення від Джоела :) Космонавти архітектури взяли на себе joelonsoftware.com/items/2008/05/01.html
Міхал Франк

@adityamenon Тоді ваша відповідь "рефакторинг настане пізніше". Додайте інші класи лише після того, як вони знадобляться для спрощення коду - зазвичай саме тоді починається третій подібний випадок використання. Дивіться відповідь Саймона.
Ізката

3

Для впровадження будь-яких додаткових занять абсолютно немає необхідності. У вас є певна MAGIC_NUMBERпроблема у вашій cost()функції, але це все. Все інше - це масивна надмірна інженерія. Однак, на жаль, часто даються дуже погані поради - наприклад, коло успадковує форму. Однозначно не є ефективним жодним чином виводити клас для простого успадкування однієї функції. Ви можете використовувати функціональний підхід, щоб налаштувати його замість цього.


1
Це цікаво. Чи можете ви пояснити, як би ви зробили це на прикладі?
guillaume31

+1, я погоджуюсь, немає причин перевищувати / ускладнювати такий невеликий функціонал.
GrandmasterB

@ ian31: Що робити конкретно?
DeadMG

@DeadMG "використовувати функціональний підхід", "використовувати функціональне розширення, а не спадщину"
guillaume31
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.