Как интерфейсы могут заменить необходимость множественного наследования при наличии существующих классов - PullRequest
53 голосов
/ 15 февраля 2011

Прежде всего ... Извините за этот пост.Я знаю, что в stackoverflow есть много сообщений о множественном наследовании.Но я уже знаю, что Java не поддерживает множественное наследование, и я знаю, что использование интерфейсов должно быть альтернативой.Но я не понимаю и вижу свою дилемму:

Мне нужно внести изменения в очень очень большой и сложный инструмент, написанный на Java.В этом инструменте есть структура данных, построенная из множества различных объектов класса со связанной иерархией элементов.В любом случае ...

  • У меня есть один класс Tagged, который имеет несколько методов и возвращает тег объекта в зависимости от класса объекта.Ему нужны члены и статические переменные.
  • И второй класс, называемый XMLElement, позволяет связывать объекты и, в конце концов, генерировать файл XML.Здесь мне также нужны переменные-члены и статические переменные.
  • Наконец, у меня есть много классов данных, которые почти все должны расширять XMLElement, а некоторые из них Tagged.

Okхорошо, это не сработает, так как можно расширить только один класс.Я часто читал, что с Java все в порядке и нет необходимости иметь множественное наследование.Я верю, но я не понимаю, как интерфейс должен заменить наследование.

  1. Нет смысла помещать реальную реализацию во все классы данных, поскольку она всегда одинакова, но это необходимо для интерфейсов (я думаю).
  2. Я не понимаю, как я мог бы изменить один из моих классов наследования на интерфейс.У меня есть переменные здесь, и они должны быть именно там.

Я действительно не понимаю, поэтому, пожалуйста, кто-нибудь может объяснить мне, как справиться с этим?

Ответы [ 10 ]

74 голосов
/ 03 ноября 2012

На самом деле, у меня нет хорошего ответа, кроме Java, я ДОЛЖЕН иметь множественное наследование.Весь смысл в том, что интерфейсы должны быть в состоянии заменить необходимость множественного наследования, похоже на большую ложь, которая, когда повторяется достаточно много раз, становится правдой.

Аргумент состоит в том, что множественное наследование вызывает все эти проблемы (la-di-dah), но я продолжаю слышать те аргументы от разработчиков Java, которые никогда не использовали C ++.Я также НИКОГДА не помню, чтобы программисты на С ++ говорили: «Ну и дела, я люблю С ++, но если бы они избавились только от множественного наследования, это стало бы отличным языком».Люди использовали это, когда это было практично, и не, когда это не было.

Ваша проблема - это классический случай, когда подходит множественное наследование.Любое предложение реорганизовать код на самом деле говорит вам, как обойти ПРОБЛЕМУ, что у Java нет множественного наследования.

Также все дискуссии о том, что «о, делегирование лучше, ла-ди-да», сбивают с толку религиюс дизайном.Там нет правильного пути.Вещи либо более полезны, либо менее полезны, и это все.

В вашем случае множественное наследование было бы более полезным и более элегантным решением.

Поскольку рефакторинг вашего кода в менее полезную форму удовлетворяет всех религиозных людей, которые никогда не использовали множественное наследование и считают, что «множественное наследование - это плохо», я думаю, вам придется понизить код, потому что я неЯ не вижу, что Java "улучшается" таким образом в ближайшее время.Слишком много людей повторяют религиозную мантру до такой степени глупости, что я не вижу, чтобы она когда-либо добавлялась к языку.

На самом деле мое решение для вас было бы "x extends Tagged, XMLElement", и это было бы все.

... но, как вы можете видеть из решений, представленных выше, большинство людей думаютчто такое решение будет СЛИШКОМ СЛОЖНЫМ И ЗАМЕДЛЕННЫМ!

Я бы предпочел сам рискнуть на территорию «x расширяет a, b», даже если это очень пугающее решение, которое может превзойти возможности большинства Java-программистов.

Что еще более удивительно в предложенных выше решениях, так это то, что все присутствующие, которые предложили вам преобразовать свой код в «делегирование» из-за плохого множественного наследования, будут, если бы столкнулись с той же проблемойРешите проблему, просто выполнив: «x расширяет a, b» и покончите с этим, и все их религиозные аргументы о «делегировании против наследования» исчезнут.Вся эта дискуссия глупа, и ее продвигают только невежественные программисты, которые только демонстрируют, насколько хорошо они могут читать из книги и как мало они могут думать сами за себя.

Вы на 100% правы, что множественное наследованиепомогите, и нет, вы делаете что-то не так в своем коде, если считаете, что это должно быть в Java.

56 голосов
/ 15 февраля 2011

Вероятно, вам следует отдать предпочтение композиции (и делегированию), а не наследованию:

5 голосов
/ 11 декабря 2012

Аналогично тому, что Andreas_D предложил , но с использованием внутренних классов. Таким образом, вы действительно расширяете каждый класс и при желании можете переопределить его в своем собственном коде.

interface IBird {
    public void layEgg();
}

interface IMammal {
    public void giveMilk();
}

class Bird implements IBird {
    public void layEgg() {
        System.out.println("Laying eggs...");
    }
}

class Mammal implements IMammal {
    public void giveMilk() {
        System.out.println("Giving milk...");
    }
}

class Platypus implements IMammal, IBird {

    private class LayingEggAnimal extends Bird {}
    private class GivingMilkAnimal extends Mammal {}

    private LayingEggAnimal layingEggAnimal = new LayingEggAnimal();

    private GivingMilkAnimal givingMilkAnimal = new GivingMilkAnimal();

    @Override
    public void layEgg() {
        layingEggAnimal.layEgg();
    }

    @Override
    public void giveMilk() {
        givingMilkAnimal.giveMilk();
    }
}
4 голосов
/ 15 февраля 2011

Во-первых, нет смысла помещать реальную реализацию во все классы данных, поскольку она всегда одинакова, но это необходимо для интерфейсов (я думаю).

Как насчет использования агрегации для тегов?

  1. Переименуйте свой Tagged класс в Tags.

  2. Создание интерфейса Tagged:

    интерфейс Tagged { Теги getTags (); }

  3. Позвольте каждому классу, который нужно «пометить», реализовать Tagged и пусть у него есть поле tags, которое возвращается из getTags.

Во-вторых, я не понимаю, как я мог бы изменить один из моих классов наследования на интерфейс. У меня есть переменные здесь, и они должны быть именно там.

Правильно, интерфейсы не могут иметь переменных экземпляра. Однако структуры данных, хранящие теги, не обязательно должны быть частью IMO классов, которые помечены. Выделите теги в отдельной структуре данных.

2 голосов
/ 15 февраля 2011

Я бы решил это следующим образом: извлекать интерфейсы для классов Tagged и XMLElement (возможно, вам не нужны все методы в открытом интерфейсе).Затем реализуйте оба интерфейса, и реализующий класс имеет Tagged (ваш фактический конкретный Tagged класс) и XMLElement (ваш фактический конкретный XMLElement класс):

 public class MyClass implements Tagged, XMLElement {

    private Tagged tagged;
    private XMLElement xmlElement;

    public MyClass(/*...*/) {
      tagged = new TaggedImpl();
      xmlElement = new XMLElementImpl();
    }

    @Override
    public void someTaggedMethod() {
      tagged.someTaggedMethod();
    }
  }

  public class TaggedImpl implements Tagged {
    @Override
    public void someTaggedMethod() {
      // so what has to be done
    }
  }

  public interface Tagged {
     public void someTaggedMethod();
  }

(и то же самое для XMLElement)

1 голос
/ 07 января 2019

Хорошо используя Interface и один базовый класс, вы просто заявляете:

, что также соответствует объектам реальной жизни.

В мире, где объекты могут быть нескольких типов (по горизонтали). Допустим, дельфин - это млекопитающее, а также морское животное, в этом случае множественное наследование имело бы больше смысла.Было бы проще представить его с использованием множественного наследования.

1 голос
/ 13 февраля 2013

Просто интересно, нельзя ли просто использовать внутренние (членские) классы (LRM 5.3.7)?Например, вот так (основываясь на первом ответе выше):

// original classes:
public class Tagged {
    // ...
}

public class XMLElement {
    // ...
}

public class TaggedXmlElement {
    public/protected/private (static?) class InnerTagged extends Tagged {
      // ...
    }

    public/protected/private (static?) class InnerXmlElement extends XMLElement  {
        // ...
    }

}

Таким образом, у вас есть класс TaggedXmlElement, который фактически содержит все элементы из двух исходных классов, а внутри TaggedXmlElement у вас есть доступ к не закрытым членамчлены классов.Конечно, нельзя было бы использовать «super», но вызывать методы класса-члена.В качестве альтернативы можно расширить один из классов и сделать другой класс-член.Есть некоторые ограничения, но я думаю, что их можно обойти.

1 голос
/ 15 февраля 2011

один из возможных способов;

1 - Вы можете создать базовый класс (ы) для общей функциональности, сделать его абстрактным, если вам не нужно создавать его экземпляр.

2- Создание интерфейсов и реализация этих интерфейсов в этих базовых классах. Если требуется конкретная реализация, сделайте метод абстрактным. каждый конкретный класс может иметь свой собственный импл.

3 - расширить абстрактный базовый класс для конкретного класса (классов) и реализовать на этом уровне также определенные интерфейсы

0 голосов
/ 06 ноября 2013

Я запускаю похожую проблему на Android.Мне нужно было расширить Button и TextView (оба наследуются от View) дополнительными функциями.Из-за отсутствия доступа к их суперклассу, мне нужно было найти другое решение.Я написал новый класс, который инкапсулирует все реализации:

class YourButton extends Button implements YourFunctionSet {
    private Modifier modifier;

    public YourButton(Context context) {
        super(context);
        modifier = new Modifier(this);
    }

    public YourButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        modifier = new Modifier(this);
    }

    public YourButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        modifier = new Modifier(this);
    }

    @Override
    public void generateRandomBackgroundColor() {
        modifier.generateRandomBackgroundColor();
    }
}

class Modifier implements YourFunctionSet {

    private View view;

    public Modifier(View view) {
        this.view = view;
    }

    @Override
    public void generateRandomBackgroundColor() {
        /**
         * Your shared code
         *
         * ......
         *
         * view.setBackgroundColor(randomColor);
         */
    }
}


interface YourFunctionSet {
    void generateRandomBackgroundColor();
}

Проблема здесь в том, что вашим классам нужен один и тот же супер класс.Вы также можете попытаться использовать разные классы, но проверьте, к какому типу он относится, например,

public class Modifier{
private View view;
private AnotherClass anotherClass;

public Modifier(Object object) {
    if (object instanceof View) {
        this.view = (View) object;
    } else if (object instanceof AnotherClass) {
        this.anotherClass = (AnotherClass) object;
    }
}

public void generateRandomBackgroundColor(){
    if(view!=null){
        //...do
    }else if(anotherClass!=null){
        //...do
    }
}
}

Так что здесь в основном мой класс Modifier, который инкапсулирует все реализации.

Надеюсь, что этопомогает кому-то.

0 голосов
/ 09 июля 2013

Использование композиции было бы способом, предложенным другим разработчиком.Основным аргументом против множественного наследования является неоднозначность, создаваемая, когда вы расширяете два класса с одним и тем же объявлением метода (одно и то же имя метода и параметры).Лично, однако, я думаю, что это куча дерьма.В этой ситуации легко может возникнуть ошибка компиляции, которая не сильно отличается от определения нескольких методов с одним и тем же именем в одном классе.Что-то вроде следующего фрагмента кода может легко решить эту дилемму:

public MyExtendedClass extends ClassA, ClassB {
    public duplicateMethodName() {
        return ClassA.duplicateMethodName();
    }
}

Еще один аргумент против множественного наследования заключается в том, что Java старалась упростить задачу, чтобы разработчики-любители не создавали сеть взаимозависимых классов, которая могла бысоздать грязную, запутанную систему программного обеспечения.Но, как вы видите в вашем случае, это также усложняет и запутывает вещи, когда они недоступны.Кроме того, этот аргумент может быть использован для 100 других вещей в кодировании, поэтому у команд разработчиков есть обзоры кода, программное обеспечение для проверки стиля и ночные сборки.

Однако в вашей конкретной ситуации вам придется уладитьс композицией (см. ответ Shojaei Baghini).Он добавляет немного кода, но имитирует то же поведение, что и множественное наследование.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...