Принцип сухості в передовій практиці?


11

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

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

Проблема, яку я створив для себе, полягає в тому, що я створюю ті самі об’єкти для кількості таблиць, які я маю. Наприклад, це будуть об'єкти, які мені потрібні для обробки таблиці comments.

class Comment extends Model {

    protected $id;
    protected $author;
    protected $text;
    protected $date;
}

class CommentFactory implements iFactory {

    public function createFrom(array $data) {
        return new Comment($data);
    }
}

class CommentGateway implements iGateway {

    protected $db;

    public function __construct(\Database $db) {
        $this->db = $db;
    }

    public function persist($data) {

        if(isset($data['id'])) {
            $sql = 'UPDATE comments SET author = ?, text = ?, date = ? WHERE id = ?';
            $this->db->prepare($sql)->execute($data['author'], $data['text'], $data['date'], $data['id']);
        } else {
            $sql = 'INSERT INTO comments (author, text, date) VALUES (?, ?, ?)';
            $this->db->prepare($sql)->execute($data['author'], $data['text'], $data['date']);
        }
    }

    public function retrieve($id) {

        $sql = 'SELECT * FROM comments WHERE id = ?';
        return $this->db->prepare($sql)->execute($id)->fetch();
    }

    public function delete($id) {

        $sql = 'DELETE FROM comments WHERE id = ?';
        return $this->db->prepare($sql)->execute($id)->fetch();
    }
}

class CommentRepository {

    protected $gateway;
    protected $factory;

    public function __construct(iFactory $f, iGateway $g) {
        $this->gateway = $g;
        $this->factory = $f;
    }

    public function get($id) {

        $data = $this->gateway->retrieve($id);
        return $this->factory->createFrom($data);
    }

    public function add(Comment $comment) {

        $data = $comment->toArray();
        return $this->gateway->persist($data);
    }
}

Тоді виглядає мій контролер

class Comment {

    public function view($id) {

        $gateway = new CommentGateway(Database::connection());
        $factory = new CommentFactory();
        $repo = new CommentRepository($factory, $gateway);

        return Response::view('comment/view', $repo->get($id));
    }
}

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

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

Чи правильно я підходжу до цього, чи є інша перспектива, на яку я повинен дивитися?


Створюючи нову таблицю, чи завжди ви використовуєте один і той же набір SQL запитів (або надзвичайно схожий набір) для взаємодії з нею? Також, чи Фабрика інкапсулює якусь змістовну логіку в реальній програмі?
Іксрек

@Ixrec зазвичай є спеціальні методи в шлюзі та сховищі, які виконують більш складні запити sql, такі як приєднання, проблема полягає в тому, що функції збереження, вилучення та видалення - визначені інтерфейсом - завжди однакові, за винятком назви таблиці та, можливо, але навряд чи стовпчик первинного ключа, тому мені доведеться повторювати їх у кожній реалізації. Завод дуже рідко дотримується будь-якої логіки, а часом я взагалі пропускаю його і змушують шлюз повертати об’єкт замість даних, але я створив завод для цього прикладу, оскільки це має бути належний дизайн?
Еміліо Родрігес

Я, мабуть, не кваліфікований, щоб дати належну відповідь, але у мене склалося враження, що 1) Класи Фабрика та Репозиторій насправді не роблять нічого корисного, тому вам краще відкинути їх на роботу та працювати безпосередньо з коментарями та коментарями. 2) Потрібно мати можливість розміщувати загальні функції зберігання / отримання / видалення в одному місці, а не копіювання їх, можливо, в абстрактний клас "реалізацій за замовчуванням" (на кшталт того, що роблять колекції в Java)
Ixrec

Відповіді:


12

Проблема, яку ви вирішуєте, досить фундаментальна.

Я відчув таку ж проблему, коли працював у компанії, яка зробила велику програму J2EE, яка складалася з декількох сотень веб-сторінок і понад мільйон з половиною рядків коду Java. Цей код використовував ORM (JPA) для збереження.

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

Вашу проблему неможливо вирішити на рівні мови програмування, яку ви використовуєте. Використання шаблонів добре, але, як ви бачите, це спричиняє повторення коду (точніше кажучи: повторення конструкцій).

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

Рішення 1: Використовуйте деякі інші рамки стійкості, які дозволяють заявити лише те, що потрібно зберігати. Напевно, є такі рамки навколо. Проблема такого підходу полягає в тому, що він досить наївний, оскільки не всі ваші зразки будуть пов'язані з наполегливістю. Ви також хочете використовувати шаблони для коду інтерфейсу користувача, тож вам знадобиться GUI-рамка, яка може повторно використовувати представлення даних вибраної стійкості. Якщо ви не можете їх повторно використовувати, вам потрібно буде написати код пластини котла, щоб з'єднати представлення даних рамки GUI та рамки стійкості .. і це знову суперечить принципу DRY.

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

Рішення 3: Автоматизувати повторення коду та дизайн за допомогою певної форми генерації коду. Ви турбуєтеся про необхідність вручну повторювати повторення шаблонів та конструкцій, оскільки ручне кодування повторюваного коду / дизайну порушує принцип DRY. Нині там є дуже потужні рамки генератора коду. Існують навіть "мовні робочі стійки", які дозволяють швидко (півдня, коли у вас немає досвіду) створити власну мову програмування та генерувати будь-який код (PHP / Java / SQL - будь-який текстовий файл, що можна розширювати), використовуючи цю мову. У мене є досвід роботи з XText, але здається, що також MetaEdit і MPS добре. Я настійно раджу перевірити одну з цих мовних робочих груп. Для мене це був найвизвольніший досвід у моєму професійному житті.

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


Дякую за відповідь, я серйозно взяв до уваги генерацію коду і навіть починаю впроваджувати рішення. Вони 4 класу котлів, тому я думаю, що я міг би це зробити в самому PHP. Хоча це не вирішує проблему з повторним кодом, я вважаю, що компроміси цього варті - маючи високорентабельний і легко змінюваний, навіть незважаючи на повторюваний код.
Еміліо Родрігес

Це перший, що я чув про XText, і він виглядає дуже потужно. Дякую, що ви мене про це знаєте!
Меттью Джеймса Бріггса

8

Проблема, з якою ви стикаєтеся, стара: код для стійких об'єктів часто схожий для кожного класу, це просто код котлопластини. Ось чому деякі розумні люди винайшли Object Relational Mappers - вони вирішують саме цю проблему. Перегляньте цю колишню публікацію SO щодо списку ORM для PHP.

Якщо існуючі ORM не страждають від ваших потреб, є також альтернатива: ви можете написати власний генератор коду, який вимагає збереження мета-опису ваших об'єктів і генерує з цього повторювану частину коду. Це насправді не надто важко, я це робив у минулому для різних мов програмування, я впевнений, що також можна буде реалізувати такі речі і в PHP.


Я створив таку функціональність, але переключився з неї на це, тому що раніше я мав об'єкт даних обробляти завдання збереження даних, що не відповідає SRP. Наприклад, я використовував Model::getByPKметод, і у наведеному вище прикладі я міг би це зробити, Comment::getByPKале отримання даних з бази даних та побудова об'єкта все міститься в класі об'єкта даних, що є проблемою, яку я намагаюся вирішити за допомогою моделей дизайну .
Еміліо Родрігес

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

@Jules - це дуже вдалий момент, і мене задумалося, і мені було цікаво - у чому проблема в тому, щоб у моїй програмі були наявні як програми ActiveRecord, так і Data Mapper. Тоді я міг би використовувати кожен з тих, коли мені знадобляться - це вирішить мою проблему переписання того ж коду за допомогою шаблону ActiveRecord, і тоді, коли мені фактично потрібен картограф даних, це не буде таким болем для створення необхідних класів на роботу?
Еміліо Родрігес

1
Єдина проблема, яку я зараз можу побачити, полягає в тому, що опрацювання кращих випадків, коли запит повинен з'єднати дві таблиці, де одна використовує Active Record, а іншу керує ваш Map Mapper - це додасть шар складності, який інакше не буде не виникають. Особисто я просто скористався картографом - я ніколи не любив Active Record з самого початку - але я знаю, що це лише моя думка, а інші не згодні.
Жуль
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.