Requirements, Contracts, Messages
These are the three things I've emphasized while explaining Object-Oriented Programming.
By now, you should understand that these three aren't independent concepts; they are organically connected. This entire series of articles has just been a demonstration of their natural flow.
I've said that interfaces and polymorphism are just about "swapping parts."
But is swapping parts really that easy? There's a hidden trap. Today, I'm going to talk about that trap and how Dependency Injection (DI) is just the good old object-oriented way of fixing it.
Let's Design Based on What We've Learned
We get the theory, so let's see how it's reflected in code. I'll do exactly what we've been doing in the previous parts.
The Requirement and the Contract: "Hey, come by car."
There are two parts to this contract: you have to come to the meeting spot, and you have to come by car.
So, we design the contract as an interface, like this:
interface Car {
void move();
}
class Friend {
private final Car car;
// We inject the contract that the friend must come by car.
public Friend(Car car) {
this.car = car;
}
public void goToAppointment() {
this.car.move();
}
}
So far, so good. We've designed this naturally. Unlike the bad example from before, we haven't hardcoded a "Red 2023 Ford Mustang GT, license plate 8XN-4Y2" directly into our design. We just naturally included a Car interface in the class, just like how we talk. This is great.
2. Now, Let's Send the Message: "You Figure It Out"
We have to send a message that says, "Just honor the contract and you figure out the rest."
But You Have to Use the Design, Right?
Okay, we've designed our object-oriented system naturally. Why did we design it? To use it, obviously. Let's implement the real-world scenario of me telling my friend to come by car.
public class Me {
public static void main(String[] args) {
// "I" want to get my friend to come to the appointment.
// But... "I" have to personally create the 'car' to give to my friend.
Car aFriendsCar = new MyFordMustang();
// And then I create the friend who has that car.
Friend friend = new Friend(aFriendsCar);
// Only now can I tell my friend to come to the meeting spot.
friend.goToAppointment();
}
}
I said we have to send a message to our friend. Let me emphasize it again. OOP is this:
"As long as you honor the requirements and the contract, you figure out the rest."
In the previous example, we honored the requirements and the contract, and we designed the method—the messenger—well by using an interface. It seems like we should be able to send the "You figure it out" message. But there's a trap in the process of sending this message.
In the code above, for me to send a message to my friend, I have to inject the specific car information myself.
This is the same as the situation where I know my friend's car is a "Red 2023 Ford Mustang GT, license plate 8XN-4Y2."
In other words, this code is exactly the same as me saying, "Hey, come in that Red 2023 Ford Mustang GT, license plate 8XN-4Y2, that you own."
You go through all the trouble of designing a good, "you-figure-it-out" OOP system, only to ruin it all at the very last step when you actually run it. It's the worst-case scenario.
So How Do We Fix It?
What's the object-oriented way to solve a problem like this? Like I've been saying, you send a message: "You figure it out."
In the code above, the situation is messed up because when it was time to create the friend who would perform the action, I injected the specific car information. In other words, it's a situation I messed up by doing the injection myself.
So, what if I don't do the injection? What if I just send a message to "go figure out the injection yourself"?
Then we can just create a 'guy' whose only job is to do the injecting, and give him an order: "Hey, you handle the injection."
We can express it like this:
public class CarFactory {
// This is the response to the request, "I need a car, prepare one."
public Car createCar() {
// Only this guy knows what the friend's car is.
return new MyFordMustang();
}
}
Then, I can send my message like this:
public class Me {
public static void main(String[] args) {
// I call the guy who knows what my friend's car is.
CarFactory carFactory = new CarFactory();
// I delegate all the specific details to that guy.
// "I" no longer know if it's a 'MyFordMustang'. I just know it's a 'Car'.
Car aFriendsCar = carFactory.createCar();
// I don't know what kind of car it is, I just send the message to my friend: "Hey, come by car."
Friend friend = new Friend(aFriendsCar);
// Then my friend figures it out and comes by car.
friend.goToAppointment();
}
}
I solved the trap of knowing my friend's car by using the original object-oriented solution. I created a 'guy' who handles injecting the car info, and I just sent him a message to do his job.
I have perfectly implemented the "Hey, come by car" message, instead of the "Hey, come in your specific Ford Mustang" command.
Conclusion
When people explain DI, they throw around a bunch of complicated words like "Inversion of Control~~" or "reducing coupling~~"...
It's just the same old OOP trick for avoiding the trap. It's no big deal. It's just sticking to the "you figure it out" attitude to the very end.
Top comments (0)