it-swarm-ru.tech

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

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

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

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

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

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

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

53
fpdragon

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

public interface TaggedInterface {
    void foo();
}

public interface XMLElementInterface {
    void bar();
}

public class Tagged implements TaggedInterface {
    // ...
}

public class XMLElement implements XMLElementInterface {
    // ...
}

public class TaggedXmlElement implements TaggedInterface, XMLElementInterface {
    private TaggedInterface tagged;
    private XMLElementInterface xmlElement;

    public TaggedXmlElement(TaggedInterface tagged, XMLElementInterface xmlElement) {
        this.tagged = tagged;
        this.xmlElement = xmlElement;
    }

    public void foo() {
        this.tagged.foo();
    }

    public void bar() {
        this.xmlElement.bar();
    }

    public static void main(String[] args) {
        TaggedXmlElement t = new TaggedXmlElement(new Tagged(), new XMLElement());
        t.foo();
        t.bar();
    }
}
56
JB Nizet

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

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

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

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

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

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

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

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

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

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

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

74
SouthRoad

Подобно тому, что предложил 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();
    }
}
5
SHOJAEI BAGHINI

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

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

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

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

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

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

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

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

4
aioobe

Я бы решил это так: извлеките интерфейсы для классов 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)

2
Andreas_D

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

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

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

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

1
fmucar

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

    A) Один объект может быть только одного типа (что верно в реальной жизни, если вы думаете, что голубь - это птица, тойота - это машина и т.д.). Голубь - это тоже животное, но в любом случае каждая птица - животное, поэтому Иерархически он находится над типом птицы. И в вашем OOP дизайне. Класс Animal должен быть основой класса Bird на случай, если вам нужно его представить -) и
    Б) может делать много разных вещей (птица может петь, может летать. Машина может бежать, может остановиться и т.д.)

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

1
Jeff Schreib

Просто интересно, нельзя ли просто использовать внутренние (членские) классы (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
Einar H.

Я работаю в аналогичной проблеме на 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
Marian Klühspies

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

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

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

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

0
Greg H