Is is better to think of methods as active (X does Y to Z) or passive (Y is done to Z by X)?

A place to discuss the implementation and style of computer programs.

Moderators: phlip, Moderators General, Prelates

User avatar
Euphonium
Posts: 162
Joined: Thu Nov 12, 2009 11:17 pm UTC
Location: in ur bourgeois bosses' union, agitating ur workers

Is is better to think of methods as active (X does Y to Z) or passive (Y is done to Z by X)?

Postby Euphonium » Tue Oct 02, 2018 10:23 pm UTC

As a long-term personal project to get comfortable with C++ and MFC programming for Windows, I'm working on something similar to Football Manager, but for American Football, except with all the things about league structure that I like about European association football leagues (promotion and relegation, player loans, championships based on record rather than a playoff, etc.). No visualization of games or anything like that--eventually you'll call plays and get a written description of what happens.

So I have a player object.

There are two ways to implement, e.g., Player.Tackle(). Either a tackle can be something that a player does to another player, so you get Player.Tackle(&Tacklee), or it's something that happens to a player, so you get Player.Tackle(&Tackler).

What might be the pros and cons of hitchhikingone vs. the other? Is one or the other more idiomatically OO?

User avatar
Pfhorrest
Posts: 5008
Joined: Fri Oct 30, 2009 6:11 am UTC
Contact:

Re: Is is better to think of methods as active (X does Y to Z) or passive (Y is done to Z by X)?

Postby Pfhorrest » Tue Oct 02, 2018 10:51 pm UTC

I'm so barely a programmer that I don't even deserve the "Software Engineer" on my resume, but when first learning OOP, I asked about something like this imagining that there is a "ball" object that I want to "kick", and someone explained to me that instead of "I" the overarching program doing kick(ball) "myself", the OOP way was more to have the ball know "how to be kicked" (what to do when kicked), and then order that ball.bekicked. If there is something else in the universe of discourse that is doing the kicking, that thing will .kick(ball), and part of that .kick method will involve invoking the ball.bekicked method.

So I guess in your example, the tackler .tackles(tacklee), but if tacklee has to do something in response to being tackled, .tackle(tacklee) should invoke some tacklee.betackled method?

Someone else will probably set us both straight on this though.
Forrest Cameranesi, Geek of All Trades
"I am Sam. Sam I am. I do not like trolls, flames, or spam."
The Codex Quaerendae (my philosophy) - The Chronicles of Quelouva (my fiction)

Tub
Posts: 410
Joined: Wed Jul 27, 2011 3:13 pm UTC

Re: Is is better to think of methods as active (X does Y to Z) or passive (Y is done to Z by X)?

Postby Tub » Wed Oct 03, 2018 1:28 pm UTC

Disclaimer: good design is more of an art than a science, and anything that's not a technical requirement is highly subjective. Strive for consistency.

First of all, Player::tackle(Player &) should cause the 'this' player to tackle the player passed via parameter, never the other way around. If you want to put it in passive, then you should name it passive as well, like Player::beTackled(Player &) or something.
Parameter names are not part of the method signature, and they don't show up in your code when you call the method (it's just varA->tackle(varB)), so you need to be clear in the method name about who does what to whom.

In most cases, the method's location is decided by the need for private access. Player::serialize(Packet &) does something to the packet, but requires private access to the player, so that's where it goes. Player::updateFromNetwork(Packet &) does something to the player, but again the need for private access puts it onto the Player object.

Of course, private access is not a concern when both objects belong to the same class, so we're talking about an edge case here.

You can split it into two methods (as Pfhorrest suggested), but I would only do that if you can meaningfully call one of the methods without calling the other. That doesn't seem to be the case here, unless you allow tackling the referee, or being tackled by a fan.
Splitting also causes concerns about preconditions. You'll want to check if both players are close enough to each other before you allow the tackling. So you need to duplicate that check in both methods. That makes it easy to introduce bugs during future changes, and you've also introduced a weird edge case where the check can pass in one method, then fail in the other, due to some circumstances that you haven't thought about (like compiler-induced floating point inaccuracies, or because one of the methods modifies the player's position).

You can always use a static method Player::tackle(Player &tackler, Player &tacklee), to make it explicit that you couldn't decide where to put it this is a tightly tangled interaction between two equals.

If in doubt, I'd use active.

User avatar
Flumble
Yes Man
Posts: 2082
Joined: Sun Aug 05, 2012 9:35 pm UTC

Re: Is is better to think of methods as active (X does Y to Z) or passive (Y is done to Z by X)?

Postby Flumble » Wed Oct 03, 2018 7:25 pm UTC

If it has no visualisations, at what level do you simulate a game? Do you have like a simulation loop with 24 objects moving around on a football field or do you generate a list of game events (tackles, touchdowns, maybe passes) with some probability? What are the effects of a tackle?


Since I agree with the gist of both Pfhorrest and Tub, I'll just comment on some details.
Tub wrote:Of course, private access is not a concern when both objects belong to the same class, so we're talking about an edge case here.

I'd say that, even if the language allows objects of the same class to touch each other's privates, they should call a method on each other instead. If the tackled player has some internal state that says they're on the ground for some time, they should set that status themselves via a .betackled method. And the .betackled method can (must) then be called by the .tackle'ing tackler, like Pfhorrest suggested.
And I guess the referee may be watching too with a .checkLegalTackle if a tackle can be foul play (I don't know the rules of sportsball), which the tackler should invoke too.

Tub wrote:Splitting also causes concerns about preconditions.

I guess that's a tradeoff: if you make the players responsible for their own state, you can put clear constraints on what a method does to a player, while having very loose constraints on what can happen on the field. If a player's state can be changed from the outside, you may not have any guarantees on what the state of a player is at any time, while having proper constraints on what the game looks like. Or maybe I'm generalizing too much and one/both approaches do allow you to write preconditions and postconditions on the whole simulation.

Tub wrote:You can always use a static method Player::tackle(Player &tackler, Player &tacklee), to make it explicit that you couldn't decide where to put it this is a tightly tangled interaction between two equals.

To push this further: if both players are just victims of a game that generates a tackle event, you can have a .performTackle "game" method that takes both a tackler and a tacklee. Inside it, you can either call .tackle, .betackled and .checkLegalTackle, or strip away the OOP and set the player states directly.
Anyway, whichever approach is in line with the rest of the program, is probably the best one. "Strive for consistency."

Tub
Posts: 410
Joined: Wed Jul 27, 2011 3:13 pm UTC

Re: Is is better to think of methods as active (X does Y to Z) or passive (Y is done to Z by X)?

Postby Tub » Thu Oct 04, 2018 4:10 pm UTC

One more point I wish to make. We've talked about the low-level details, but we also need to consider the proper top-down approach. This is based on a few year of experience developing games, and inheriting projects from other people developing games.

MVC is a good thing. Even if you're just running a simulation without a view, it's good to separate the model and the controller.

Your model contains values like a current match, an array of players and their positions, a referee, a ball etc, each with a reasonably abstracted bunch of setters and getters to change those values.

Your controller is a set of atomic actions or events (like "Match starts" or "Player A tackles Player B") that transform the model from one valid state into another.
(If that sound familiar to you, then you've heard of ACID, where a database transaction transforms the database from one consistent state into another. Unsurprisingly, implementing the controller as a set of atomic actions works equally well if your model lives in a database.)

If you stick to that architecture, then your Player class is just the model. And the controller lives somewhere else, and it has a function (or method or class) with a signature like ActionTackle(Match &match, Player &tackler, Player &tacklee), and that function does everything related to the action - do all precondition checks, update both players, figure out if the tackle was legal at that point in the game and if the ref saw anything, possibly handle penalties, injuries, figure out if other players or the audience start a riot.. it can get complicated.

Now, within ActionTackle, how do you update the players in response to the tackle?
  1. Do you update the players directly, using reasonable abstractions like setProneState(), sustainInjury(), receivePenalty(), ...?
  2. Do you call tackler.tackle(&tacklee) and have it update both player objects?
  3. Do you call tackler.tackle(&tacklee, &referee) and tacklee.beTackled(&tackler, &referee), letting the model determine the chance of penalty and injury?
  4. Do you call tackler.tackle(bool penalty) and tacklee.beTackled(bool injured), determining the outcomes in the controller, but letting the model handle it?
  5. Do you abstract everything away, implementing Player::canTackle(), Player::canBeTackled(), Player::prepareToTackle(), Player::prepareToBeTackled(), Player::tackle(bool), Player::beTackled(bool), Player::postTackleCleanup(), Player::postBeTackledCleanup()?

So the point I'm going to make is this: the model has no business knowing what a "tackle" is, because that's an action, and that's the controller's job. It certainly has no business determining the chance of penalties or injuries. In the list above, I've always gone for number 1.

As a rule of thumb, if your model has a method that's only called from a single place in your code, that's always suspicious. Is it too specific? Can it be split into reusable parts? Can it be generalized to be more useful? Should it be inlined into the controller?
As another rule of thumb, if a model object's method receives another model object as a parameter, that's also suspicious. If there's an interaction between both objects, let the controller figure out the results of that interaction, and pass the results.

As usual, exceptions apply.


Return to “Coding”

Who is online

Users browsing this forum: No registered users and 8 guests