Подтвердить что ты не робот

Альтернатива для множественного наследования

Спустя примерно 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
    }
}
4b9b3361

Ответ 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
}