I've been trying to learn OOP techniques and design patterns for a little while and my approach has certainly improved, but I don't quite click when we're talking about objects of different classes interacting. I mostly work in PHP but I'd accept a general answer for the sake of learning. And apologies in advance if I use terms wrong, I'm self-taught here. Here's an example:
Suppose I want to model people moving around by car. There are 3 classes:
class Car {}
class Driver {}
class Road {}
and I want the Car class to have a function
public function Drive($time) {}
whereby driving should update the location of the driver, dependent on the speed limit of the road. My question is what's the best way to structure Drive so that Car interacts with the other objects. I can see 3 possibilities off the top of my head:
public function Drive($time, $Driver, $Road) {}
The advantage here is that the function itself tells me what arguments I need. The drawback is I can end up with lots of arguments passing around if I have a lot of similar actions, call private functions along the way, add passenger objects, etc.
$Car->setDriver($Driver);
$Car->setRoad($Road);
$Car->Drive($time);
The advantage is that I can call Drive very simply and it has access to whatever it needs. The drawback is that I have to remember to set the driver and road first because the function definition doesn't tell me.
class Car() {
public function Drive($time) {
$Driver = getDriver($this->DriverID);
$Road = getRoad($this->RoadID);
}
}
function getDriver($DriverID) {
static $DriverArray;
// check isset(), create object and place in array if not
return $DriverArray[$DriverID];
}
function getRoad($RoadID) {} // assume similar
The advantage is that all the logic is internal, with DriverID and RoadID existing because they're foreign keys from the db. (Or they can be manually set beforehand, but that wouldn't be a memory issue because it'd be an intentional assignment.) The drawback is that all Driver operations would have to go through the same getDriver lest I end up with separate instances of the same Driver.
Just for completeness my fourth approach would be SELECT <columns> FROM Car_tbl INNER JOIN Driver_tbl on DriverID INNER JOIN Road_tbl on RoadID
, which I'm pretty sure is bad OO design but is what I used to do because it performs fewer queries.
I'm leaning toward approach 3 but suspect that I'm missing something on a large level. Feel free to answer PHP specific or just how this is generally considered. Thanks.
edit: Appreciate answers so far, including links to further reading.
Rather than overcomplicate the example, let's assume simplest possible UseCase now but most complex possible UseCase later. So e.g. Speed is right now function of getRoadSpeedLimit()
, but later is limited by getMaxCarSpeed()
and getMaxDriverSpeed()
and the car's need for gas and the driver's need for food. And the road's speed limit adjusts if we have too many Driver objects using the same road, etc.
What Gordon and UmlCat both seem to be driving at (so to speak) is using an encapsulating class, is that right?
class RoadTrip_aka_Universe {
// Operates on instances of Car, Driver, Route (which may contain roads)
}
But doesn't that still leave the question, are $Car, $Driver and $Route added in the constructor, applied by setters, pulled in from static externals? Whichever class we decide should have the methods, how does it get access to those other objects?
Also my gut reaction is that if everything just goes in a larger class, doesn't the inside of that class start to resemble a big classless structure with global variables? Don't take that as criticism, I'm just trying to understand because it feels like the letter of OOP but not the spirit of it.
Again, thanks.
Definition. A design class represents an abstraction of one or several classes in the system's implementation; exactly what it corresponds to depends on the implementation language. For example, in an object-oriented language such as C++, a class can correspond to a plain class.
The duration for a Bachelor of Fashion Design course is 3 - 4 years. The duration of a BSc Fashion Designing is the same and is spread over 6 semesters. The students with a BSc in Fashion Designing can expand to multiple fronts of jobs like accessory designing, footwear designing, jewelry as well as textiles.
First, altought, seems unnecesary, you may add a single class that contains all other objects, like "universe" or "world". This, will help you model the real world, into an application.
class CarClass
{
} // class CarClass
class DriverClass
{
} // class DriverClass
class RoadClass
{
} // class RoadClass
class WorldClass
{
/* public CarClass */ $Car;
/* public DriverClass */ $Driver;
/* public RoadClass */ $Road;
} // class WorldClass
And, an small code example. Could be something, like these:
/* void */ function Example()
{
WorldClass $MyWorld = new WorldClass();
// ...
} // void function Example
Next. What are each objects "fields" , "operations", "members" ? You mention, that "Speed Limit" depends on the road.
class RoadClass
{
/* public int */ $SpeedLimit;
} // class RoadClass
/* void */ function Example()
{
WorldClass $MyWorld = new WorldClass();
$MyWorld->Road->SpeedLimit = "50" // ouch, "cow country"
// ...
} // void function Example
How objects interacts. Well, here there is a problem. Sometimes objects depend on others, like been part of others, some times, objects are independent, they just related.
In this example, most objects depends of "World" object, but, they are only related to each other. Lets add a small comment to check that.
class WorldClass
{
/* public depends CarClass */ $Car;
/* public depends DriverClass */ $Driver;
/* public depends RoadClass */ $Road;
function __construct() {
// parent::__construct();
// "World" "contains" these objects
$this->Car = new CarClass();
$this->Driver = new DriverClass();
$this->Road = new RoadClass();
} // function __construct()
} // class WorldClass
The Car has a register machine that records how much distance has move, if starts, moves, and stops. Its a simple value, its not an object.
class CarClass
{
/* public int */ $Distance;
} // class RoadClass
But, there is an interaction with "outer" objects:
class CarClass
{
/* public int */ $Distance;
/* public DriverClass related */ $Driver;
/* public RoadClass related */ $Road;
function __construct() {
// parent::__construct();
// "Car" only relates these objects
$this->Driver = null;
$this->Road = null;
} // function __construct()
} // class RoadClass
/* void */ function Example()
{
WorldClass $MyWorld = new WorldClass();
$MyWorld->Road->SpeedLimit = "50"; // ouch, "cow country"
$MyWorld->Car->Road = $MyWorld->Road; // a reference only
$MyWorld->Car->Driver = $MyWorld->Driver; // a reference only
// ...
} // void function Example
Since the distance of the car depends on the road speed limit, and changes, when the car drives then:
class CarClass
{
/* public int */ $Distance;
/* public DriverClass related */ $Driver;
/* public RoadClass related */ $Road;
function __construct() {
// parent::__construct();
// "Car" only relates these objects
$this->Driver = null;
$this->Road = null;
} // function __construct()
/* void */ function Drive($time) {
// general idea of formula:
$this->distance = ($this->SpeedLimit * $time * $this->Road->SpeedLimit);
} // void function Drive(...)
} // class RoadClass
/* void */ function Example()
{
WorldClass $MyWorld = new WorldClass();
MyWorld->Road->SpeedLimit = "50"; // ouch, "cow country"
$MyWorld->Car->Road = $MyWorld->Road; // a reference only
$MyWorld->Car->Driver = $MyWorld->Driver; // a reference only
/* int */ $time = 60; // 1 hours in minutes
$MyWorld->Car->Drive($time);
} // void function Example
Cheers.
You usually put the method onto the class that has the most information to fulfill a certain task. So in your case, ask yourself which class that is. Also, you should try to keep dependencies between classes small and manageable, so you can achieve low coupling and high cohesion.
See GRASP (object-oriented_design)
As for your specific UseCase, I think you should first take a step back from writing concrete code. Instead try to put what you want to achieve into plain english first. What you said so far is:
If you think about this, you could end up with an (incomplete) UseCase like this:
And this should already tell you, that having Driver, Car and Road is insufficient to transform this UseCase into code. As you can see there is something like a Location
as well. However, there is no information whatsoever about what Location is. Is it GPSCoordindates
? StreetAdresses
? Or merely DistanceTraveled
?
We can also argue that you do not need the Driver
at all, because it is the main user of the application and all actions it takes are made through the UI anyway. And if so, maybe there should be another Domain concept: Roadtrip
. And that collects the used Car
, and the Road
or rather the Route
. And that can hold the StartingLocation
and many Roads
, each having a possible SpeedLimit
.
What the UseCase doesnt say is whether the Driver will always drive at maximum allowed speed or whether the Car has a fuel tank that can run out of gas, and so on. Right now, Car doesnt provide anything to the Roadtrip. Do we really need it? So before diving into the code, make sure you understand the Problem Domain you are modelling.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With