与单一责任原则作斗争


11

考虑以下示例:

我有一个网站。它允许用户发布帖子(可以是任何内容)并添加描述帖子的标签。在代码中,我有两个代表帖子和标签的类。让我们称这些类PostTag

Post负责创建帖子,删除帖子,更新帖子等。 Tag负责创建标签,删除标签,更新标签等。

缺少一项操作。标签到帖子的链接。我正在努力与谁应该执行此操作。它在任何一个类别中都同样适用。

一方面,Post该类可以具有一个将a Tag作为参数的函数,然后将其存储在标签列表中。另一方面,Tag该类可以具有将a Post作为参数并将链接Tag到的函数Post

以上只是我的问题的一个示例。实际上,我遇到了多个相似的类。两者都可以很好地融合在一起。除了实际上在两个类中都包含功能之外,还存在哪些约定或设计风格可以帮助我解决此问题。我假设必须要挑选一个呢?

也许将它们放在两个类中都是正确的答案?

Answers:


11

像《海盗守则》一样,SRP不仅仅是一条准则,而是一条准则,而且措辞也不是特别好。大多数开发人员已经接受了Martin Fowler(在Refactoring中)和Robert Martin(在Clean Code中)的重新定义,这表明类应该只具有一个改变的理由(而不是一种责任)。

这是一个很好的,牢固的(对双关语表示敬意的)准则,但是挂掉它几乎和忽略它一样危险。

如果你可以添加一个张贴标签反之亦然,你没有破坏单责任原则。两者仍然只有一个更改的理由-如果该对象的结构发生更改。更改任何一个的结构都不会更改将其添加到另一个结构的方式,因此您不会在其中添加新的“责任”。

您的最终决定实际上应该由前端所需的功能决定。在某个时候可能需要将标签添加到帖子中,因此请执行以下操作:

// C-style-language pseudo-code
class Post {
    string _title;
    string _content;
    Date _date;
    List<Tag> _tags;

    Post(string title, string content) {
        _title = title;
        _content = content;
        _date = Now;
        _tags = new List<Tag>();
    }

    Tag[] getTags() {
        return _tags.toArray();
    }

    void addTag(Tag tag) {
        if (_tags.contains(tag)) {
            throw "Cannot add tag twice";
        }

        _tags.Add(tag);
        tag.referencePost(this);
    }

    // more stuff here, obviously
}

class Tag {
    string _name;
    List<Post> _posts;

    Tag(string name) {
        _name = name;
    }

    Post[] getPosts() {
        return _posts.toArray();
    }

    void referencePost(Post post) {
        if (!post.getTags().contains(this) || _posts.contains(post)) {
            throw "Only reference a post by calling Post.addTag()";
        }

        _posts.Add(post);
    }

    // more stuff here too
}

以后,如果您还需要将Posts添加到Tag,只需将addPost方法添加到Tag类,并将referenceTag方法添加到Post类。显然,我用不同的方式命名了它们,以便您不会通过从addPost调用addTag和从addTag调用addPost意外导致堆栈溢出。


我认为Tag和Post之间的关系是多对多的,在这种情况下,让Tag保留对多个Post的引用是有意义的。如果您保留一个参考,您将如何处理?
安德烈斯·F

@AndresF .:我同意你的观点,所以我显然写得不太好。我进行了重大修改。(如果这改变了您所看到的意思,请向先前的作者表示歉意。)
pdr

6

不,不是两者兼而有之!它应该放在一个地方。

我对您的问题感到不安的是,您说“ Post负责创建帖子,删除帖子,更新帖子”,并且与相同Tag。好吧,那是不对的。Post只能处理更新,与相同Tag。创建和删除是别人在Post和之外的工作Tag(我们称之为Store)。

良好的责任Post是“知道其作者,内容和最新更新日期”。良好的责任Tag是“知道其名称和目的(阅读:描述)”。良好的责任Store是“知道所有帖子和所有标签,并可以添加,删除和搜索它们”。

如果您查看这三个参与者,那么最自然的人应该是拥有标记后关系知识的人?

(对我来说,这是“帖子”,它“知道其标签”似乎很自然;反向搜索(所有带有标签的帖子)似乎是商店的工作;尽管我可能会误会)


如果每个帖子都有一个标签列表,和/或每个标签都有一个包含其列表的帖子列表,则可以轻松地回答“帖子x是否包含标签y”的问题。除了通过使用类似的东西ConditionalWeakTable(假设一个人很幸运能够拥有一个存在的框架)之外,是否有任何方法可以在没有任何一个班级负责的情况下有效地回答这一问题?
2013年

3

等式中缺少重要的细节。为什么标签包含邮政信息,反之亦然?这个问题的答案决定了每个给定集合的解决方案。

总的来说,我可以想到类似的情况。一个盒子和内容。盒子里有东西,所以有一个关系是合适的。物品可以有一个盒子吗?当然,一盒一盒。箱子是内容。但是,IS-A并非适用于所有包装盒。在这种情况下,我将考虑装饰器模式。这样,可以在运行时根据需要用内容装饰盒子。

标签也可以有帖子,但是对我来说这不是静态关系。相反,它可能是所有带有标记的帖子的报告。在这种情况下,它是一个新实体,没有-a。


2

虽然从理论上讲,这样的事情可以双向执行,但实际上,当您着手实施时,一种方法几乎总是比另一种更好。我的直觉是,它将更好地适合该Post班级,因为该关联将在帖子创建或编辑期间创建,而与此同时其他有关帖子的内容也会发生变化。

另外,如果您要关联多个标签并希望在一个数据库更新中进行操作,则在进行更新之前,您需要构建与同一帖子关联的所有标签的某种列表。该列表适合Post全班同学。


1

就个人而言,我不会将该功能添加到其中任何一个中。

对我而言,PostTag都是数据对象,因此不应处理数据库功能。它们应该简单地存在。它们旨在保存数据,并由应用程序的其他部分使用。

取而代之的是,我有另一个类负责与您的网页有关的业务逻辑和数据。如果你的页面上显示了岗位,并允许用户添加标签,那么类将有一个Post对象,并包含功能添加TagsPost。如果您的页面正在显示标签,并允许用户向这些标签添加帖子,则该页面将包含一个Tag对象并具有添加Posts到的功能Tag

不过那只是我。如果您觉得必须处理数据对象中的数据库功能,那么我建议使用pdr的答案


0

当我阅读此问题时,首先想到的是多对多数据库关系。帖子可以有很多标签...标签可以有很多帖子...。在我看来,两个课程都需要在某种程度上管理这种关系的能力。

从发布的角度看...
如果您编辑或创建发布,则辅助活动变为管理标签关系。

  1. 将现有标签添加到发布
  2. 从帖子中删除标签

IMO,创建一个全新的TAG并不在这里。

从标签的角度看...
您可以创建标签,而不必将其分配给帖子。我看到的唯一涉及与帖子交互的活动是“ 删除标签”功能。但是,此功能应该是独立的独立功能。

仅当存在解决多对多关系的数据库链接表时,这才有效

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.