11

Consider this example:

I have a website. It allows users to make posts (can be anything) and add tags that describe the post. In the code, I have two classes that represent the post and tags. Lets call these classes Post and Tag.

Post takes care of creating posts, deleting posts, updating posts, etc. Tag takes care of creating tags, deleting tags, updating tags, etc.

There is one operation that is missing. The linking of tags to posts. I am struggling with who should do this operation. It could fit equally well in either class.

On one hand, the Post class could have a function that takes a Tag as a parameter, and then stores it in a list of tags. On the other hand, the Tag class could have a function that takes a Post as a parameter and links the Tag to the Post.

The above is just an example of my problem. I am actually running into this with multiple classes that are all similar. It could fit equally well in both. Short of actually putting the functionality in both classes, what conventions or design styles exist to help me solve this problem. I am assuming there has to be something short of just picking one?

Maybe putting it in both classes is the correct answer?

6 Answers 6

12

Like the Pirate Code, the SRP is more of a guideline than a rule, and it's not even a particularly well-worded one. Most developers have accepted the redefinitions of Martin Fowler (in Refactoring) and Robert Martin (in Clean Code), suggesting that a class should only have one reason to change (as opposed to one responsibility).

It's a good, solid (excuse the pun) guideline, but it's almost as dangerous to get hung up on it as it is to ignore it.

If you can add a Post to a Tag and vice versa, you haven't broken the single-responsibility principle. Both still only have one reason to change -- if the structure of that object changes. Changing the structure of either one does not change the way it is added to the other, so you are not adding a new "responsibility" to it.

Your final decision should really be dictated by the functionality required on the front end. There will likely be a need to add a Tag to a Post at some point, so do something like the following:

// 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
}

If, later, you find a need to add Posts to Tags as well, just add an addPost method to the Tag class and a referenceTag method to the Post class. Obviously, I have named them differently so that you don't accidentally cause a stack overflow by calling addTag from addPost and addPost from addTag.

2
  • I think the relationship between Tag and Post is many-to-many, in which case it makes sense for a Tag to keep references to multiple Posts. How would you handle this if you keep a single reference?
    – Andres F.
    Nov 30, 2011 at 1:57
  • @AndresF.: I agree with you, so I clearly didn't write my answer very well. I have edited significantly. (Apologies to the previous upvoter if this changes the meaning as you saw it.)
    – pdr
    Nov 30, 2011 at 4:19
6

No, not in both! It should be in one place.

What I find uneasing in your question is the fact that you say "Post takes care of creating posts, deleting posts, updating posts" and same for Tag. Well, that is not right. Post can only take care of updating, the same for Tag. Creating and deleting is the work of someone else, external to Post and Tag (let's just call it Store).

Good responsibility for Post is "knows its author, contents and date of last update". Good responsibility for Tag is "knows its name and purpose (read: description)". Good responsibility for Store is "knows all Posts and all Tags and can add, remove and search them".

If you look at these three participants, who is most naturally the one who should have the knowledge of Post-Tag relationship?

(for me, it is the Post, it seems natural that it "knows its tags"; reverse search (all posts for a tag) seems to be the job of the Store; though I may be mistaken)

1
  • If each post has a list of tags, and/or each tag has a list of posts to which include it, one can easily answer the question "does post x include tag y". Is there any way to efficiently answer such a question without either class taking responsibility, other than by using something like a ConditionalWeakTable (assuming one is lucky enough to have a framework where one exists)?
    – supercat
    Dec 23, 2013 at 21:47
3

Theres an important detail missing from the equation. Why does tag contain post and visa-versa? The answer to this question determines the solution for each given set.

In general I can think of a situation that is similar. A box and contents. A box has contents so a has-a relationship is appropriate. Can contents have a box? Sure, a box with a box. A box is contents. But IS-A is not a good design for all boxes. In such a case I would consider decorator pattern. This way a box is decorated with contents at runtime as necessary.

Tags can have Posts too, but to me this is not a static relationship. Rather it could be a report of all posts having said tag . In this case its a new entity, not has-a.

2

While in theory things like that can go either way, in practice when you get down to the implementation one way is almost always a better fit than the other. My hunch is it will fit better in the Post class because the association will be created during post creation or editing, when other things about the post are changing at the same time.

Also, if you are associating multiple tags and want to do it in one database update, you'd need to build some sort of list of all the tags associated with the same post before doing the update. That list fits a lot better in the Post class.

1

Personally, I wouldn't add that functionality to either of them.

To me, both Post and Tag are data objects, so shouldn't be handling database functionality. They should simply exist. They're meant to hold data, and be used by other parts of your application.

Instead, I'd have another class that is responsible for your business logic and data pertaining to your web page. If your page is displaying a post and allowing users to add tags, then the class would have a Post object, and contain functionality to add Tags to that Post. If your page is displaying tags and allowing users to add posts to those tags, it would contain a Tag object and have functionality to add Posts to that Tag.

That's just me though. If you feel that you must handle database functionality in your data objects, then I'd recommend pdr's answer

0

As I read this question the first thing that came to mind was a Many To Many database relationship. Posts can have many Tags... Tags can have many Posts.... It appears to me that both classes need the ability to manage this relationship to some extent.

From the Post point of view...
If your editing or creating a Post a secondary activity becomes managing Tag relationships.

  1. Add Existing Tag to Post
  2. Revove Tag from Post

IMO, the creation of a completely new TAG does not belong here.

From the Tag point of view...
You can create a Tag without having to assign it to a Post. The only activity I see that involves interacting with a Post is a Delete Tag function. However, this function should be an independent stand alone function.

This will only work if there is a database linking table that resolves the Many to Many relationship

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.