5

For example, imagine a website which stores results about a certain sport or game, which has a typical "season" structure, such that there's both a Player and a Season class. To retrieve a player's average score during a specific season, should this be done with $player->averageScore($season), or should it be done with $season->playerAverageScore($player)? Why?

  • related (possibly a duplicate): What is the real responsibility of a class? – gnat Mar 30 '16 at 16:26
  • 2
    "a typical 'season' structure" That's going to be specific to your nation and your favourite sport, neither of which you named. Perhaps you could elaborate? – Lightness Races in Orbit Mar 30 '16 at 16:32
  • 1
    My rule of thumb is if it can legitimately go either way then you are missing a class that ties them together or you named/defined your classes very poorly. In regards to your example, I think you are missing a class to tie them together. – Dunk Mar 30 '16 at 22:13
  • 1
    You are assuming these are mutually exclusive. Why?. But the same "game - season - total stats" (or whatever) class (aka data structure) better be involved at the bottom of it. – radarbob Apr 1 '16 at 1:28
9

This almost feels like you need a PlayerStatistics class to calculate stats for a single player. You might also benefit from a SeasonStatistics class to calculate stats for an entire season. Off the top of my head, statistics are often kept for the following things:

  • Player
  • Game
  • Team
  • Division
  • Game Series (Baseball in the U.S.A. has a "series" which is two teams playing multiple games in a row)
  • Time period (say the last 3 years for a particular team, or player)

This effectively decouples these calculations from the entities themselves (Player, Season), and gives you lots of additional options for calculating all kinds of statistics at various levels.

I think Dunk's comment on the question sums this up best, and gives a very good guideline for making these decisions in the future:

... if it can legitimately go either way then you are missing a class that ties them together...

You can't get more succinct than that.

  • 1
    not "almost", but definitely, – radarbob Apr 1 '16 at 1:30
3

Why not a SeasonScoreCalculator class with an averageScore method taking both the season and the player?

The point being, if you can't decide which side of the fence a method should fall, then that could be a sign that 1) your classes are doing too much and 2) you're missing a concept that would allow you to simplify.

2

These types of issues are generally solved with a "who needs to know whom" analysis.

It seems to me that a player ought to know what a season is, but a season should not know what a player is. Therefore, I would definitely take the player->averageScore(season) approach.

You can use this kind of reasoning in any scenario, not just the one at hand.

0

The rule of thumb is to look at the underlying data model how points are represented. You don't state how points are actually represented in your model so nobody can answer for your particular case, but if we imagine a model where a "point" is an entity which have a reference to a "player" and to a "game" (which in turn have a reference to a "season"), then you would naturally have a Points class which would allow you to aggregate point over players and seasons.

0

The rule to learn and apply is:-

"An action on a real-world object, becomes a method of that object in object-oriented code"

An object in oriented-code knows itself how to do things. So, a player knows its scores, and has methods to reflect that.

By contrast in procedural code, a procedure asks something else (can be an object, another procedure, data etc.) for information, so the procedure can determine how and if to act.

  • 1
    This advice was common in the 1980's but has been completely abandoned. In this case there are no "real-world" objects. Season is certainly not an object, and neither is Player in the context of this question. – kevin cline Apr 12 '16 at 7:53
0

Most likely the method belongs in the Season and not the Player class, but it could go either way. The most important thing is to prevent circular dependencies. If Season calls any methods in Player, then Player should not call any methods in Season. And vice versa. You get to pick which one depends on the other. If you already have methods of Season that are calling methods of Player then you have no choice -- no method of Player can call a method of Season.

0

If this is really for statistics purposes, as Greg Burghardt implied, I think you could explicitly employ the read-models, popularized in DDD. It is a manifestation of CQRS concept. The point is that you don't need objects, used on your command side, implementing some business-logic, for querying data. So in your case you could have a separate repository, like the following:

class StatisticsRepository
{
    public function getByPlayerAndSeason(PlayerId $playerId, SeasonId $seasonId)
    {
        // db query
        return new Statistics(
            // here it goes raw data
        );
    }
}

You could have raw data returned as a simple array, or you could have a read-model objects, that have nothing to do with your rich domain objects used on your command side.

There are couple of profits:

  • Simplicity. You don't have to adhere your bahavioral objects to the myriad of queries. Otherwise your command-site will plunge in querying logic.
  • Hence, more probably than not, performance. You're dealing with raw sql, not ORM.
0

It should probably be on some class that you haven't mentioned as getAverageScore(season, player). Likely, season and player are members of the class already so you might be able to get away with simply getAverageScore()...

This is a very old question in OOP because the paradigm prioritizes one object over the others in an interaction. After all, only one object can be to the left of the method name while all the others must be to the right of it. (Should the OO cow un-milk itself or should the OO milk de-cow itself?)

The first question to ask is, which object must change state as a result of the collaboration? That object's class likely the one that should house the method.

Now you are probably thinking that no state is changing in your example, but that is probably wrong. Some piece of code needs the average score because that piece of code is planning on doing something with the information and so that likely is where the method should reside.

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.