In my opinion, this is a very broad question, and the answer depends entirely on the use case. Let me show you some simple examples:
Your first suggestion:
abstract class Vehicle { String name = "SuperDriver"; int numberOfWheels = 4; int maxSpeed = 20; public function drive(Terrain terrain) { echo this.name." drives with ".(this.maxSpeed - terrain->modifier)." mph in ".terrain->name; } } class Car extends Vehicle { public function __construct() { this.name="Ferrari"; this.numberOfWheels = 4; this.maxSpeed = 310; } } class Bicycle extends Vehicle { public function __construct() { this.name="Mountain Bike"; this.numberOfWheels = 2; this.maxSpeed = 150; } }
Benefits:
You can even create an instance of Vehicle if you don't declare it abstract, and it can even control it.
Disadvantages:
- An unsafe vehicle doesn't make much sense
- Anyone who extends a class should know that all properties must be overwritten.
- hard to expand and hard to maintain
- maybe others ...
Your second suggestion:
abstract class Vehicle { public function drive(Terrain terrain) { echo this.name." drives with ".(this.maxSpeed - terrain->modifier)." mph in ".terrain->name; } } class Car extends Vehicle { this.name="Ferrari"; this.numberOfWheels = 4; this.maxSpeed = 310; } class Bicycle extends Vehicle { this.name="Mountain Bike"; this.numberOfWheels = 2; this.maxSpeed = 150; }
I do not see any advantage. If the parent class needs these properties, you must definitely make them necessary. If the parent class does not need any of these properties, then this is the territory of the child class, which you should not care about.
Your third suggestion:
abstract class Vehicle { protected function drive(Terrain terrain, String name, int maxSpeed) { echo name." drives with ".(maxSpeed - terrain->modifier)." mph in ".terrain->name; } abstract public function drive(Terrain terrain); } class Car extends Vehicle { this.name="Ferrari"; this.numberOfWheels = 4; this.maxSpeed = 310; function drive(Terrain terrain) { parent::drive(terrain,this.name,this.maxSpeed); } } class Bicycle extends Vehicle { this.name="Mountain Bike"; this.numberOfWheels = 2; this.maxSpeed = 150; function drive(Terrain terrain) { parent::drive(terrain,this.name,this.maxSpeed); } }
Benefits:
- You now have the flexibility.
- You can expand the disk method
Disadvantages:
- Basically, you declare two different methods with the same name to achieve what you want it to be a workaround.
- not very flexible
Here's how you could force immutable properties:
abstract class Vehicle { String name; int numberOfWheels; int maxSpeed; public function __construct() { } protected abstract function getName(); protected abstract function getNumberOfWheels(); protected abstract function getMaxSpeed(); public function drive(Terrain terrain) { echo this.getName()." drives with ".(this.getMaxSpeed() - terrain->modifier)." mph in ".terrain->name; } } class Car extends Vehicle { protected function getName() { return "Ferrari"; } protected function getNumberOfWheels() { return 4; } protected function getMaxSpeed() { return 310; } } class Bicycle extends Vehicle { protected function getName() { return "Mountain Bike"; } protected function getNumberOfWheels() { return 2; } protected function getMaxSpeed() { return 150; } }
If you do this like this, no one can change the properties outside, but only subclasses.
You can also use the protected constructor in the superclass:
abstract class Vehicle { String name; int numberOfWheels; int maxSpeed; protected function __construct($name,$numberOfWheels,$maxSpeed) { this.name = name; this.numberOfWheels = numberOfWheels; this.maxSpeed = maxSpeed; } public function drive(Terrain terrain) { echo this.name." drives with ".(this.maxSpeed - terrain->modifier)." mph in ".terrain->name; } } class Car extends Vehicle {
Why does it work:
Note. Parent constructors are not called implicitly if the child class defines the constructor. To start the parent constructor, call parent :: __ construct () inside the child constructor. If child does not define a constructor, then it can be inherited from the parent class, as a regular class method (if it was not declared as private).
If you have public properties that just need to be initialized, just use the constructor arguments.
Your second question: Is it bad OOP that parent methods rely on what the child has to provide?
In my opinion, no, not necessarily. Otherwise, there would be only interfaces, not abstract classes or, for example, Traits (PHP). Mechanisms like this avoid code duplication .