Чем в java заменить множественное наследование?

Хочу реализовать приблизительно следующую архитектуру:
Пояснение:
- Base — Что-то вроде сущности-хранилища, в котором хранятся какие-то базовые объекты;
- ChildN — потомки, реализующие свой спектр действий над данными, которые хранятся в классе Base . По сути являются интерфейсами, т.к. никакого состояния не хранят;
- static Mixin — класс, объединяющий в себе все методы из ChildN . Коллизий между именами методов нет. Все его методы (т.е. методы, унаследованные от ChildN ) должны быть статическими, вызываться без инстанцирования.
В python эта задача легко бы решилась множественным наследованием, в java такого нет. Как быть?
Примерный псевдокод того, что хочу реализовать:
class Base < static protected final String someString = "01"; >class Child1 extends Base < static public char get_0() < return someString.charAt(0); >> class Child2 extends Base < static public char get_1() < return someString.charAt(1); >> class MixIn extends Child1, Child2 < // Так сделать нельзя >public class Main < public static void main(String[] args) < System.out.println(MixIn.get_0()); // Итоговое использование должно быть таким System.out.println(MixIn.get_1()); >>
Отслеживать
задан 11 дек 2020 в 9:21
Mikhail Murugov Mikhail Murugov
5,436 1 1 золотой знак 16 16 серебряных знаков 36 36 бронзовых знаков
В Java есть понятие interface, вы можете свои Child классы реализовать с их помощью. Множественное наследование интерфейсов допустимо
11 дек 2020 в 9:27
у всех Ваших классов будет один общий предок. И тут возникает целый спектр разных проблем. Поэтому в Java и решили отказаться от этого. Но можно делать множественное «наследование» от интерфейсов. А у интерфейсов теперь есть «имплементация методов»:)
11 дек 2020 в 9:28
Если Base — хранилище, используйте композицию. Использование наследования здесь будет семантически неверно, т.к. наследование классов, выполняющих операции над данными, от класса Base , не представляет отношение IS-A
11 дек 2020 в 9:29
Все было понятно пока не появились статические методы. Вы просто так добавили static? Для статических методов наследование здесь имеет мало смысла. Почему нельзя написать Child1.get_0() и Child2.get_1() если методы не переопределяются все равно? Может доработаете пример чтобы было понятнее что именно Вам нужно?
11 дек 2020 в 9:30
Возможно, паттерн стратегия подойдет. Классы ChildN выполняют только действия, поэтому их вполне можно заменить на стратегии.
11 дек 2020 в 9:32
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Как выяснилось, разделение функционала на несколько классов с повторным объединением в один нужно вам исключительно в целях логической группировки. Возможные решения вашей проблемы:
Использование интерфейсов
Вы можете сделать child-ы интерфейсами. В Java нет множественно наследования, зато можно имплементировать несколько интерфейсов. Чтобы сделать реализацию методов прямо в интерфейсах (чтобы они не требовали реализации в реализующем их классе), можно использовать ключевое слово default :
class Base < static public final String str = "01"; >interface Child1 < default char get0() < return Base.str.charAt(0); >> interface Child2 < default char get1() < return Base.str.charAt(1); >> class MixIn implements Child1, Child2 < >class Main < public static void main(String[] args) < MixIn mixIn = new MixIn(); System.out.println(mixIn.get0()); System.out.println(mixIn.get1()); >>
Отказ от разделения функционала
Вы работаете с одним репозиторием Base. На мой взгляд здесь более чем достаточно использование одного класса без всяких интерфейсов. Более того, можно все эти методы разместить в классе Base.
class Base < private final String str = "01"; public String getStr() < return str; >> class MixIn < private Base base; public MixIn(Base base) < this.base = base; >public char get0() < return base.getStr().charAt(0); >public char get1() < return base.getStr().charAt(1); >> public class Main < public static void main(String[] args) < MixIn mixIn = new MixIn(new Base()); System.out.println(mixIn.get0()); System.out.println(mixIn.get1()); >>
Использование статических внутренних классов
Вы говорите, что вам нужна логическая группировка. В Java существует конструкция, которая называется статический внутренний или вложенный класс. Внутри класса создаётся ещё один класс, который отмечается static . Таким образом вы можете достичь логической группировки. Пример:
class Base < static public final String someString = "01"; >class MixIn < public static class Child1 < static char get1() < return Base.someString.charAt(1); >> public static class Child2 < static char get0() < return Base.someString.charAt(0); >> >
Видите, я создал общий класс MixIn, а внутри него два статических класса. Вызываются они вот так:
class Main < public static void main(String[] args) < System.out.println(MixIn.Child1.get1()); System.out.println(MixIn.Child2.get0()); >>
Как видите, никакого инстанцирование нет, а классы/методы логически сгруппированы.
Наследование в Java
В случае наследования в Java для указания родительского класса используется ключевое слово extends, которое следует после имени дочернего, т. е. производного, класса. После extends пишется родительский класс. Другими словами, «дочерний класс расширяет родительский».
В Java нет множественного наследования от классов, но есть множественное наследование интерфейсов.
В Java есть особенность, связанная с наследованием конструкторов и их вызовом. В конструкторе дочернего класса, перед тем как будут выполнены выражения тела, неявно вызывается дефолтный (без параметров) конструктор предка.
Причем конструктор без параметров обязательно должен быть в родительском классе, если в дочернем предполагаются только конструкторы с аргументами, а родительские конструкторы не вызываются явно. В этом случае, если в родительском классе будут только конструкторы с аргументами, то в дочернем будет возникать ошибка.
public class Main public static void main(String[] args) Child child = new Child(10); System.out.println(child.a); System.out.println(child.b); > >
class Parent int a; Parent(int a) this.a = 1; > >
class Child extends Parent int b; Child(int b) this.b = b; > >
В приведенном выше примере ошибка возникает на уровне конструктора класса Child. Перед тем как выполнится выражение this.b = b, будет неявно вызван конструктор Parent без параметров. Однако такого конструктора в родительском классе нет.
Если мы закомментируем или удалим конструктор Parent,
class Parent int a; // Parent(int a) // this.a = 1; // > >
то ошибка в дочернем классе исчезнет. Дело в том, что в Java любой класс есть потомок базового класса Object, у которого всегда имеется пустой дефолтный конструктор. В данном случае, двигаясь по дереву наследования к корню, из Child будет вызываться конструктор базового для всех класса.
Почему же он не вызывался, когда в Parent был определен конструктор с параметрами? В Java, как только вы определяете в классе любой конструктор, дефолтный родительского уже не наследуется напрямую, а начинает неявно вызываться из собственных конструкторов.
Заметим, что у объектов Child поле this.a существует, даже если ему не было присвоено значение. В данном случае значение будет нулевым.
Обычным вариантом недопущения подобных ошибок является объявление в Parent конструктора без аргументов. Он может быть пустым, или содержать какой-либо код:
import java.util.Random; class Parent int a; Parent() Random random = new Random(); this.a = random.nextInt(10); > >
Другой распространенный вариант – когда в дочернем классе явно вызывается конструктор родительского класса с помощью ключевого слова super. Причем вызывать всегда надо первой строчкой тела конструктора.
public class Main public static void main(String[] args) Child child = new Child(10, 20); System.out.println(child.a); System.out.println(child.b); > >
class Parent int a; Parent(int a) this.a = a; > >
class Child extends Parent int b; Child(int a, int b) super(a); this.b = b; > >
Обычные методы наследуются и переопределяются стандартно для объектно-ориентированного программирования. При этом в Java наследовать и переопределять можно только нестатические методы. Таким образом, если в родительском классе объявлен статический метод, то дочерние классы его не наследуют.
В Java объекты дочернего класса можно присваивать переменным родительского. Подобное называется расширением типа. При этом в случае вызова переопределенных методов будут вызываться методы дочернего класса, а не родительского. Однако набор допустимых для вызова методов определяется родительским классом, то есть типом переменной.
Также возможно сужение типа, когда объект, связанный с переменной родительского типа, присваивается переменной дочернего, то есть своего типа. В этих случаях, чтобы избежать ошибок, следует проверять тип объекта. Например, пусть класс B производный от класса A. Тогда:
A var = new B(); if (var instanceof B) B var1 = (B) var; >
Если в методе дочернего класса требуется использовать возможности родительского, то обращение ко второму происходит через слово super, после которого пишется необходимый метод.
public class ParentMeth public static void main(String[] args) ChildMeth ch = new ChildMeth(); ch.PrintStr(); > public void PrintStr() System.out.println("Parent class"); > > class ChildMeth extends ParentMeth public void PrintStr() super.PrintStr(); System.out.println("Child class"); > >
Parent class Child class
X Скрыть Наверх
Программирование на Java. Курс
Почему считают, что в Java нет множественного наследования?
Множественное наследование — это когда в определении класса может быть задано несколько классов-предков. Но в Java может быть указан только один класс-предок.
Наследование — это ТОЛЬКО «папа». «Дедушки», «прадедушки» и т. д. не входят в наследование.
В Java используется механизм интерфейсов, который обеспечивает такую же мощность, как и множественное наследование, но без головной боли ромбовидного наследования.
NextМудрец (19662) 2 года назад
Проблему ромба в с++ вроде компилятор отрабатывает. В java тоже есть не абстрактные методы в интерфейсах, там компилятор не даст скомпилироваться если у двух интерфейсов одинаковые методы. В чем разница то
Next, разница в подходе. Препочтительней использовать интерфейсы. Например, в фразе «и швец, и жнец, и на дуде игрец» мы скорее видим некоего человека который способен выполнять _роль_ швеи, жнеца и исполнителя на духовых инструментах. В случае с наследованием это будет связь _является_. Т. е. замужняя Швея с двумя детьми из города которая также является холостым Комбайнёром (известным по всей деревне ходоком) и ещё с неопределившимся со своей сексуальной ориентацией работником сферы развлечений из большого города
Родительский класс наследует Object, а не твой класс. Унаследовать второй класс ты не сможешь. Только интерфейсы.
Ладно, я передумал, всё же отвечу))
Так считают, потому что ты не сможешь в одном классе унаследоваться от более, чем одного. Вот, можешь сам попробовать вставить такое в ide:
public class Main extends Thread, Exception >
На что ты получишь ошибку Class cannot extend multiple classes
Почему множественное наследование не поддерживается в Java
Это просто устранить двусмысленность, поскольку множественное наследование может вызвать двусмысленность вокруг проблемы Diamond в нескольких сценариях. Например, если у вас есть два родительских класса, они могут иметь разные реализации одной и той же функции — или, возможно, две разные функции с тем же именем. Затем вам нужно решить, какой из них будет использоваться вашим подклассом. Есть способы справиться с этим, но разработчики Java считают, что множественное наследование вызывает больше проблем и путаницы, чем решает. Поэтому они избегают множественного наследования с языка Java.
(Пока оценок нет)