Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / Java [игнор отключен] [закрыт для гостей] / Интерфейсы вместо множественного наследования: в чём выгода от них ? / 25 сообщений из 30, страница 1 из 2
28.04.2013, 00:54:34
    #38242837
ozzmosis
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
Наверняка баян, но прошу не пинать.

Когда класс "С" должен унаследовать функционал от класса "А" и от класса "B", то без междумордов интерфейсов не обойтись, так ? Если да, то в следующем примере получается, что одни и те же вызовы методов интерфейса Flying указаны в двух разных классах.
Что-то корявое тут есть: как минимум, дублирование самих вызовов. Код какой-то "засорённый" получается.

Содержание примера: есть класс "Ж ы вотные", от него наследники: "Млекопитающие" и "Птицы". От "Млекопитающих" есть еще один наследник - "Летущие мыши". Но мыши эти, как известно, обладают некоторыми возможностями птиц, а именно: летают, причем способны управлять полётом (в отличие, к примеру, от летающих рыб).
Ну так вот: как правильно научить летучую мышь... летать ? (на яве, разумеется :))
У мну получилось пока что вот это:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
package InheritanceExploring;
class Animals {
    protected void move() {}
    protected void eat() {}
    protected void reproduce() {}
}
class Mammal extends Animals {
    protected void produceMilk() { /* ... special code for class Mammal here ... */ }
}

interface Flying {
    void rising();
    void lowering();
    void accelerating();
    void turning(int azimut);
    void landing();
}

class FlyingCreature implements Flying {
    @Override
    public void rising() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[2];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void lowering() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[2];
        System.out.println(me.getClassName()+"."+me.getMethodName());
   }

    @Override
    public void accelerating() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[2];
        System.out.println(me.getClassName()+"."+me.getMethodName());
   }

    @Override
    public void turning(int azimut) {
        StackTraceElement me = Thread.currentThread().getStackTrace()[2];
        System.out.println(me.getClassName()+"."+me.getMethodName());
   }

    @Override
    public void landing() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[2];
        System.out.println(me.getClassName()+"."+me.getMethodName());
   }

}

class Birds extends Animals implements Flying {
    public void makeNest() { /* ... special code for class Birds here ... */ }
    public void oviposit() { /* ... special code for class Birds here ... */ }
    @Override
    public void rising() { new FlyingCreature().rising(); }

    @Override
    public void lowering() { new FlyingCreature().lowering(); }

    @Override
    public void accelerating() { new FlyingCreature().accelerating(); }

    @Override
    public void turning(int azimut) { new FlyingCreature().turning(azimut); }

    @Override
    public void landing() { new FlyingCreature().landing(); }

    void doAll() {
        rising();
        accelerating();
        turning(360);
        lowering();
        landing();
    }
}

class Bat extends Mammal implements Flying {
    @Override
    public void rising() { new FlyingCreature().rising(); }

    @Override
    public void lowering() { new FlyingCreature().lowering(); }

    @Override
    public void accelerating() { new FlyingCreature().accelerating(); }

    @Override
    public void turning(int azimut) { new FlyingCreature().turning(azimut); }

    @Override
    public void landing() { new FlyingCreature().landing(); }

    void doAll() {
        rising();
        accelerating();
        turning(360);
        lowering();
        landing();
    }
}

// test
public class AnimalKingdom {
    public static void main(String[] args) {
        new AnimalKingdom().run();
    }
    void run(){
        Birds b1 = new Birds();
        b1.doAll();
        System.out.println("");
        Bat b2 = new Bat();
        b2.doAll();

    }
} // end of class AnimalKingdom

Output:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
InheritanceExploring.Birds.rising
InheritanceExploring.Birds.accelerating
InheritanceExploring.Birds.turning
InheritanceExploring.Birds.lowering
InheritanceExploring.Birds.landing

InheritanceExploring.Bat.rising
InheritanceExploring.Bat.accelerating
InheritanceExploring.Bat.turning
InheritanceExploring.Bat.lowering
InheritanceExploring.Bat.landing

Класс FlyingCreature содержит код-реализацию методов интерфейса Flying. Эти методы вызываются как из объекта класса Birds, так и из объекта класса Bat.
Но то, что в Bat'e приходится дублировать вызовы методов интерфейса, только лишь из-за того, что они должны быть реализованы этим классом, выглядит как-то "избыточно".
Если бы ява допускала множ. наследование, то достаточно было бы записать гораздо короче:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
class Bat extends Mammal, Birds  {
    void doAll() {
        rising();
        accelerating();
        turning(360);
        lowering();
        landing();
    }
}

А если бы некий метод (например, turning()) присутствовал и в Mammal и в Birds, то кастовать к конкретному классу, типа такого: (birds)turning(360).
Но это всё лирика.

ВОПРОС: я правильно понимаю, что когда класс должен хапнуть функционал от нескольких "источников", то надо делать так, как показано в примере выше ? Или там есть принципиальные ошибки и можно всё записывать короче ?
...
Рейтинг: 0 / 0
28.04.2013, 07:40:43
    #38242883
Usman
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
ozzmosisНо то, что в Bat'e приходится дублировать вызовы методов интерфейса, только лишь из-за того, что они должны быть реализованы этим классом, выглядит как-то "избыточно".Этого не избежать. Так или иначе код придется реализовать. Вопрос где лучше?
<imho>Самое подходящее место для этого - конкретный (конечный) класс.</imho>
ozzmosisА если бы некий метод (например, turning()) присутствовал и в Mammal и в Birds, то кастовать к конкретному классу, типа такого: (birds)turning(360).Здесь поможет полиморфизм (через общий интерфейс):
Пример
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
interface Animals {
    void move();
    void eat();
    void reproduce();
}

interface Mammals extends Animals {
    void produceMilk();
}

interface Flying {
    void rising();
    void lowering();
    void accelerating();
    void turning(int azimut);
    void landing();
}

interface Birds extends Animals, Flying { // Пока еще абстрактный класс
    void makeNest();
    void oviposit();
}

class Eagle implements Birds { // Конкретный класс
    ...
}

class Bat implements Mammals, Flying { // Конкретный класс
    ...
}
...
public void getToFly(Flying flying) { // Полиморфизм
    flying.rising();
    ...
    flying.landing();
}


...
Рейтинг: 0 / 0
28.04.2013, 10:52:39
    #38242925
Сено
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
Принципиальная ошибка заключается в том, что класс никогда не должен наследоваться от двух классов. Точка.
Если вы приходите к такой необходимости, значит у вас где-то ошибка в дизайне, и вы не понимаете сущность проектируемого класса.
...
Рейтинг: 0 / 0
28.04.2013, 11:19:47
    #38242933
chabapok
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
Проблема множественного наследования - методы с одинаковыми именами, которые реализованы в классах-предках.

кроме того, получается возможно наследоваться от двух цепочек, внутри которых есть одинаковые классы - и тогда вообще неясно как оно себя должно вести. То есть

A->B->C
A->D->E

и теперь создаем класс F, и наследуем его от E и C. Получается, что А у нас как бы дважды наследовано. Это сильно усложняет структуру и запутывает программу.


авторПринципиальная ошибка заключается в том, что класс никогда не должен наследоваться от двух классов. Точка.

тут конечно тоже автор не прав. Весьма удобно было бы, например, если б PropertyChangeSupport был классом, от которого можно наследоваться множественно, а не городить паттерн враппер в каждом классе.
...
Рейтинг: 0 / 0
28.04.2013, 12:17:25
    #38242948
ozzmosis
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
Usman
Код: sql
1.
2.
3.
4.
5.
6.
...
public void getToFly(Flying flying) { // Полиморфизм
    flying.rising();
    ...
    flying.landing();
}

Что-то я не понял, как это правильно прикручивать к вышеприведенной схеме.
Если этот метод (getToFly(Flying flying)) всаживать в каждый класс (Eagle, Bat), то полиморфизма тут нет совсем. Я сделал абстрактный класс FlyingCreature с этим методом, а от него наследуют уже Eagle & Bat (помимо того, что они еще и/фейсы реализуют).

Но требование к обоим классам реализовать интерфейсы приводит вот к такому уродству:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
package inheritance2;
interface Animals {
    void move();
    void eat();
    void reproduce();
}

interface Mammals extends Animals {
    void produceMilk();
}

interface Flying {
    void rising();
    void lowering();
    void accelerating();
    void turning(int azimut);
    void landing();
}

interface Birds extends Animals, Flying { // Пока еще абстрактный класс
    void makeNest();
    void oviposit();
}
abstract class FlyingCreature {
    public static void getToFly(Flying flying) { // Полиморфизм
        flying.rising();
        flying.landing();
    }
}

class Eagle extends FlyingCreature implements Birds { // Конкретный класс

    @Override
    public void makeNest() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void oviposit() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void move() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void eat() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void reproduce() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void rising() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void lowering() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void accelerating() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void turning(int azimut) {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void landing() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }
}


class Bat extends FlyingCreature implements Mammals, Flying { // Конкретный класс

    @Override
    public void produceMilk() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void move() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void eat() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void reproduce() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void rising() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void lowering() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void accelerating() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void turning(int azimut) {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void landing() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

}

// test
public class AnimalKingdom {
    public static void main(String[] args) {
       new  AnimalKingdom().run();
    }
    private void run() {
       Flying e = new Eagle();
       Flying b = new Bat();

       Eagle.getToFly(e);
       Bat.getToFly(b);
    }
}

Output:
Код: plaintext
1.
2.
3.
inheritance2.Eagle.rising
inheritance2.Eagle.landing
inheritance2.Bat.rising
inheritance2.Bat.landing
100500 строк продублированного кода :(

А если же переносить как можно больше реализации внутрь абстрактного класса (FlyingCreature), то в классах Eagle & Bat остается уже лишь самое необходимое и специфическое для них, но зато FlyingCreature становится мусорным ведром: в нём будут методы сразу от двух и/ф, Animals и Flying. И всё потому, что наследовать можно только от одного класса.
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
package inheritance2;
interface Animals {
    void move();
    void eat();
    void reproduce();
}

interface Mammals extends Animals {
    void produceMilk();
}

interface Flying {
    void rising();
    void lowering();
    void accelerating();
    void turning(int azimut);
    void landing();
}

interface Birds extends Animals, Flying { // Пока еще абстрактный класс
    void makeNest();
    void oviposit();
}
abstract class FlyingCreature implements Animals, Flying {

    // implementation of methodss in Animals interface:
    @Override
    public void move() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void eat() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void reproduce() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    // implementation of methodss in Flying interface:
    @Override
    public void rising() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void lowering() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void accelerating() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void turning(int azimut) {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void landing() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    public static void getToFly(Flying flying) { // Полиморфизм
        flying.rising();
        flying.landing();
    }

}

class Eagle extends FlyingCreature implements Birds { // Конкретный класс

    @Override
    public void makeNest() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }

    @Override
    public void oviposit() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }
}

class Bat extends FlyingCreature implements Mammals{ // Конкретный класс

    @Override
    public void produceMilk() {
        StackTraceElement me = Thread.currentThread().getStackTrace()[1];
        System.out.println(me.getClassName()+"."+me.getMethodName());
    }
}

// test
public class AnimalKingdom {
    public static void main(String[] args) {
       new  AnimalKingdom().run();
    }
    private void run() {
       Flying e = new Eagle();
       Flying b = new Bat();

       Eagle.getToFly(e);
       Bat.getToFly(b);
    }
}

...
Рейтинг: 0 / 0
28.04.2013, 12:30:23
    #38242953
ozzmosis
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
СеноПринципиальная ошибка заключается в том, что класс никогда не должен наследоваться от двух классов. Точка.
Если вы приходите к такой необходимости, значит у вас где-то ошибка в дизайне, и вы не понимаете сущность проектируемого класса.* летучая мышь, которая есть млекопитающее, но может таки летать "аки орёл";
* эвглена зелёная (Euglena viridis), имеющая характеристики и животного и растения;
* шахматный ферзь, унаследовавший ходы от ладьи и слона;
* любой ребёнок, унаследовавший хромосомы отца и матери
- это всё тоже ошибки дизайна ?
...
Рейтинг: 0 / 0
28.04.2013, 12:32:48
    #38242954
ozzmosis
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
chabapokПроблема множественного наследования - методы с одинаковыми именами, которые реализованы в классах-предках.компилятор разве не может увидеть этих предков и заставить прописать в коде "квалификатор" вызываемого метода ? ну, то есть просто заставить указать класс-предок, от которого надо взять в данном классе метод - это разве непосильная задача для компилятора ?
...
Рейтинг: 0 / 0
28.04.2013, 12:50:43
    #38242963
Сено
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
chabapok ,
Если честно, вообще не могу себе представить use case, в котором может быть польза от множественного наследования от PropertyChangeSupport .
...
Рейтинг: 0 / 0
28.04.2013, 12:53:30
    #38242965
Сено
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
ozzmosisСеноПринципиальная ошибка заключается в том, что класс никогда не должен наследоваться от двух классов. Точка.
Если вы приходите к такой необходимости, значит у вас где-то ошибка в дизайне, и вы не понимаете сущность проектируемого класса.* летучая мышь, которая есть млекопитающее, но может таки летать "аки орёл";
* эвглена зелёная (Euglena viridis), имеющая характеристики и животного и растения;
* шахматный ферзь, унаследовавший ходы от ладьи и слона;
* любой ребёнок, унаследовавший хромосомы отца и матери
- это всё тоже ошибки дизайна ?Это не ошибки дизайна. Это вообще не дизайн. Это требования. А вот дизайнить подобные вещи через множественное наследование - ошибка.
Один из основополагающих принципов ООП - Composition over inheritance . Как и все остальные принципы, это не догма, но на ваши вопросы отвечает.
...
Рейтинг: 0 / 0
28.04.2013, 13:04:40
    #38242971
chabapok
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
Может, в плюсах так и делает, но это довольно сильно запутывает. Там тогда возникают проблемы с уже написанным кодом - передаем "древовидно-наследованый" обьект в функцию, а в ней не конкретизировано какую цепочку суперкласса использовать - компилятор будет ругаться, например. В яве подобное тоже есть с интерфейсами. Если сделать class Foo implements IA, IB. А потом завести перегруженную функцию func(IA val){...} func(IB val), то компилятор и в яве потребует привести к интерфейсу, если мы передадим туда обьект Foo.

ни один из нормальных языков не использует множественно наследование, это не просто так, а потому что оно показало себя плохо, и как от таких проблем уходить - еще не придумали.
...
Рейтинг: 0 / 0
28.04.2013, 13:15:28
    #38242979
ozzmosis
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
СеноЭто требования. А вот дизайнить подобные вещи через множественное наследование - ошибка.А через интерфейсы - коряво как-то получается. Я вот попробовал выше - ну уродство же форменное.
Вы можете показать, как поизящнее сделать, чтобы сдублированного кода не было и в тоже время чтобы классы, реализующие несколько и/ф, не напоминали свалку всего и вся ?
...
Рейтинг: 0 / 0
28.04.2013, 13:18:00
    #38242980
Сено
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
ozzmosisСеноЭто требования. А вот дизайнить подобные вещи через множественное наследование - ошибка.А через интерфейсы - коряво как-то получается. Я вот попробовал выше - ну уродство же форменное.
Вы можете показать, как поизящнее сделать, чтобы сдублированного кода не было и в тоже время чтобы классы, реализующие несколько и/ф, не напоминали свалку всего и вся ?В ссылке, что я привел выше, есть ответ на ваш вопрос. Посмотрите UML схему.
...
Рейтинг: 0 / 0
28.04.2013, 13:41:52
    #38242993
vromanov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
СеноПринципиальная ошибка заключается в том, что класс никогда не должен наследоваться от двух классов. Точка.
Если вы приходите к такой необходимости, значит у вас где-то ошибка в дизайне, и вы не понимаете сущность проектируемого класса.
Или вам просто надо поменять язык на другой, где это возможно
...
Рейтинг: 0 / 0
28.04.2013, 14:33:33
    #38243016
J.Serge
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
ozzmosis* лeтучая мышь, которая есть млекопитающее, но может таки летать "аки орёл";
* эвглена зелёная (Euglena viridis), имеющая характеристики и животного и растения;
* шахматный ферзь, унаследовавший ходы от ладьи и слона;
* любой ребёнок, унаследовавший хромосомы отца и матери
- это всё тоже ошибки дизайна ?

Еcли у тебя будет class Mother и class Child extends Mother - таки-да, ошибка дизайна иерархии классов
Ферзь, строго говоря, тоже не является ни ладьей, ни слоном, хоть и ходит, как они. Что с того?
...
Рейтинг: 0 / 0
28.04.2013, 14:57:28
    #38243028
chabapok
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
Сено chabapok ,
Если честно, вообще не могу себе представить use case, в котором может быть польза от множественного наследования от PropertyChangeSupport .

А это потому, что вы неправильно меня поняли (или я слишком мало уделил внимания пояснению). Что есть "множественное наследование от pcs" - не знаю, но мной имелось в виду - pcs как один из наследников.

pcs даже сейчас не очень красив. Традиционная методика - заводить поле типа PropertyChangeSupport, и реализовывать addListener, removeListener и тд. И делать это в каждом долбаном классе, который генерит эвенты.
Как результат - жуткое и громоздкое повторение кода. Тупо сидишь -- ctrl-c, ctrl-v.

Выглядит это как костыль, и по большому счету им является.
правильно было бы просто сделать extends PropertyChangeSupport и все - весь твой класс начинает уметь работать с евентами.
...
Рейтинг: 0 / 0
28.04.2013, 15:38:55
    #38243060
stratilat19
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
ozzmosisНо мыши эти, как известно, обладают некоторыми возможностями птиц, а именно: летаютложный начальный посыл не может привести к правильному выводу
почему полёт объявлен свойством именно птиц?

во 1-х не все птицы летают
во 2-х, летать также умеют насекомые, самолеты, пыльца, астероиды и тарелка супа

почему свойство полёта хочется позаимствовать непременно у птиц?
потому что они по размерам похожи на мышь?
потому что на деревьях живут?
а собственно полёт тут при чём?

если нужно свойство полёта, то его нужно брать из интерфейса или абстрактного класса или класса-адаптера полёта, не отягощенного чужими подробностями
класс-адаптер - возможный вариант избежать дублирования кода, там где это удобно
выше предлагался FlyingCreature, можно согласиться, и даже унаследовать FlyingCreature от FlyingObject

в некоторых случаях больше всего подошли бы аспекты из аспектно-ориентированного программирования, они как раз собирают одинаковые события у неродственных классов
...
Рейтинг: 0 / 0
28.04.2013, 16:01:11
    #38243075
ozzmosis
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
СеноВ ссылке, что я привел выше, есть ответ на ваш вопрос. Посмотрите UML схему.Схему посмотрел, утомили эти водоплавающие. Текст там гораздо понятнее, особливо пример на C#.
Но в этой статье также признаётся проблема избыточного кода: http://en.wikipedia.org/wiki/Composition_over_inheritance One drawback to using composition in place of inheritance is that all of the methods being provided by the composed classes must be implemented in the derived class, even if they are only forwarding methods . In contrast, inheritance does not require all of a base class's methods to be re-implemented within the derived class. <...> This drawback can be avoided by using traits.Что такое `traits` в яве ?
...
Рейтинг: 0 / 0
28.04.2013, 16:03:55
    #38243078
ozzmosis
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
stratilat19выше предлагался FlyingCreature, можно согласиться, и даже унаследовать FlyingCreature от FlyingObjectпример покажете ? или под "выше предлагался" имеется в виду второй фрагмент отсюда ?
...
Рейтинг: 0 / 0
28.04.2013, 18:02:46
    #38243127
ozzmosis
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
J.SergeФерзь, строго говоря, тоже не является ни ладьей, ни слоном, хоть и ходит, как они. Что с того?С шахматными фигурами еще нагляднее будет, чем с летучими мышами.
Допустим, есть два интерфейса:
1) MovableOnLines - возможность фигуры ходить по вертикалям или горизонталям
2) MovableOnDiags - возможность фигуры ходить по диагоналям
Требуется реализовать методы перемещения трёх шахматных фигур: слона (Bishop), ладьи (Rook) и ферзя (queen), с соблюдением правил игры и ограничений размеров шахматной доски.
Если делать один абстрактный класс, реализующий оба интерфейса, то в него надо заталкивать проверку на то, какая фигура сейчас ходит. Дабы не было слонов, летающих по горизонталям/вертикалям (и аналогично про ладей). Ну и реализацию каждого из методов обоих и/фейсов надо делать.
Получится примерно вот это:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
package inheritance2;

import java.util.logging.Level;
import java.util.logging.Logger;

interface MovableOnLines {
    void lineMoving(int where, int steps);
}
interface MovableOnDiags {
    void diagMoving(int where, int steps);
}

abstract class Figure implements MovableOnLines, MovableOnDiags {

    final int Y_MIN=1;
    final int X_MIN=1;
    final int Y_MAX=8;
    final int X_MAX=8;
    int xPos;
    int yPos;

    Figure(int xInitialPos, int yInitialPos) {
        xPos = xInitialPos;
        yPos = yInitialPos;
    }
    public String toString() {
        return this.getClass().getName()+"["+this.xPos+","+this.yPos+"]";
    }
    @Override
    public void lineMoving(int where, int steps)  {
        String msg = "where="+where+", steps="+steps+": bad new coords";
        System.out.println(this.toString()+", going move on LINE direction for "+steps+" cells.");
        // where: 1=up, 2=right, 3=down, 4=left
        try {
            if (this instanceof Bishop) {
                throw new Exception("Moving type not applicabe to this figure");
            }
            switch (where) {
                case 1:
                    if (yPos + steps > Y_MAX ) {throw new Exception(msg);}
                    else { yPos += steps; }
                    break;
                case 2:
                    if (xPos + steps > X_MAX ) {throw new Exception(msg);}
                    else { xPos += steps; }
                    break;
                case 3:
                    if (yPos - steps < 1 ) {throw new Exception(msg);}
                    else { yPos -= steps; }
                    break;
                case 4:
                    if (xPos - steps < 1 ) {throw new Exception(msg);}
                    else { xPos -= steps; }
                    break;
            }
        } catch (Exception ex) {
            Logger.getLogger(Figure.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println(this.toString()+" - new position of figure");
    }
    @Override
    public void diagMoving(int where, int steps) {
        System.out.println(this.toString()+", going move on DIAG direction for "+steps+" cells.");
        // where: 1=up_right, 2=up_left, 3=down_left, 4=down_right
        String msg = "where="+where+", steps="+steps+": bad new coords";
        try {
            if (this instanceof Rook) {
                throw new Exception("Moving type not applicabe to this figure");
            }

            switch (where) {
                case 1: // up_right
                    if (yPos + steps > Y_MAX || xPos + steps > X_MAX ) {throw new Exception(msg);}
                    else { yPos += steps; xPos += steps; }
                    break;
                case 2: // up_left
                    if (yPos + steps > Y_MAX || xPos - steps < 1 ) {throw new Exception(msg);}
                    else { yPos += steps; xPos -= steps; }
                    break;
                case 3: // down_left
                    if (xPos - steps < 1 || yPos - steps < 1 ) {throw new Exception(msg);}
                    else { xPos -= steps; yPos -= steps; }
                    break;
                case 4: // down_right
                    if (xPos + steps > X_MAX || yPos-steps < 1) {throw new Exception(msg);}
                    else { xPos += steps; yPos -= steps; }
                    break;
            }
        } catch (Exception ex) {
            Logger.getLogger(Figure.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println(this.toString()+" - new position of figure");
    }
} // end of abstract class Figure
class Bishop extends Figure {
    Bishop (int xInitialPos, int yInitialPos) {
        super(xInitialPos, yInitialPos);
    }
}
class Rook extends Figure {
    Rook (int xInitialPos, int yInitialPos) {
        super(xInitialPos, yInitialPos);
    }
}
class Queen extends Figure {
    Queen (int xInitialPos, int yInitialPos) {
        super(xInitialPos, yInitialPos);
    }
}
public class ChessFigures {
    public static void main(String[] args) {
        new ChessFigures().run();
    }
    private void run() {
        Rook r = new Rook(3, 4);
        //r.diagMoving(1, 3); // exception: "Moving type not applicabe to this figure"
        r.lineMoving(3, 2); // OK

        Bishop b = new Bishop(4,6);
        //b.lineMoving(2, 2); // exception: "Moving type not applicabe to this figure"
        b.diagMoving(3, -1); // OK

        Queen q = new Queen(5,5);
        q.lineMoving(1, 3); // OK
        q.diagMoving(2, -2); // OK
    }

} // end of class ChessFigures

Про абстрактный класс (Figure) в итоге можно сказать просто: "и швец, и жнец, и на дуде игрец". Хотя во всех ководствах по ООП настоятельно рекомендуется не делать классы, выполняющие сразу несколько функций. Классы должны быть "специалистами узкого профиля", а тут всё наоборот.

Если же попытаться разделить большой класс Figure на два: один только для констант и конструирования объектов типа "Фигура", а второй (FigureMover) для реализации двух алгоритмов перемещения фигур, то получим всё равно "многабукф" в каждом конкретном классе (Rook, Bishop, Queen): там будут "транзитные" методов, требуемых интерфейсами (FigureMover.diagMoving и .linemoving - в каждом из конкретных классов). Грустно
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
package inheritance2;

import java.util.logging.Level;
import java.util.logging.Logger;

interface MovableOnLines2 {
    void lineMoving(int where, int steps);
}
interface MovableOnDiags2{
    void diagMoving(int where, int steps);
}
abstract class Figure2 {
    final static int Y_MIN=1;
    final static int X_MIN=1;
    final static int Y_MAX=8;
    final static int X_MAX=8;
    int xPos;
    int yPos;
    Figure2(int xInitialPos, int yInitialPos) {
        xPos = xInitialPos;
        yPos = yInitialPos;
    }
    public String toString() {
        return this.getClass().getName()+"["+this.xPos+","+this.yPos+"]";
    }
}

abstract class FigureMover {
   static void lineMoving(Figure2 f, int where, int steps)  {
        String msg = "where="+where+", steps="+steps+": bad new coords";
        System.out.println(f.toString()+", going move on LINE direction for "+steps+" cells.");
        try {
            switch (where) {
                case 1:
                    if (f.yPos + steps > Figure2.Y_MAX ) {throw new Exception(msg);}
                    else { f.yPos += steps; }
                    break;
                case 2:
                    if (f.xPos + steps > Figure2.X_MAX ) {throw new Exception(msg);}
                    else { f.xPos += steps; }
                    break;
                case 3:
                    if (f.yPos - steps < 1 ) {throw new Exception(msg);}
                    else { f.yPos -= steps; }
                    break;
                case 4:
                    if (f.xPos - steps < 1 ) {throw new Exception(msg);}
                    else { f.xPos -= steps; }
                    break;
            }
        } catch (Exception ex) {
           Logger.getLogger(FigureMover.class.getName()).log(Level.SEVERE, null, ex);
        }
   }

    static void diagMoving(Figure2 f, int where, int steps) {

        // where: 1=up_right, 2=up_left, 3=down_left, 4=down_right
        String msg = "where="+where+", steps="+steps+": bad new coords";
        System.out.println(f.toString()+", going move on DIAG direction for "+steps+" cells.");
        try {
            switch (where) {
                case 1: // up_right
                    if (f.yPos + steps > Figure2.Y_MAX || f.xPos + steps > Figure2.X_MAX ) {throw new Exception(msg);}
                    else { f.yPos += steps; f.xPos += steps; }
                    break;
                case 2: // up_left
                    if (f.yPos + steps > Figure2.Y_MAX || f.xPos - steps < 1 ) {throw new Exception(msg);}
                    else { f.yPos += steps; f.xPos -= steps; }
                    break;
                case 3: // down_left
                    if (f.xPos - steps < 1 || f.yPos - steps < 1 ) {throw new Exception(msg);}
                    else { f.xPos -= steps; f.yPos -= steps; }
                    break;
                case 4: // down_right
                    if (f.xPos + steps > Figure2.X_MAX || f.yPos-steps < 1) {throw new Exception(msg);}
                    else { f.xPos += steps; f.yPos -= steps; }
                    break;
            }
        } catch (Exception ex) {
            Logger.getLogger(FigureMover.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

class Bishop2 extends Figure2 implements MovableOnDiags2 {
    Bishop2 (int xInitialPos, int yInitialPos) {
        super(xInitialPos, yInitialPos);
    }
    @Override
    public void diagMoving(int where, int steps) {
        FigureMover.diagMoving(this, where, steps); // вызов-"транзит"
    }
}
class Rook2 extends Figure2 implements MovableOnLines2 {
    Rook2 (int xInitialPos, int yInitialPos) {
        super(xInitialPos, yInitialPos);
    }
    @Override
    public void lineMoving(int where, int steps) {
        FigureMover.lineMoving(this, where, steps); // вызов-"транзит"
    }
}
class Queen2 extends Figure2 implements MovableOnDiags2, MovableOnLines2{
    Queen2 (int xInitialPos, int yInitialPos) {
        super(xInitialPos, yInitialPos);
    }
    @Override
    public void lineMoving(int where, int steps) {
        FigureMover.lineMoving(this, where, steps); // вызов-"транзит"
    }

    @Override
    public void diagMoving(int where, int steps) {
        FigureMover.diagMoving(this, where, steps); // вызов-"транзит"
    }
}

// test
public class ChessFigures2 {
    public static void main(String[] args) {
        new ChessFigures2().run();
    }
    private void run() {
        Rook2 r = new Rook2(3, 4);
        // r.diagMoving(1, 3); // not compiled
        r.lineMoving(3, 2); // OK

        Bishop2 b = new Bishop2(4,6);
        // b.lineMoving(2, 2); // not compiled
        b.diagMoving(3, -1); // OK

        Queen2 q = new Queen2(5,5);
        q.lineMoving(1, 3); // OK
        q.diagMoving(2, -2); // OK
    }

} // end of class ChessFigures2

...
Рейтинг: 0 / 0
28.04.2013, 18:05:59
    #38243130
ozzmosis
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
PS. я чего хотел узнать-то: вот говорится, что множественное наследование плохо из-за "проблемы ромба". А что, авторкомпилятор разве не может увидеть (при множ. наследовании) этих предков и заставить прописать в коде "квалификатор" вызываемого метода ? ну, то есть просто заставить указать класс-предок, от которого надо взять в данном классе метод - это разве непосильная задача для компилятора ?
...
Рейтинг: 0 / 0
28.04.2013, 18:30:09
    #38243142
dominator
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
ozzmosis,

Для того что бы все встало на свои места нужно отделить наследование поведения (интерфейса) от наследования реализации поведения (класс). При множественном наследовании интерфейсов (гарантия наличия поведения) проблем не происходит, а вот если есть множественное наследование классов, то возникает неоднозначность.
...
Рейтинг: 0 / 0
28.04.2013, 19:39:19
    #38243181
stratilat19
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
ozzmosisЕсли делать один абстрактный класс, реализующий оба интерфейса, то в него надо заталкивать проверку на то, какая фигура сейчас ходит.нет, методологически неверно
делегирование ответственности однонаправленное
класс-предок не должен делать никаких предположений о потребностях потомков и вообще знать об их существовании
иначе появление новых потомков будет вынуждать к модификации предка, а вместе с ним и к модификации ранее существовавших потомков
наоборот, только потомки знают о существовании предка и модифицируют его поведение
...
Рейтинг: 0 / 0
28.04.2013, 19:59:05
    #38243194
Сено
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
ozzmosis,

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
// Абстракция позиции.
class Position {
    int x;
    int y;
}

// Фигура.
abstract class Figure {
    // Текущая позиция.
    Position curPos; 

    // Куда ее можно двигать.
    abstract Set<Position> availableMovements(); 

    // Двинуть.
    void move(Position pos) throws IllegalMovementExeption {
        if (availableMovements().contains(pos))
            curPos = pos;
        else
            throw new IllegalMovementException(this, pos);
    }

    // Куда можно идти по прямым относительно текущей позиции.
    static Set<Position> orthogonalMovements(Position pos) {
        ...
    }

    // Куда можно идти по диагонали относительно текущей позиции.
    static Set<Position> diagonalMovements(Position pos) {
        ...
    }
}

// Реализации.
class Bishop extends Figure {
    Set<Position> availableMovements() {
        return diagonalMovements(curPos);
    }
}

class Rook extends Figure {
    Set<Position> availableMovements() {
        return orthogonalMovements(curPos);
    }
}

class Queen extends Figure {
    Set<Position> availableMovements() {
        return orthogonalMovements(curPos).addAll(diagonalMovement(curPos));
    }
}



Можно дискутировать на тему статических методов, и т.п., но общая идея должна быть понятна. Никакого дублирования кода, все нормально.
...
Рейтинг: 0 / 0
28.04.2013, 22:29:26
    #38243276
just_vladimir
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
Если честно, то не могу себе представить ни одной реальной задачи, где может потребоваться множественное наследование. Также не сталкивался с ситуациями, где есть иерархии доменных объектов по типу того, что любят приводить в теоретических книжках, подобно иерархии приведенной выше про млекопитающих/птиц и т.д. Впрочем, имхо, вообще всяческое ООП, DDD излишне переоцененные подходы порой слабосвязанные с реальностью :-)
А так вброшу говен на вентилятор, в Java 8 есть множественное наследование и называется оно interface default implementation :-)
...
Рейтинг: 0 / 0
29.04.2013, 08:19:56
    #38243428
Сено
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Интерфейсы вместо множественного наследования: в чём выгода от них ?
just_vladimirЕсли честно, то не могу себе представить ни одной реальной задачи, где может потребоваться множественное наследование. Также не сталкивался с ситуациями, где есть иерархии доменных объектов по типу того, что любят приводить в теоретических книжках, подобно иерархии приведенной выше про млекопитающих/птиц и т.д. Впрочем, имхо, вообще всяческое ООП, DDD излишне переоцененные подходы порой слабосвязанные с реальностью :-)В промышленном порграммировании, когда все приложение это что-то вроде "доменные объекты + сервисы + веб-морда", такое действительно встречается редко. В таких типовых задачах обычно практически нет мест, где можно развернуться в ООП.
А в системном программировании, где понятие "доменная модель" в принципе отсутствует, а есть сотни и тысячи абстракций, которые зависят друг от друга произвольным образом - там такое сплошь и рядом. "Летучая мышь" - это цветочки.
...
Рейтинг: 0 / 0
Форумы / Java [игнор отключен] [закрыт для гостей] / Интерфейсы вместо множественного наследования: в чём выгода от них ? / 25 сообщений из 30, страница 1 из 2
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]