3

I am currently building a small project that is supposed to keep track of recipes. Each recipe has a bunch of information, including a rating for said recipe. Since the rating has some additional logic i put it in it's own class. Now i am thinking about how to call a rating vote on my recipe object:

public class Recipe {
    private Rating rating = new Rating();

    public Rating getRating() {
        return this.rating;
    }

    public void submitVote(int score) {
        this.rating.vote(score);
    }
}

public class Rating {
    private int numberVotes;
    private int totalScore;

    public double getAverage() {
        return totalScore / (double) numberVotes;
    }

    public void vote(int score) {
        // Assure that score is within defined boundaries, for example 0-5
        totalScore += score;
        numberVotes++;
    }
}

public class Main {
    public static void main(String[] args) {
        Recipe r = new Recipe();
        // Option 1
        r.getRating().vote(5);
        // Option 2
        r.submitVote(5);
   }
}

Which of these is preferable?

10

Your option 2 is usually preferred. Option 1 violates the common guideline known as the "Law of Demeter" -- it means that your main program now knows in detail how your Recipe voting works, which makes it harder to change that if you decide to do it another way later. This is an example of unnecessary coupling, which we usually try to minimise.

  • So it's worth to introduce a facade for some objects in my aggregate, even if it's just delegating the same call? – Luca Fülbier Jul 23 '16 at 11:00
  • 4
    Yes, because by doing so you make it easier to change the implementation later. – Jules Jul 23 '16 at 11:02
2

In addition to @Jules very good answer:

Perhaps you are thinking about the decision making at too low a level. Remember, you are trying to provide a useful and complete abstraction to clients/users/callers. So, one way to frame the question is, do you want clients/callers to have an API that is about:

  1. recipes that have some relationship with a popularity and votes entity.
  2. recipes that have popularity and can be voted on

I think that option (2) provides a simple, complete, and relevant abstraction to the client/user/caller; whereas option 1 begs questions, such as what is the (cardinality of) the relationship between recipe and ratings (1:1 or 1:N or M:N), how do I navigate from a rating to recipe(s)?


P.S. You haven't demonstrated or explained why you need a ratings object as a separate entity. In fact, in your example, you're showing an exact 1:1 relationship between recipes and ratings. Perhaps this is an artifact of condensing the example for question asking purposes; however, just to be clear, you have another option of not using two classes at all. Some might argue that you'd be violating single responsibility (SRP); so then you be back at two classes where one delegates to the other. On the other hand there is a tension with YAGNI, in having two classes now, just in case only one of them needs to change in the future; this tension in part because it would be easy to refactor into two classes at such time as necessary rather than upfront now.

  • The Ratings class actually allows for multiple votes, tracking the lowest and highest vote and a method for calculating the average score. I might add different rating systems and rating comments in the future aswell, so that would be a good place to encapsulate these. – Luca Fülbier Jul 23 '16 at 15:25
  • Very good, I was wondering if something like that might be the case. – Erik Eidt Jul 23 '16 at 15:32

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.