2016-04-17 3 views
1

Необходимо использовать следующий контекст: Цель этого способа кодирования состоит в том, чтобы избежать if - else операторов и instanceof; которая всегда плохая идея.Наследование Java не ведет себя так, как ожидалось

У меня есть 3 класса со следующими подписями:

abstract class A {} 
class B extends A {} 
class C extends A {} 

Тогда у меня есть еще один класс со следующей структурой:

class MyClass { 
    private final A model; 

    public MyClass(A m) { 
     this.model = m; 
    } 

    public void doSomething() { 
     System.out.println(this.model instanceof C); //TRUE!! 
     execute(this.model); 
    } 

    private void execute(A m) { 
     System.out.println("noo"); 
    } 

    private void execute(C m) { 
     System.out.println("yay"); 
    } 
} 

И, наконец, содержимое моего основного:

public static void main(String... args) { 
    C mod = new C(); 
    MyClass myClass = new MyClass(mod); 
    myClass.doSomething(); 
} 

Теперь проблема; метод execute (C) никогда не выполняется, это всегда метод execute (A). Как я могу это решить? Я не могу изменить подпись метода execute (A) для выполнения (B), так как это даст ошибку, говоря, что java «не может разрешить выполнение метода (A)» в MyClass # doSomething.

+5

Это может быть решена (в некоторой стоимости) с помощью [шаблон посетитель] (https://en.wikipedia.org/wiki/Visitor_pattern). –

+0

'A model' ->' execute (A) ', в чем проблема? – Andrew

+0

@ OliverCharlesworth еще не слышал об этом; кажется довольно интересным. – thepieterdc

ответ

3

Ваш код иллюстрирует разницу между статическим и динамическим типом объекта. Статический тип - это то, что известно компилятору; динамический тип - это то, что на самом деле существует во время выполнения.

Статический тип вашего model поля A:

private final A model; 

То есть, компилятор знает, что A сам или некоторые его реализаций собирается быть назначен model. Компилятор ничего не знает, поэтому, когда дело доходит до выбора между execute(A m) и execute(C m), его единственным выбором является execute(A m). Метод разрешен для статического типа объекта.

instanceof, с другой стороны, понимает динамический тип. Он может сказать, что значение model установлено на C, и, следовательно, сообщает true в распечатке.

Вы можете решить, добавив метод A и опрокинув его в B и C на пути к правильной execute:

abstract class A { 
    public abstract void callExecute(MyClass back); 
} 
class B extends A { 
    public void callExecute(MyClass back) { 
     back.execute(this); 
    } 
} 
class C extends A { 
    public void callExecute(MyClass back) { 
     back.execute(this); 
    } 
} 

class MyClass { 
    private final A model; 

    public MyClass(A m) { 
     this.model = m; 
    } 

    public void doSomething() { 
     System.out.println(this.model instanceof C); //TRUE!! 
     model.callExecute(this.model); 
    } 

    public void execute(B m) { 
     System.out.println("noo"); 
    } 

    public void execute(C m) { 
     System.out.println("yay"); 
    } 
} 

Обратите внимание, что обе реализации называют

back.execute(this); 

Однако, реализация внутри B имеет this типа B, а реализация внутри C имеет this типа C, поэтому вызовы маршрутизируются в разные перегрузки execute методом MyClass.

Я не могу изменить подпись метода execute(A) к execute(B)

Также отметим, что теперь вы можете (и должны) сделать это, тоже, потому что обратные вызовы выполняются для правильной перегрузки в зависимости от типа this.

5

Перегрузки метода разрешены во время компиляции. Во время компиляции тип m равен A, поэтому execute(A m) получает исполнение.

Кроме того, частные методы не являются допустимыми.

Решение состоит в использовании шаблона посетителя, предложенного @OliverCharlesworth.

+1

Но вопрос: «Как мне это решить?» ... –

+0

@OliverCharlesworth Согласен, и шаблон посетителя - это ответ, как вы и предполагали. – EJP

+1

Принял другой ответ, потому что он более подробно, но ваш тоже правильный. – thepieterdc

2

Перегрузка метода - это полиморфизм времени компиляции. Таким образом, для метода вызова execute(C) вам необходимо определить вашу модель как class C. Лучше определить метод execute() в class A и переопределить его в подклассах.

abstract class A { 
    abstract void execute(); 
} 
class B extends A { 
    public void execute(){}; 
} 
class C extends A { 
    public void execute(){}; 
} 

И потом:

class MyClass { 
    private final A model; 

public void doSomething() { 
    model.execute(); 
} 

Это гораздо лучше использовать полиморфизм, чтобы избежать, если-иначе заявления и InstanceOf проверки

-2

Вы отправляете объект типа C, как объект типа A в конструкторе (вы сделали upcasting) и присвоили ему ссылку на тип A (что приведет к вызову только метода execute (A)). Вы можете проверить, является ли объект экземпляром C и в зависимости от результата, вызовите желаемый метод. Вы можете сделать это, как этот

public void doSomething(){ 
     System.out.println(model instanceof C); 
     if (model instanceof C) execute((C)model); 
     else 
      execute(model); 
    } 
+0

Никогда не используйте instanceof – thepieterdc

+0

Я бы не сказал никогда, но я согласен, что есть лучший способ сделать это :) –