良好实践中的DRY原则?


11

我试图在编程中尽可能地遵循DRY原则。最近,我一直在学习OOP中的设计模式,最后又重复了一遍。

我创建了一个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查询(如连接),问题是接口定义的持久,检索和删除功能总是相同的,除了表名,可能还有但是主键列不太可能,因此我必须在每个实现中都重复这些。工厂很少保留任何逻辑,有时我完全跳过它,让网关返回对象而不是数据,但是我为这个示例创建了工厂,因为它应该是正确的设计?
Emilio Rodrigues

我可能没有资格给出正确的答案,但是我给人的印象是1)Factory和Repository类实际上并没有做任何有用的事情,因此最好放弃它们并直接使用Comment和CommentGateway进行操作。 2)应该有可能将公用的持久/检索/删除函数放在一个地方,而不是将它们复制粘贴到一个地方,也许放在“默认实现”的抽象类中(有点像Java中的Collections所做的那样)
Ixrec

Answers:


12

您要解决的问题是非常根本的。

当我在一家制造大型J2EE应用程序的公司工作时遇到了同样的问题,该应用程序由数百个网页和一百万行半的Java代码组成。该代码使用ORM(JPA)进行持久化。

当您在体系结构的每一层中使用第三方技术,并且这些技术都需要使用自己的数据表示形式时,此问题会变得更加严重。

您所使用的编程语言无法解决您的问题。使用模式是好的,但是如您所见,它会导致代码重复(或更准确地说,是设计重复)。

我认为只有3种可能的解决方案。实际上,这些解决方案归结为相同的解决方案。

解决方案1:使用其他一些持久性框架,该框架允许您仅声明必须保留的内容。大概有一个这样的框架。这种方法的问题在于它过于幼稚,因为并非所有模式都与持久性相关。您还需要对用户界面代码使用模式,因此您需要一个GUI框架,该框架可以重用您选择的持久性框架的数据表示形式。如果您不能重复使用它们,则需要编写样板代码来桥接GUI框架和持久性框架的数据表示。这又违背了DRY原理。

解决方案2:使用另一种功能更强大的编程语言,该语言具有允许您表达重复设计的结构,以便您可以重新使用设计代码。这可能不是您的选择,但是暂时可以。再一次,当您开始在持久层之上创建用户界面时,您将希望该语言再次强大到足以支持创建GUI而不必编写样板代码。由于大多数语言都依赖于第三方框架来进行GUI构建,因此每种语言都需要使用自己的数据表示才能工作,因此不可能有一种功能强大的语言来执行您想要的操作。

解决方案3:使用某种形式的代码生成自动化代码重复和设计。由于手工编码重复代码/设计违反了DRY原理,因此您担心必须手工编码样式和设计的重复。如今,有非常强大的代码生成器框架。甚至还有“语言工作台”,使您可以快速(无经验的半天)创建自己的编程语言,并使用该语言生成任何代码(PHP / Java / SQL-任何可想到的文本文件)。我有使用XText的经验,但是MetaEdit和MPS似乎也不错。我强烈建议您检查其中一种语言工作台。对我来说,这是我职业生涯中最自由的经历。

使用Xtext,可以让您的机器生成重复代码。Xtext甚至为您生成语法高亮编辑器,并为您自己的语言规范提供代码完成功能。从那时起,您只需参加网关和工厂类,然后在它们上打孔就可以将它们变成代码模板。您将它们提供给生成器(由您的语言解析器调用,生成器也完全由Xtext生成),生成器将填补模板中的空缺。结果是生成的代码。从那时起,您可以在任何地方重复执行任何代码(GUI代码持久性代码等)。


感谢您的答复,我已经认真考虑了代码生成,甚至开始实施解决方案。它们是4个样板类,所以我想我可以在PHP本身中做到这一点。虽然这不能解决重复代码的问题,但我认为折衷是值得的-即使重复代码也具有高度可维护性和易于更改的优点。
Emilio Rodrigues 2015年

这是我第一次听说XText,它看起来非常强大。谢谢您让我意识到这一点!
马修·詹姆斯·布里格斯

8

您面临的问题是一个古老的问题:持久性对象的代码通常在每个类中看起来都相似,只是简单的样板代码。这就是为什么一些聪明的人发明了对象关系映射器的原因 -他们正是解决了这个问题。有关PHP的ORM列表,请参见此前SO帖子

当现有的ORM不能满足您的需求时,还有另一种选择:您可以编写自己的代码生成器,该代码生成器对对象进行元描述以持久保存并从中生成代码的重复部分。这实际上并不难,我过去曾针对某些不同的编程语言进行过此操作,我相信也可以在PHP中实现此类操作。


我已经创建了这样的功能,但是我从此切换到了此功能,因为我曾经让数据对象处理不符合SRP的数据持久性任务。例如,我曾经有过Model::getByPK方法,在上面的示例中,我可以做,Comment::getByPK但是从数据库中获取数据并构造对象都包含在数据对象类中,这就是我试图使用设计模式解决的问题。
Emilio Rodrigues

ORM不必将持久性逻辑放在模型对象中。这是Active Record模式,虽然很流行,但也有其他选择。看一下可用的ORM,您应该找到一个没有此问题的ORM。
2015年

@Jules非常好,这让我思考,我在想-在我的应用程序中同时具有ActiveRecord和Data Mapper实现会是什么问题。然后我可以在需要时使用它们中的每一个-这将解决我通过使用ActiveRecord模式重写相同代码的问题,然后当我实际上需要数据映射器时,创建必要的类就不会那么麻烦为了工作?
Emilio Rodrigues 2015年

1
我现在可以看到的唯一问题是,当一个查询需要联接两个表(其中一个使用Active Record而另一个由Data Mapper管理)时,得出一些极端的情况-这会增加一层复杂性,否则不会出现。就个人而言,我只是使用映射器-从一开始我就从不喜欢Active Record-但是我知道那只是我的观点,其他人则表示不同意。
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.