I'm not going to criticize your design because I see that it's not the point. Indeed coins shouldn't know about vending machines, and the related arguments, are valid. Let's assume that these classes are just dummies like Foo and Bar.
The crux of your question seems to be in these code comments:
coin->Insert(vending_machine); // which function goes first? this one?
vending_machine->InsertCoin(coin); // or this one?
At the highest level of abstraction, there should only be one function call here. There is a single operation. The verb is insert
, and the nouns are vending machine
and coin
.
There are two main schools of thought in the world of OOP about this: one which is mainstream (C++'s object system being in this category), and one that is a little off the beaten path.
Mainstream OOP is primarily about creating service abstractions. Specifically, in such a way that the service abstractions can be implemented in multiple different ways, but used the same way. Under this view, in a given object collaboration scenario, we identify which object or objects are the service providers. In this situation, that is probably the vending machine. So what we want is just this:
vending_machine->InsertCoin(coin);
The user of these two objects should ideally not be required to initiate any more steps in order to get the vending machine and the coin to interact.
Now, it may be a fact in the implementation that the coin needs to know that it has been inserted into the vending machine. That aspect has been criticized in some of the other answers and comments. Those criticisms are valid when they are literally about vending machines and coins. In general, there are situations where one object is added to another (that one being the main service abstraction) and yet has to know.
Here is where the non-mainstream OOP makes a valuable contribution. There are object-oriented systems that have multiple dispatch. Instead of methods, there are generic functions. Generic functions are specialized into numerous methods which are dispatched on the run-time type of all of the arguments. Under multiple dispatch OOP, we have this:
insert(machine, coin);
insert
is a generic function. The generic function looks at the type of the machine
object and the coin
object, and then selects a method which best matches the combination. That method then contains logic that is specific to both the type of the machine and the type of coin; it does whatever is necessary without the concern of "which class has this responsibility".
Back under the mainstream OOP, when the responsibility cannot be entirely pinned to one class, we introduce some object collaboration protocols to make up for this.
The collaboration for the insert
operation might start with:
vending_machine->InsertCoin(coin);
We make coin
itself contain a little service. Anything receiving the coin must follow the protocol of announcing itself to the coin. Thus the vending machine's InsertCoin
method does this:
void VendingMachine::InsertCoin(CoinBase *coin)
{
// notify coin of insertion
coin->Inserted(this);
// ..
}
This is almost like a multiple dispatch. First we dispatch the method InsertCoin
based on the run-time type of the vending machine. Then, a second dispatch takes place to complete the operation by doing the coin-side handling: a specific method is chosen based on the type of coin.
Side note: thanks to C++ overloading, the base class CoinBase
can have multiple overloads for different kinds of vending machines, which are (statically) resolved by the this
argument in the Inserted
call.
One of the most useful documentation/design methods for this kind of programming is the sequence diagram:
main machine coin
|---- Insert(coin) --> |
|
|---Inserted(machine)->|
|
If you're confused, try drawing sequence diagrams.
These diagrams capture very specific scenarios, often related to use cases; no single sequence diagram gives you the complete picture.
For instance, we would have a dedicated sequence diagram for some use case of inserting a coin into the machine, and successfully obtaining a product, as well as change.
vending_machine->AcceptCoin(coin)
(and no Insert method on coin; still, the machine could interrogate the coin for its value). 1/1