It's a bit of a long answer, but I will address your example first and then move on to some general tips. Bare in mind all code is pseudo code, it will not compile without syntax adjustments.
First of all, your logical structure does not make sense, which is why it might be hard for you to pinpoint if this is correct or not.
For instance in real world, you do not address a car to buy it, you address a shop or a service that sells them. You only address a car to drive, or use other functionality that it offers. The car does not assign itself a license. Finally, a purchase is a process that is generally linear (and can be expressed by a method, without triggers) if you take a basic sellers/buyer example. So when you call shop.BuyCar( sportsCar )
all of the purchase logic can be called from the buy method.
Class Shop{
public Car BuyCar( carWithoutLicense ){
//Purchase logic
LicenseService.AssignLicense( carWithoutLicense ).
return carWithoutLicense.
}
}
//A person would call this method, no the car
A better example of correctly utilized event would be one of the alert lights on the front panel of a car, because it is there to notify the driver of something he/she might want to react to. For instance: a check engine light.
class Car {
Bool CheckEngingLightIsOn = false;
public void Ride( ... ){
if( enginge.faultDetected == true ){
TurnOnCheckEngineAlert( );
}
}
public void TurnOnCheckEngineAlert( ){
CheckEngingLightIsOn = true;
if( CheckEngineLightSwitch != null ){
CheckEngineLightSwitch.Invoke( ... )
}
}
}
class Driver {
public Driver( Car car ){
this.car = car;
if( driverState != Drunk ){
car.CheckEngineLightSwitch = TakeAction;
}
}
public Drive( ){
car.Ride( );
}
public void TakeAction( Car car, EventArgs e ){
//get out, open the hood, check the engine...
if( car.CheckEngingLightIsOn == true ){ "Light turned on
//Check Engine
}else{
//continue driving
}
}
}
Without getting too deep into abstraction, notice the chain of events:
Driver
drives a car and does not worry about other things (such as that check engine light) until they happen.
- If
Car
detects a fault, it turns on the check engine light and there are event handlers on that event (subscribers) the car triggers the event.
- The event fires, but the driver has to be subscribed to it to notice the change.
- ONLY if the driver is subscribed to that event (in this case, if not drunk) he will take action, based on that event.
This example is fundamentally different to your example, because:
- While driving the car, the driver doesn't need to pay attention to check engine light all the time (even though, he can check it).
- It IS the standard process of a car to check engine state and represent it on the engine light.
- Driver and the Car both affect each others further actions and this logic is inefficient if express linearly.
In other words, Buying process is set in stone (paying, license, goods transfer) and skip essential steps cannot be skipped. The process of a car moving itself is not set in stone, because neither the car nor the driver knows what will happen in journey. I.E. Driver might drive to destination without stopping (if no fault develops or if he doesn't notice the light) or the car might force the driver to stop when he notices the check engine light go on (essentially, they both control each other in a sense).
Some general tips about your use-case
In your example You have made a somewhat complex use-case (with incorrectly placed logic), which it will run, but will be structurally incorrect (and bad logical structure tends to lead to human mistakes when designing further logic).
First thing you should look at is what logic is relevant to each object. Does an event/method in your object represent something that your object does (i.e. functionality that an object performs itself) or something that affects your object but the object itself does not do anything in process? For instance: a car does "riding" on it's own (even if start of this process and all of its parameters, such as speed or direction are controlled by the driver); assigning license to a car happens completely outside of the car structure and the car only get an attribute changed (license) in process.
This distinction is important because only logic performed by your object is relevant to that object, and by extension, any logic that is performed by another object and only affects your object is irrelevant belongs somewhere else. So Buy
definitely does not belong in the car and Ride (the process of moving) belongs to the car.
Secondly, your naming will go a long way to help you understand this topic. Methods represent actions and should be named as such (Shop.BuyCar
, Car.Ride
, Driver.Drive
), events represent reaction triggers ( Car.CheckEngineLightSwitch
) and event handlers represent reactions to an action (a reaction is still an action, so no special naming is needed, but you can name to to make a distinction between an action and a reaction).