tl;dr– Since Bus
and Car
differ only by the argument that they need to .drive()
, you can just make that argument generic (Approaches 3 and 4). Alternatively, you might make the method itself a generic-lambda (Approach 5).
Approach 3: Describe the distinction generically.
Looks like the main difference between your examples is what information that they need to drive. You can do that with generics.
Basically:
Bus
and RaceCar
are both Vehicle
's that .drive()
.
They differ only by what argument they need to .drive()
.
So, make that argument generic.
// Language: Java.
public abstract class Vehicle <T extends I_DrivingMode> {
public abstract CompletableFuture<Double> Drive(T drivingMode);
}
public class Bus extends Vehicle<I_BusDrivingMode>
{
@Override
public CompletableFuture<Double> Drive(I_BusDrivingMode drivingMode) {
// ....
}
}
public class RaceCar extends Vehicle<I_RaceCarDrivingMode>
{
@Override
public CompletableFuture<Double> Drive(I_RaceCarDrivingMode drivingMode) {
// ....
}
}
public interface I_DrivingMode {
}
public interface I_BusDrivingMode extends I_DrivingMode {
String getSomeOtherKey();
Controller getController();
}
public interface I_RaceCarDrivingMode extends I_DrivingMode {
Wheel getWheel();
Tactic getTactic();
}
public class SimpleBusDrivingMode implements I_BusDrivingMode {
public SimpleBusDrivingMode(String someOtherKey, Controller controller) {
super();
this._someOtherKey = someOtherKey;
this._controller = controller;
}
private String _someOtherKey;
@Override
public String getSomeOtherKey() {
return _someOtherKey;
}
private Controller _controller;
@Override
public Controller getController() {
return _controller;
}
}
public class SimpleRaceCarDrivingMode implements I_RaceCarDrivingMode {
public SimpleRaceCarDrivingMode(Wheel wheel, Tactic tactic) {
super();
this._wheel = wheel;
this._tactic = tactic;
}
private Wheel _wheel;
@Override
public Wheel getWheel() {
return _wheel;
}
private Tactic _tactic;
@Override
public Tactic getTactic() {
return _tactic;
}
}
Approach 4: Describe the distinction generically, with Tuple<>
's.
Like Approach 3, but lazier in languages that have sufficient generics, e.g. C#.
// Language: C#.
public abstract class Vehicle<T_DrivingArgs>
{
public abstract double Drive(T_DrivingArgs drivingArgs);
}
public class RaceCar : Vehicle<(Wheel wheel, Tactic tactic)>
{
public override double Drive((Wheel wheel, Tactic tactic) drivingArgs)
{
throw new NotImplementedException();
}
}
public class Bus : Vehicle<(string someOtherKey, Controller controller)>
{
public override double Drive((string someOtherKey, Controller controller) drivingArgs)
{
throw new NotImplementedException();
}
}
Logically, this is much the same as Approach 3, just using Tuple<>
's as anonymous stand-ins for the argument-class
's.
Approach 5: Describe the distinction generically, with delegates/lambdas.
This can be cleaner with generic-lambdas.
Two versions below:
Minimal version.
Expanded version with interface
's and another abstract class
.
// Language: C#.
public abstract class Vehicle<T_DrivingMethod> where T_DrivingMethod : System.Delegate
{
public abstract T_DrivingMethod Drive { get; }
}
public class RaceCar : Vehicle<Func<Wheel, Tactic, double>>
{
public override Func<Wheel, Tactic, double> Drive => (Wheel wheel, Tactic tactic) =>
{
throw new NotImplementedException();
};
}
public class Bus : Vehicle<Func<string, Controller, double>>
{
public override Func<string, Controller, double> Drive => (string someOtherKey, Controller controller) =>
{
throw new NotImplementedException();
};
}
Alternatively, to define interface
's and another abstract class
:
public interface I_Vehicle { }
public interface I_Vehicle<T_DrivingMethod> : I_Vehicle where T_DrivingMethod : System.Delegate
{
T_DrivingMethod Drive { get; }
}
public interface I_RaceCar : I_Vehicle<Func<Wheel, Tactic, double>> { }
public interface I_Bus : I_Vehicle<Func<string, Controller, double>> { }
public abstract class Vehicle : I_Vehicle { }
public abstract class Vehicle<T_DrivingMethod> : Vehicle, I_Vehicle<T_DrivingMethod> where T_DrivingMethod : System.Delegate
{
public abstract T_DrivingMethod Drive { get; }
}
public class RaceCar : Vehicle<Func<Wheel, Tactic, double>>, I_RaceCar
{
public override Func<Wheel, Tactic, double> Drive => (Wheel wheel, Tactic tactic) =>
{
throw new NotImplementedException();
};
}
public class Bus : Vehicle<Func<string, Controller, double>>, I_Bus
{
public override Func<string, Controller, double> Drive => (string someOtherKey, Controller controller) =>
{
throw new NotImplementedException();
};
}
Optionally, the delegates can be named by:
Add named-delegate
definitions:
public delegate double DriveRaceCarDelegate(Wheel wheel, Tactic tactic);
public delegate double DriveBusDelegate(string someOtherKey, Controller controller);
Replacements:
Replace Func<Wheel, Tactic, double>
with DriveRaceCarDelegate
.
Replace Func<string, Controller, double>
with DriveBusDelegate
.