2013-08-07 2 views
1

Пытается реорганизовать код, и я наткнулся на проблеме, которая может быть продемонстрирована с помощью следующего кода:метод перегрузки, почему супертип должен быть отлит

public static abstract class Animal { 

    public abstract void attack(Animal other); 
} 

public static class Cat extends Animal { 

    @Override 
    public void attack(Animal other) { 
     catAttack(other); <-------- Problem here 
    } 

    private void catAttack(Cat other) { 
     // Maybe a meow showdown wins the fight, no need to get physical 
    } 

    private void catAttack(Dog other) { 
     // Dogs are dangerous, run! 
    } 

} 

public static class Dog extends Animal { 

    @Override 
    public void attack(Animal other) { 

    } 

} 

Почему это специфический типа other в catAttack(other) не может можно найти так, чтобы можно было назвать наиболее специфический метод? Вместо этого проверка должно быть сделано:

if (other instanceof Cat) 
catAttack((Cat) other); 
if (other instanceof Dog) 
catAttack((Dog) other); 

Edit: Вопрос был, возможно, немного неясно, вот пояснение я отправил как комментарий к ответу:

кошка может атаковать собаку или другой кот, который оба являются животными, мне любопытно, почему бремя бросания другого животного на собаку или кошку на меня, почему я не пытаюсь посмотреть, является ли это кошкой или собакой, потому что у меня есть два метода, которые соответствуют либо кошке, либо собаке, а затем бросают ошибку, если вместо этого это был слон?

+0

, связанный с двойным удалением - http://en.wikipedia.org/wiki/Double_dispatch – Jayan

ответ

1

кошка может напасть на собаку или другую кошку, которая, как случается быть животными, мне интересно, почему бремя отливки другое животное ко мне или собаке или кошке на мне, почему я не пытаюсь понять, является ли это кошкой или собакой, потому что у меня есть два метода, которые соответствуют кошке или собаке, а затем бросают ошибку, если это был слон ?

К сожалению, все, что вы предлагаете, не совпадает с текущей системой соответствия методам java-компилятора.

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

В качестве примера, рассмотрим:

float b = 4.6f; 
int a = 5 + b; 

Здесь компилятор знает, что Ь имеет тип поплавка, знает, что оператор + имеет запись для (INT + всплывают = поплавка), и знает, что имеет введите целое число. Таким образом, он указывает, что выражение имеет тип float и должно быть отнесено к целому числу, которое должно быть присвоено a. Это преобразование не разрешено в java, поэтому оно выводит ошибку, чтобы предотвратить потерю точности.

С объектами это в основном то же самое, зная, что классы, более глубокие по иерархии, могут быть «преобразованы» в их мелких родителей, но не наоборот.

Таким образом, в вашем случае:

public void attack(Animal other) { 
    catAttack(other); <-------- Problem here 
} 

private void catAttack(Cat other) { 
    // Maybe a meow showdown wins the fight, no need to get physical 
} 

private void catAttack(Dog other) { 
    // Dogs are dangerous, run! 
} 

Java-компилятор не может вывести реальный класс переменной other в методе attack(Animal other) без выполнения всей программы. Он может знать только, что «указатель» имеет тип Animal, поэтому реальный класс может быть чем-либо, что расширяет животное, и он должен проверять тип (и статически разрешать вызов метода) ваш вызов catAttack(other), зная только это.

Поскольку метод catAttack(Animal) отсутствует, java-компилятор не может гарантировать безопасность типа для вашего кода и выводит ошибку.

Что вы можете сделать вместо этого, является:

public static abstract class Animal { 

    public abstract void attack(Animal other); 

    public abstract void beAttackedByCat(Animal cat); 
    public abstract void beAttackedByDog(Animal dog); 
} 

public static class Cat extends Animal { 

    @Override 
    public void attack(Animal other) { 
     other.beAttackedByCat(this); 
    } 

    public void beAttackedByCat(Animal cat){ // cat > cat } 
    public void beAttackedByDog(Animal dog){ // dog > cat } 
} 

public static class Dog extends Animal { 

    @Override 
    public void attack(Animal other) { 
     other.beAttackedByDog(this); 
    } 

    public void beAttackedByCat(Animal cat){ // cat > dog } 
    public void beAttackedByDog(Animal dog){ // dog > dog } 
} 

Что, вероятно, милях от идеального решения, но только, чтобы дать вам представление о том, что вы можете сделать и то, что вы не можете.

+0

Я действительно рассматривал шаблон, который, вероятно, был связан с моим дизайном, я просто не хотел добавлять методы в суперкласс, поскольку я создал новые подклассы или продолжаю добавлять, если instanceOf в моих подклассах, я думал о создании другого метода выглядел лучше и позволял упростить переписывание кода. – arynaq

+0

Да, я прекрасно понимаю вашу мысль. К сожалению, Java не поддерживает так называемую двойную отправку, поэтому, возможно, идеальное решение довольно сложно найти. Возможно, вы могли бы подумать о некоторых способах решения этой проблемы с использованием рефлексии, чтобы помочь вам не добавлять вещи всякий раз, когда определяется новый подкласс (обычно я использую отражение для таких проблем), но я еще не думал о деталях ^^ – Lake

0

Проще говоря.Потому что у вас нет метода ниже в вашем классе Cat.

private void catAttack(Animal other) { 
    } 

Edit: Помните, что вы можете присвоить ссылку суперкласса к подклассу directly.This на самом деле то, что вы doing.Suppose Если вы что-то вроде ниже

Animal animal=new Cat(); 

И тогда вы звоните animal.Attack(animal) для вызова метода CatAttack вы можете сделать

if(animal instanceOf Cat) 
{ 
catAttack((Cat)animal) 
} 

Поскольку животное Ссылки на сайты e содержит объект cat, вы можете сделать кастинг явно.

+0

Хммм да, но кошка может напасть на собаку или другую кошку, которая, как и быть, является животными, мне любопытно, почему бремя литье другого животного на собаку или кошку на меня, почему я не пытаюсь проверить, является ли она кошкой или собакой, потому что у меня есть два метода, которые соответствуют кошке или собаке, а затем выдают ошибку, если это слон вместо этого? – arynaq

+0

То, что вы хотите сделать, может быть проверено только во время выполнения. Однако Java предпочитает проверять такие вещи во время компиляции. – Naetmul

+0

@arynaq Прежде всего, зачем вам нужны эти отдельные методы catAttack(), dogAttack()? Просто переопределите метод attack() в классе Cat, Dog и реализуйте там специфическое поведение атаки. – Algorithmist

0

Я уверен, что в java вы не можете создать статический класс, если это не внутренний класс; но это касается, помимо точки вашего вопроса.

Вы должны выполнить бросок, потому что вы не можете найти подходящий прототип метода private void catAttack(Cat other) или private void catAttack(Dog other) с catAttach(Animal other).

Чтобы обойти это, вы программируете суперкласс или интерфейс.

Обычно это делается таким образом:

public interface Animal { 
    public void attack(Animal other); 
} 

public class Cat implements Animal { 
    public void attack(Animal other) { 
     /* TODO: implement attack method */ 
    } 
} 

public class Dog implements Animal { 
    public void attack(Animal other) { 
     /* TODO: implement attack method */ 
    } 
} 

Если вы думаете об этом, такой подход имеет смысл. A Cat в этом случае имеет свою собственную атаку.Вы можете сделать что-то вроде:

public class Cat implements Animal { 
    public void attack(Animal other) { 
     if (this.equals(other)) { 
      System.out.println("The Cat won't fight itself."); 
     } elsif (other instanceof Cat) { 
      System.out.println("Cats are friendly to one another, so the Cat forfeits the fight!"); 
     } elsif (other instanceof Dog) { 
      System.out.println("Cats hate Dogs, so the Cat viciously attacks the Dog!"); 
     } else { 
      System.out.println("The Cat seems to be unamused by the other animal, and walks away..."); 
    } 
} 

И вы можете сделать его еще более сложным:

public class MountainLion extends Cat { 
    @Override 
    public void attack(Animal other) { 
     System.out.println("Mountain Lions do not like to be challenged and will strike down any predator with the fire inside their heart!"); 
    } 
} 

Но в своем классе, который использует их, вы просто следовать схеме интерфейса, например так:

public class AnimalKingdom { 

    public static void main(final String[] args) { 

     Animal cat = new Cat(); // could be read in from something like Spring using context.getBean("catObject", Animal.class); 
     Animal dog = new Dog(); // same as above... 
     Animal randomAnimalType = new MountainLion(); // same as above... 

     cat.attack(cat); 
     cat.attack(dog); 
     cat.attack(randomAnimalType); 

     dog.attack(cat); 
     dog.attack(dog); 
     dog.attack(randomAnimalType); 

     randomAnimalType.attack(cat); 
     randomAnimalType.attack(dog); 
     randomAnimalType.attack(randomAnimalType); // this doesn't use super, doesn't check if the animal is the same instance... 
    } 
} 

Суть в том, что теперь я могу создать поведение AnimalKingdom, не беспокоясь о том, какой тип Animal он есть, только что он является Animal (поэтому он следует интерфейсу Animal). Если я использую какую-то структуру (например, Spring), я могу динамически внедрять то, что работает на основе какого-либо внешнего файла конфигурации, чтобы создать множество возможных сценариев без необходимости копировать и вставлять или переписывать код.

Надеюсь, это было полезно.

спасибо

1

Я думаю, что это хороший вопрос. Кто-то может почувствовать, что причиной является то, что Animal является абстрактным классом, который не может быть создан. Таким образом, компилятор должен поддерживать catAttack(other), но проблема в том, компилятор знает только два метода

private void catAttack(Cat other) { 
     // Maybe a meow showdown wins the fight, no need to get physical 
    } 

    private void catAttack(Dog other) { 
     // Dogs are dangerous, run! 
    } 

Но есть возможность иметь другие классы, которые распространяется животное, а .OTHER, чем кошка и собака .Lets говорят Кролик. Таким образом, во время компиляции другой экземпляр может быть чем-то расширением Animal. Это может быть и Кролик. Поскольку компилятор недостаточно умен или достаточно полезен, чтобы узнать каждый класс, который расширяет животное, и проверить тип другого экземпляра, который передается catAttack, он показывает ошибку времени компиляции. Если у вас есть метод

private void catAttack(Animal other) { 

    } 

, то он будет скомпилирован

0

Это случай double-dispatch; см. также этот SO question, и это code smell post.

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

Существуют различные способы его реализации, но вот один из них.

В классе животных, у нас есть абстрактные методы (Animal также имеет имя атрибута):

public abstract void attack(Animal other); 
public abstract void attackedBy(Cat other); 
public abstract void attackedBy(Dog other); 
public abstract void attackedBy(Rat other); 

В вызывающем коде мы могли бы иметь

Animal c1 = new Cat("C1"); 
Animal c2 = new Cat("C2"); 
Animal d1 = new Dog("D1"); 
Animal d2 = new Dog("D2"); 
Animal r1 = new Rat("R1"); 
Animal r2 = new Rat("R2"); 
c1.attack(c2); 
c1.attack(d1); 
c1.attack(r1); 
d1.attack(c1); 
d1.attack(d2); 
d1.attack(r1); 
r1.attack(c1); 
r1.attack(d1); 
r1.attack(r2); 

САТ, и каждый из другие классы у нас есть код, такие как

public void attack(Animal other) { 
    System.out.println("Cat("+name+") attacks other"); 
    other.attackedBy(this); 
} 
public void attackedBy(Cat other) { 
    System.out.println("Cat("+name+") attacked by Cat("+other.name+")"); 
} 
public void attackedBy(Dog other) { 
    System.out.println("Cat("+name+") attacked by Dog("+other.name+")"); 
} 
public void attackedBy(Rat other) { 
    System.out.println("Cat("+name+") attacked by Rat("+other.name+")"); 
} 

вызов таких как

c1.attack(d1); 

звонки Cat.attack(Animal other), который передает управление на другой объект Animal (The Dog):

public void attack(Animal other) { 
    System.out.println("Cat("+name+") attacks other"); 
    other.attackedBy(this); 
} 

Звонок поступает на Dog.attackedBy(Cat other):

public void attackedBy(Cat other) { 
    System.out.println("Dog("+name+") attacked by Cat("+other.name+")"); 
} 

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