СУХОЙ принцип в хороших практиках?

11

Я стараюсь следовать принципу СУХОЙ в своем программировании изо всех сил. Недавно я изучал шаблоны проектирования в ООП и закончил тем, что повторял себя довольно много.

Я создал шаблон Repository вместе с шаблонами 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
@Ixrec, как правило, в шлюзе и репозитории будут настраиваемые методы, которые выполняют более сложные SQL-запросы, такие как объединения, проблема в том, что функции persist, retrieve и delete - определенные интерфейсом - всегда одинаковы, за исключением имени таблицы и, возможно, но вряд ли столбец первичного ключа, поэтому я должен повторять это в каждой реализации. Фабрика очень редко содержит какую-либо логику, и иногда я вообще ее пропускаю и заставляю шлюз возвращать объект вместо данных, но я создал фабрику для этого примера, так как это должен быть правильный дизайн?
Эмилио Родригес
Я, вероятно, не обладаю достаточной квалификацией, чтобы дать правильный ответ, но у меня складывается впечатление, что 1) классы Factory и Repository на самом деле не делают ничего полезного, так что вам лучше отказаться от них и работать напрямую только с Comment и CommentGateway. 2) Должна быть возможность поместить общие функции persist / retrieve / delete в одном месте, а не копировать их, возможно, в абстрактном классе «реализаций по умолчанию» (вроде как то, что делают Коллекции в Java)
Ixrec

Ответы:

12

Проблема, которую вы решаете, является фундаментальной.

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

Эта проблема усугубляется, когда вы используете сторонние технологии на каждом уровне архитектуры, и все технологии требуют собственного представления данных.

Ваша проблема не может быть решена на уровне используемого вами языка программирования. Использование шаблонов - это хорошо, но, как вы видите, это вызывает повторение кода (точнее говоря, повторение проектов).

На мой взгляд, есть только 3 возможных решения. На практике эти решения сводятся к одному и тому же.

Решение 1. Используйте некоторую другую среду хранения, которая позволяет вам указывать только то, что должно быть сохранено. Вероятно, есть такая основа вокруг. Проблема этого подхода в том, что он довольно наивен, потому что не все ваши шаблоны будут связаны с постоянством. Вы также захотите использовать шаблоны для кода пользовательского интерфейса, поэтому вам понадобится среда графического интерфейса, которая может повторно использовать представления данных выбранной вами среды персистентности. Если вы не можете использовать их повторно, вам нужно будет написать код базовой платы, чтобы связать представления данных каркаса GUI и каркаса персистентности ... и это опять противоречит принципу DRY.

Решение 2. Используйте другой - более мощный - язык программирования, имеющий конструкции, которые позволяют вам выражать повторяющийся дизайн, чтобы вы могли повторно использовать код проекта. Вероятно, это не вариант для вас, но предположим, что это на мгновение. С другой стороны, когда вы начнете создавать пользовательский интерфейс поверх слоя постоянства, вы захотите, чтобы язык снова был достаточно мощным, чтобы поддерживать создание графического интерфейса без необходимости написания кода. Маловероятно, что существует достаточно мощный язык, чтобы делать то, что вы хотите, так как большинство языков полагаются на сторонние фреймворки для построения GUI, каждый из которых требует своего собственного представления данных для работы.

Решение 3. Автоматизируйте повторение кода и дизайна, используя некоторую форму генерации кода. Вы беспокоитесь о необходимости ручного кодирования повторений образцов и конструкций, поскольку повторяющийся код / ​​дизайн вручную нарушает принцип СУХОГО. В настоящее время существуют очень мощные структуры генератора кода. Есть даже «языковые рабочие места», которые позволяют вам быстро (полдня, когда у вас нет опыта) создавать свой собственный язык программирования и генерировать любой код (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 не обязательно помещать логику персистентности в объект модели. Это шаблон Active Record, и, хотя он популярен, есть альтернативы. Посмотрите, какие ORM доступны, и вы должны найти тот, у которого нет этой проблемы.
Жюль
@Jules, это очень хорошая мысль, это заставило меня задуматься, и мне стало интересно - в чем проблема с доступностью реализаций ActiveRecord и Data Mapper в моем приложении. Тогда я мог бы использовать каждый из них, когда мне это нужно - это решит мою проблему переписывания того же кода с использованием шаблона ActiveRecord, а затем, когда мне действительно понадобится маппер данных, было бы не так сложно создавать необходимые классы. для работы?
Эмилио Родригес
1
Единственная проблема, с которой я могу сейчас столкнуться, заключается в том, что при разработке крайних случаев, когда запрос должен объединить две таблицы, где одна использует Active Record, а другая управляется вашим Data Mapper - это добавило бы уровень сложности, который в противном случае не возникает. Лично я бы просто использовал картограф - мне никогда не нравилась Active Record с самого начала - но я знаю, что это только мое мнение, а другие не согласны.
Жюль