1

Тема: Як влаштовано наслідування у ваших фреймворках?

Поки що маю грунтовний досвід використання лише KohanaFramework.

В ньому працює наступна система наслідування.
Системні класи:

<?php
class First {
  // Певний код
}

class Second extends First {
  // Цей клас повністю пустий
}

class Third extends Second {
  // Певний код
}

class Fourth extends Third {
  // Цей клас повністю пустий
}

class Fifth extends Fourth {
  // Певний код
}

Тобто KohanaFramework абсолютно між кожним системним класом вставляє пустий клас, і наступні класи посилаються саме на пустий клас.

І потім, якщо в application нам потрібно розширити, наприклад, class First, то достатньо розширити повністю пустий клас Second. І цей клас буде впливати на все дерево класів, бо жоден системний клас не посилається на клас First. Всі класи, яким потрібен код класа First, посилаються на клас Second.

При такій схемі можна перевизначити майже будь-який системний клас фреймворка. Мінус в цій схемі: навіть якщо жоден системний клас в application не перевизначається, витрачаються додаткові ресурси на відкриття пустих класів. Тобто якщо в робочому потоці, по-суті, бере участь 20 класів, то разом з пустими класами, кількість відкритих файлів буде дорівнювати 20*2 = 40.

А тепер мені цікаво почути яка схема наслідувань в інших відомих вам фреймворках. Чи існує в них можливість так само легко перевизначати "class Second", без подальшого переписування схеми наслідувань системних класів?

2

Re: Як влаштовано наслідування у ваших фреймворках?

Тут потрібно задати таке питання - а наскільки часто взагалі потрібно перевизначати системні класи фреймворку? Мені взагалі здається, що таке перевизначення є прямим порушенням Open/Closed principle, а такі речі у більшості випадків створюють більше проблем, ніж вирішуть.

Такий підхід - це чисто Kohana-way. Більше я ніде такого не бачив. В Yii та Laravel є hooks, тобто методи на подобу beforeSave, afterSave, які за замовчуванням пусті, але їх можна перевизначити для зміни поведінки того чи іншого методу.

3

Re: Як влаштовано наслідування у ваших фреймворках?

TwiStar написав:

Тут потрібно задати таке питання - а наскільки часто взагалі потрібно перевизначати системні класи фреймворку? Мені взагалі здається, що таке перевизначення є прямим порушенням Open/Closed principle, а такі речі у більшості випадків створюють більше проблем, ніж вирішуть.

Такий підхід - це чисто Kohana-way. Більше я ніде такого не бачив. В Yii та Laravel є hooks, тобто методи на подобу beforeSave, afterSave, які за замовчуванням пусті, але їх можна перевизначити для зміни поведінки того чи іншого методу.

Тут треба відповісти: на стільки часто перевизначаються системні класи, на скільки в цьому є потреба.

Я особисто не підраховував, але точно використовував не один раз. Не знаю як вам, TwiStar, але для мене це очевидна зручність архітектури фреймворка. Звичайно, можна 100% покладатись на готові класи і використовувати лише API, але, як на мене, це більше "користувальницько-готовий" підхід, ніж підхід повноправного програміста.

4

Re: Як влаштовано наслідування у ваших фреймворках?

Юзайте DI (dependency injection) і буде вам щастя
Я думаю це самий хороший спосіб для фреймворків)

5 Востаннє редагувалося TwiStar (22.01.2015 16:05:42)

Re: Як влаштовано наслідування у ваших фреймворках?

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

Програмування починається там, де ви реалізовуєте особливості саме вашого проекту, але вони не стосуються фреймворку, в жодному разі, навпаки, ви використовуєте фреймворк щоб не розв'язувати кожен раз давно розв'язані задачі, на зразок авторизації користувачів або роутингу, бо це безглузде марнування часу.

Щодо того, про що ви кажете, Dependency Injection розв'язує такі задачі більш ефективно, аніж наслідування.

Подякували: funivan, quez2

6

Re: Як влаштовано наслідування у ваших фреймворках?

TwiStar, все написане вами має сенс, але не протиставте це тому, що я кажу. Якщо фреймворк хороший навіть при використанні лише готових класів, то чому в заслугу фреймворку не можна записати ще й можливість легко переписувати системні класи?

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

Що до DI, то про вирішення якої проблеми йде мова?

7

Re: Як влаштовано наслідування у ваших фреймворках?

Для мене у 95% переписування системних класів фреймворку - це антипаттерн та поганий стиль кодування. Тому коли фреймворк замість того, щоб змушувати розробника писати хороший код, тобто використовувати Dependency Inversion (класи залежать не один від одного а від інтерфейсів) та DI Container, дає розробнику в руки костиль з великою вірогідністю все зламати (бо у Kohana компоненти не наслідують інтерфейси і я часто бачив як при такому оверрайді порушували контракт, що призводило до появи помилок) - це не є заслугою, це, як кажуть зарубіжні коллеги - that's why we can't have nice things.

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

Саме тому я питав про приклади та наскільки велика потреба у переписуванні того чи іншого класу. Я впевнений, що у кожному наведеному прикладі проблему можна розв'язати і не чіпаючи клас фреймворку, а створивши, наприклад, Декоратор.

8

Re: Як влаштовано наслідування у ваших фреймворках?

Оскільки ніколи не було та не буде ідеальних фреймворків, то відповідно - завжди буде потреба щось підправити під себе в системних класах. Причому коли я пишу "системні класи", то маю на увазі, в тому числі, і класи модулів фреймворка.

У Kohana класи наслідують інтерфейси, з чого ви взяли, що не наслідують? І що це за абстрактне "порушення контракту"?

9

Re: Як влаштовано наслідування у ваших фреймворках?

І що це за абстрактне "порушення контракту"?

це коли у вас є клас

class A{

}

class B {
 protected function getUser(){

 }
}

ви лізете у клас А і можете випадково поламати метод getUser

Тобто у хороших фреймоврках (системах) роблять так:
1. інтерфейси
2. DI

якщо вам треба поміняти логіку компоненту ви створюєте свій клас, реалізуєте логіку інтерфейсу (відповідно він буде відповідати вимогам) який йому потрібен
і прикріплюєте його через DI

останнім часом такій підхід крутий так як ви не переписуєте компоненти фреймоврка ви можете свої модулі і логіку вписати через свої класи безпечно (не ламаючи нічого у самому фреймоврку)

Це моє бачення:)

10 Востаннє редагувалося ktretyak (01.02.2015 18:03:35)

Re: Як влаштовано наслідування у ваших фреймворках?

Іншими словами - ваше бачення полягає в тому, що не правильно зробили розробники PHP допускаючи наслідування класів та перевизначення поведінки батьківських класів. Так?

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

Для чого взагалі потрібні Dependency Injection?
Перше: вони потрібні для так званого "лінивого" завантаження класів, завдяки чому можна не робити ініціалізацію цих класів "наперед", а лише по мірі потреби їх роботи.
Друге: підхід з використанням DI дозволяє проводити краще тестування певних класів, які використовують об'єкти інших класів в своїй роботі.

Тому я і задав питання "які саме проблеми можна вирішити за допомогою DI?", бо я не бачу як можна стикувати те що я зараз написав стосовно призначення DI та інтерфейсів, з перевизначенням системних класів фреймворка та класів модулів.

11

Re: Як влаштовано наслідування у ваших фреймворках?

Можливо я не так пояснив але я не вважаю що наслідування погана штука.
Про Di ви вказали все вірно але скоріш за все це не перший і другий пункт. Перший пункт це IoC https://uk.wikipedia.org/wiki/%D0%86%D0 … 0%BD%D1%8F

Фактично ви вірно зауважили за інтерфейси але не обов’язково що б методи працювали із іншими класами а у більш глобальному сенсі: що б модулі взаємодіяли між собою по контракту.

Якщо Ви хочете використовувати DI вам необхідні інтерфейси. Якщо Ви вважаєте що можете використовувати DI без інтерфейсів - наведіть приклад коду і я постараюсь вам доказати що інтерфейси необхідні)
Якщо ви не знаєте як застосувати DI а краще використовувати принцип KohanaFramework постараюсь вам навести зрзумілий приклад по Di і інтерфейсах)  (Я не стараюсь доказати що хтось крутий а хтось ні, просто мені здається що підхід кохани ну дуже не вдалий)

12

Re: Як влаштовано наслідування у ваших фреймворках?

У Kohana класи наслідують інтерфейси, з чого ви взяли, що не наслідують?

http://prntscr.com/60hsk6 Ось чому я вважаю, що у Kohana немає наслідування інтерфейсів.

Порушення контракту - це коли у процесі переписування методу при наслідуванні змінюємо сигнатуру методу, або тип значень, яке буде повернуте функцією, або виключення, які можуть бути викинуті цим методом.
Звичайно, інтерфейси не можуть запобігти всьому, але, наприклад, при використанні PHPDoс-комментарів та нормальної IDE, я можу одразу бачити, які у метода параметри, що він мені поверне, та які виключення він може викинути, наприклад:

     interface TestInterface
    {
        /**
         * @param Connection $connection
         * @param array $params
         * @throws MethodNotFoundException
         * @return mixed
         */
        function test(\Connection $connection, array $params);
    }

    class Test implements TestInterface
    {

        /**
         * @param Connection $connection
         * @param array $params
         * @throws MethodNotFoundException
         * @return mixed
         */
        function test(\Connection $connection, array $params)
        {
            // TODO: Implement test() method.
        }
    }

Цей код був створений за допомогою ПХПШторму, і коментарі я писав тільки для інтерфейсу, у класі метод з коментарями був згенерований автоматично.

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

Ще трохи про наслідування L у SOLID це Liskov Substitution Principle, який я власне і описав вище, коли говорив про порушення контракту. Більш розгорнуто про це є дуже гарна стаття на SitePoint - http://www.sitepoint.com/liskov-substitution-principle/

13

Re: Як влаштовано наслідування у ваших фреймворках?

TwiStar написав:

Ось чому я вважаю, що у Kohana немає наслідування інтерфейсів.

По-вашому 18 впроваджень інтерфейсів це занадто мало для фреймворка?

TwiStar написав:

Порушення контракту - це коли у процесі переписування методу при наслідуванні змінюємо сигнатуру методу, або тип значень, яке буде повернуте функцією, або виключення, які можуть бути викинуті цим методом.

Що саме ви вкладаєте поняття "змінюємо сигнатуру методу"? Якщо ви маєте на увазі, що не можна змінювати внутрішньої поведінки методу, то... а як же інкапсуляція? Це теж погано?

Стосовно типу даних, що повертає ця функція, то мій безкоштовний NetBeans 8, не менш турботливо підказує мені те, що записано в PHPdoc-коментарях, коли я намагаюсь передати параметри задокументованої функції. За ради підказки в IDE - впроваджувати інтерфейси - це реально "сильно" =)

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

14

Re: Як влаштовано наслідування у ваших фреймворках?

Сигнатура - це не внутрішня поведінка метода.

15

Re: Як влаштовано наслідування у ваших фреймворках?

Так, 18 впроваджень інтерфейсу - це замало.

У Silex, для порівняння - 188, у Symfony - більше 1000.

Я не сказав, що впроваджую інтерфейси задля підказок у IDE, а просто коментував ( :) ) яким чином я для себе вирішую проблеми збереження контракту коли мені потрібно замінити будь-який функціонал.

Про DI-контейнери і навіщо вони потрібні. Чесно кажучи, я дуже рідко стикався з необхідністю замінювати залежності "на лету", і ніколи - у PHP.

Найкраща частина - це коли DI-контейнер вміе у автоматичну підстановку залежностей. Тобто, щось на зразок:

class Foo
{
    /**
     * @var BarInterface
     */
    private $bar;
    /**
     * @var BazInterface
     */
    private $baz;

    function __construct(
        BarInterface $bar,
        BazInterface $baz
    ) {

        $this->bar = $bar;
        $this->baz = $baz;
    }
}

$container = new \Di\Container;
$container->singleton('\BarInterface', '\Bar');
$container->singleton('\BazInterface', '\Baz');

$foo = $container->get('\Foo');

І якщо потім потрібно замінити \Bar на \BarAdapter, ми змінюємо одну строку і все, більше нічого робити не треба. Ще краще, коли наш Di контейнер може керуватися окремим конфігураційним файлом.

Звичайно, так, як зроблено у Kohana, взагалі нічого змінювати не потрібно, просто переписав метод і готово. Але Di дає набагато більшу гнучкість у тих випадках, коли потрібно змінити залежніть не у всіх класів, а лише у обраних. Найкраще це зроблено у Laravel 5.0, там можна писати так -

          $this->app->when('App\Handlers\Commands\CreateOrderHandler')
          ->needs('App\Contracts\EventPusher')
          ->give('App\Services\PubNubEventPusher');

З нетерпінням чекаю Laravel 5.0, анонсували на наступному тижні реліз.