Альтернатива для множественного наследования
Спустя примерно 20 лет программирование Java в первый раз, я хочу, чтобы у меня было множественное наследование. Но я не так, я ищу альтернативу для этой конкретной проблемы.
Реальное приложение - это своего рода ERP и несколько сложный, поэтому я пытаюсь перевести эту проблему на автомобили. Это происходит только до сих пор, но это лучшее, что я могу сделать.
Давайте начнем с интерфейса, описывающего, что может сделать автомобиль:
public interface Car {
public String accelerate();
public String decelerate();
public String steerLeft();
public String steerRight();
}
Теперь у нас есть базовая (но не абстрактная) реализация:
public class BasicCar implements Car {
protected final String name;
public BasicCar( String name ) {
this.name = name;
}
// In the real word this method is important and does lots of stuff like calculations and caching
protected String doDrive( String how ) {
return name + " is " + how + "ing";
}
@Override
public String accelerate() {
return doDrive( "accelerat" );
}
@Override
public String decelerate() {
return doDrive( "decelerat" );
}
// This method is important, too
protected String doSteer( String where ) {
return name + " is steering to the " + where;
}
@Override
public String steerLeft() {
return doSteer( "left" );
}
@Override
public String steerRight() {
return doSteer( "right" );
}
}
В реальном мире это само по себе является фасадом к DAO. Обратите внимание, что мне нужны методы doDrive
и doSteer
, потому что это то, где выполняется настоящая работа, например, некоторые вычисления, переводы и кеширование.
И еще несколько конкретных реализаций:
public class Sportscar extends BasicCar {
public Sportscar( String name ) {
super( name );
}
// I need to call the super method
@Override
public String doDrive( String how ) {
return super.doDrive( how ) + " fastly";
}
}
public class Truck extends BasicCar {
public Truck( String name ) {
super( name, new BasicMotor(), new TruckySteerer() );
}
// I need to call the super method
@Override
public String doSteer( String where ) {
return super.doSteer( where ) + " carefully";
}
}
Теперь я могу сделать следующее:
Car mcqueen = new Sportscar( "McQueen" );
mcqueen.steerLeft() //-> "McQueen is steering left"
mcqueen.accelerate() // -> "McQueen is accelerating fastly"
Car mack = new Truck( "Mack" );
mack.steerLeft() //-> "Mack is steering left carefully"
mack.accelerate() // -> "Mack is accelerating"
Теперь я хочу объединить эти два в один, который разделяет их функциональность:
Car red = new Firetruck( "Red" );
red.steerLeft() //-> "Red is steering left *carefully*"
red.accelerate() // -> "Red is accelerating *fastly*"
Что я пробовал/думал о
Я мог бы скопировать и вставить код из обоих в один класс. Но это никогда не бывает хорошей идеей. И в реальном приложении это довольно много кода, поэтому это еще худшая идея.
Я думаю, что я столкнулся с каким-то серьезным переписанием/рефакторингом.
Поэтому я мог бы сделать Sportscar
и Truck
Decorator, а для Firetruck
использовать оба. Но это не сработает, потому что doSteer
и doDrive
вызываются из украшенных объектов, тогда как декоратор работает только для вызовов из "снаружи". Поэтому мне пришлось бы поместить эти методы в декоратор, тоже, и это тоже не очень хорошая идея.
Поэтому я мог бы использовать новые причудливые функции Java8 и сделать doSteer
и doDrive
делегирование интерфейса, например:
@FunctionalInterface
interface Driver {
public String doDrive( String where );
}
И подавая его конструктору BasicCar
но тогда (кроме того, что вы становитесь довольно сложными и получаете некоторые неприятные проблемы с java с final
), я больше не имею доступа к состоянию BasicCar
.
Так что в настоящее время я немного потерялся здесь. Любые хорошие идеи будут рассмотрены.
EDIT: Чтобы привести пример, как это выглядит в реальном мире: группа doSteer
может быть примерно такой:
class ProductImpl {
String getTitle(){
return getField( "title" );
}
String getTeaser(){
return getField( "teaser" );
}
String getField( String name ){
Locale loc = configuredLocale();
Map vars = configuredVarialbes();
String value = getCached( name, loc );
value = translateVariables( value );
value = replaceVariables( value, vars );
// and more
}
}
Ответы
Ответ 1
Вы можете использовать стратегию шаблонов для определения разных типов поведения и выполнения подклассов.
Как на картинке:
![Pattern strategy]()
Это французский (словарь ниже), но довольно понятный:
- Все классы, наследующие от
Personnage
, являются символами, используемыми в видеоигре.
- Все классы, реализующие интерфейс (
EspritCombatif
, Deplacement
, Soin
), определяют метод в интерфейсе. Это поведение.
- Класс
Personnage
содержит объект каждого из интерфейсов, определенных справа.
- Каждый символ (подкласс
Personnage
) выбирает, какой код реализовать, выполняя, например, combat = new CombatCouteau();
в конструкторе.
Итак, если вы хотите изменить только поведение всех парней, которые сражаются с ножом, вы просто меняете код в CombatCouteau
.
Словарь:
- Personnage = Character
- Guerrier = Warrior
- Medecin = Doctor
- Chirurgien = Хирург
- Couteau = Нож
- Пистолет = Gun
- Marcher = Прогулка
- Courir = Run
- Premier soin = Первая помощь
- Операция = Хирургия
- Esprit combatif = боевой ум
- борьба, борьба = борьба, борьба
- déplacement, se déplacer = перемещение, перемещение
- soin, soigner = исцеление, исцеление
Ответ 2
Будет ли Compositor
делать? Он не переводит мир автомобилей гладко, но подходит для вашей задачи:
public class FireTruck implements Car {
public FireTruck( List<? extends Car> behaviors ) {
this.behaviors = behaviors;
}
// I need to call the super method
@Override
public String doSteer( String where ) {
String result = "":
for (Car behavior : behaviors) result += behavior.doSteer(where);
// post-process behavior some how to cut out infixes;
}
}
Я предположил, что в вашем реальном приложении doSteer
работает какой-либо объект домена, а не String
, поэтому набор методов объекта домена должен быть достаточно богат, чтобы легко создавать поведение в методах do*
.
Ответ 3
Функциональность разделения и состояние
Если вам все равно придется делать большой рефакторинг - хороший способ решить эту сложную проблему - это разделить состояние и функциональность. Просто помните, как Object-Methods в Java работают за кулисами: по сути, объектная функция является статической функцией с первым аргументом "this". Параметр "this" - это объект состояния со всеми необходимыми атрибутами. Функциональность не обязательно должна быть связана с этим.
Он может выглядеть так:
class CarState {
public float fuel;
public String name;
}
class BasicCarFunctionality {
public static void accelerate( CarState car ) {
System.out.println( "accelerating" );
}
}
class SportscarFunctionality {
public static void drive( CarState car, String how ) {
// Here you can reference Basic-Behaviour if you want
BasicCarFunctionality.accelerate( car );
System.out.println( car.name + " drive " + how );
}
}
class TruckFunctionality {
public static void steer( CarState car, String how ) {
System.out.println( car.name + " steer " + how );
}
}
interface SportsCar {
void doDrive( String how );
}
interface Truck {
void doSteer( String how );
}
class Firewagon implements SportsCar, Truck {
private CarState car = new CarState();
@Override
public void doDrive( String how ) {
SportscarFunctionality.drive( car, how );
}
@Override
public void doSteer( String how ) {
TruckFunctionality.steer( car, how );
}
}
Если вам нужны дополнительные атрибуты состояния для вашего firewagon, которые вам не нужны в каждом автомобиле, вы можете создать свое состояние с новым объектом State:
class FirewagonExtraState {
public float waterAmount;
public boolean sirenActive;
}
// And ammend the Firewagon Class:
class Firewagon implements SportsCar, Truck {
private CarState car = new CarState();
private FirewagonExtraState firewagon = new FirewagonExtraState();
@Override
public void doDrive( String how ) {
FirewagonFunctionality.activateHorn( firewagon );
SportscarFunctionality.drive( car, how );
}
@Override
public void doSteer( String how ) {
TruckFunctionality.steer( car, how );
}
}
Ответ 4
Я не уверен, что аналогия достаточно правильная, чтобы дать вам хороший ответ.
Самый простой способ справиться с несколькими проблемами наследования в Java - это использовать интерфейсы, а затем делегировать поведение с помощью композиции.
Автомобили и грузовики имеют рулевое управление и ускорение, но грузовик не является автомобилем и не должен наследовать от него поведение только потому, что часть реализации разделена. Вытяните общую реализацию в класс, который имеет больше смысла (колонка управления, поезд на поезде и т.д.), А затем передайте его как с автомобиля, так и с грузовика.
Использование функционального интерфейса делает это менее громоздким, но вам не нужна Java 8 для решения проблемы.
Ответ 5
Как насчет этого:
public class SportsCar implements Car {
private final BasicCar basicCar;
public SportsCar( String name ) {
this.basicCar = new BasicCar(name);
}
public SportsCar( BasicCar basicCar ) {
this.basicCar = basicCar;
}
@Override
public String accelerate() {
return basicCar.accelerate() + " fastly";
}
@Override
public String decelerate() {
return basicCar.decelerate() + " fastly";
}
@Override
public String steerLeft(){
return basicCar.steerLeft() + " fastly";
}
@Override
public String steerRight(){
return basicCar.steerLeft() + " fastly";
};
}
public class Truck implements Car {
private final BasicCar basicCar;
public Truck( String name ) {
this.basicCar = new BasicCar(name);
}
public Truck( BasicCar basicCar ) {
this.basicCar = basicCar;
}
@Override
public String accelerate() {
return basicCar.accelerate() + " carefully";
}
@Override
public String decelerate() {
return basicCar.decelerate() + " carefully";
}
@Override
public String steerLeft(){
return basicCar.steerLeft() + " carefully";
}
@Override
public String steerRight(){
return basicCar.steerLeft() + " carefully";
};
}
Теперь для класса FireTruck
public class FireTruck implements Car {
Collection<Car> behaviors;
BasicCar basicCar;
public FireTruck(String name ) {
this.behaviors = new ArrayList<String>();
this.basicCar = new BasicCar(name);
this.behaviors.Add(new SportsCar(this.basicCar));
this.behaviors.Add(new Truck(this.basicCar));
}
@Override
public String accelerate() {
StringBuilder result = new StringBuilder();
for(Car c : behaviors){
result.append(c.accelerate());
}
//and if I wanted to do my own thing here as a 'FireTruck' I could, something like :
String myOwnAction = basicCar.accelerate() + " sounding alarms and flashing red light";
result.append(myOwnAction);
}
//same for other methods : decelerate, steerLeft and steerRight
}
Ответ 6
Я задал очень похожий вопрос некоторое время назад... может быть, вы сочтете это полезным:
Коллекция абстрактного класса (или что-то в этом роде...)