Java: Является ли полиморфизм практичным только для методов с одинаковыми сигнатурами? - PullRequest
4 голосов
/ 27 октября 2011

Единственные примеры переопределения полиморфных методов, которые я когда-либо видел, включают методы, которые не принимают параметров или, по крайней мере, имеют идентичные списки параметров.Рассмотрим обычный пример Animal / Dog / Cat:

public abstract class Animal
{
    public abstract void makeSound();
}

public class Dog extends Animal
{
    public void makeSound()
    {
        System.out.println("woof");
    }
}

public class Cat extends Animal
{
    public void makeSound()
    {
        System.out.println("meow");
    }
}

public class ListenToAnimals
{
    public static void main(String[] args)
    {
        AnimalFactory factory = new AnimalFactory();
        Animal a = factory.getRandomAnimal(); // generate a dog or cat at random
        a.makeSound();
    }
}

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

public abstract class Animal
{   
    public abstract void makeSound();

    public void speak(String name)
    {
        System.out.println("My name is " + name);
    }
}

public class Dog extends Animal
{
    public void makeSound()
    {
        System.out.println("woof");
    }

    public void speak(String name)
    {
        super.speak(name);
        System.out.println("I'm a dog");
    }
}

public class Cat extends Animal
{
    public void makeSound()
    {
        System.out.println("meow");
    }

    public void speak(String name, int lives)
    {
        super.speak(name);
        System.out.println("I'm a cat and I have " + lives + " lives");
    }
}

public class ListenToAnimals
{
    public static void main(String[] args)
    {
        AnimalFactory factory = new AnimalFactory();
        Animal a = factory.getRandomAnimal(); // generate a dog or cat at random
        a.makeSound();
        // a.speak(NOW WHAT?
    }
}

В этой последней (закомментированной) строке основного метода я не знаюположить туда, потому что я не знаю, какой тип животных у меня есть.Мне не приходилось беспокоиться об этом раньше, потому что makeSound () не принимала никаких аргументов.Но Speak () делает, и аргументы зависят от типа Animal.

Я читал, что некоторые языки, такие как Objective-C, допускают списки переменных аргументов, поэтому такая проблема никогда не должна возникать,Кто-нибудь знает, как реализовать такие вещи в Java?

Ответы [ 9 ]

6 голосов
/ 27 октября 2011

Вы путаете метод переопределение и метод перегрузка .В вашем примере класс Cat имеет два метода:

public void speak(String name) // It gets this from its super class
public void speak(String name, int lives)

Перегрузка - это способ определения методов с похожими функциями, но разными параметрами.Не было бы никакой разницы, если бы вы назвали метод так:

public void speakWithLives(String name, int lives)

Чтобы избежать путаницы, в java рекомендуется использовать аннотацию @Override, когда вы пытаетесь переопределить метод.Поэтому:

 // Compiles
@Override
public void speak(String name)

// Doesn't compile - no overriding occurs!
@Override
public void speak(String name, int lives)

РЕДАКТИРОВАТЬ: Другие ответы упоминают об этом, но я повторяю это для акцента.Добавление нового метода сделало невозможным представление класса Cat как Animal во всех случаях, что устранило преимущество полиморфизма.Чтобы использовать новый метод, вам нужно понизить его до типа Cat:

Animal mightBeACat = ...
if(mightBeACat instanceof Cat) {
  Cat definitelyACat = (Cat) mightBeACat;
  definitelyACat.speak("Whiskers", 9);
} else {
  // Definitely not a cat!
  mightBeACat.speak("Fred");
}

Инструмент проверки кода в моей IDE помещает предупреждение в instanceof, поскольку ключевое слово указывает на возможную полиморфностьошибка абстракции.

5 голосов
/ 27 октября 2011

Ваш пример Cat больше не полиморфен, так как вы должны знать, что Cat для передачи этого параметра.Даже если бы Java это разрешило, как бы вы это использовали?

2 голосов
/ 27 октября 2011

Насколько я знаю, Java не позволяет вам этого делать. говорить (имя, жизнь) теперь просто функция кошки. Некоторые языки допускают такой тип гибкости. Чтобы заставить Java разрешить это, вы можете сделать paramater массивом объектов или какой-либо другой коллекцией.

Тем не менее, учтите, что когда вы звоните по телефону, вы должны знать, какие параметры передавать независимо, поэтому вопрос несколько спорный.

1 голос
/ 27 октября 2011

Когда вы вызываете полиморфный метод как:

a.speak("Gerorge");

Вам не нужно знать, какой тип Животного создан, потому что это цель полиморфизма. Также, поскольку у вас есть пользовательское предложение:

super.speak(name);

Обе кошки и собаки будут иметь поведение животных плюс собственное поведение.

0 голосов
/ 27 октября 2011

Я согласен с комментариями о том, как вы действительно нарушили полиморфизм, если вы должны знать тип объекта, прежде чем сможете вызывать метод Spell.Если вы абсолютно ДОЛЖНЫ иметь доступ к обоим методам разговора, вот один из способов, которым вы могли бы это реализовать.

public class Animal {      
    public void speak(String name) {
        throw new UnsupportedOperationException("Speak without lives not implemented");
    }
    public void speak(String name, int lives) {
        throw new UnsupportedOperationException("Speak with lives not implemented");
    }
}

public class Dog extends Animal {
    public void speak(String name) {
        System.out.println("My name is " + name);
        System.out.println("I'm a dog");
    }
}

public class Cat extends Animal {
    public void speak(String name, int lives) {
        System.out.println("My name is " + name);
        System.out.println("I'm a cat and I have " + lives + " lives");
    }
}

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

0 голосов
/ 27 октября 2011

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

Когда подклассы имеют поведение, которое не определеноИнтерфейс, у вас не так много опций в Java, которые не являются многословными или немного неубедительными.

У вас может быть функция speak (), которая принимает интерфейс маркера и делегирует конструкцию arg фабрике.Вы можете передать карту параметров.Вы можете использовать varargs.

В конечном счете, вам нужно знать, что передать методу, независимо от того, на каком языке.

0 голосов
/ 27 октября 2011

Если вы хотите сделать такой вызов, вы можете использовать отражение, чтобы получить класс:

if (a.getclass() == Cat.class) {
// speak like a cat
} else if (a.getclass() == Dog.class) {
.
.
.

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

0 голосов
/ 27 октября 2011
In this case best way is to use a DTO,

public class SpeakDTO
{
     //use getters and setters when you actually implement this
     String name;
     int lives;
}

public class Dog extends Animal
{
    public void speak(SpeakDTO dto)
    {
        super.speak(dto.name);
        System.out.println("I'm a dog");
    }
}

public class Cat extends Animal
{
    public void speak(SpeakDTO dto)
    {
        super.speak(dto.name);
        System.out.println("I'm a cat and I have " + dto.lives + " lives");
    }
}

public class ListenToAnimals
{
    public static void main(String[] args)
    {
        AnimalFactory factory = new AnimalFactory();
        Animal a = factory.getRandomAnimal(); // generate a dog or cat at random
        a.makeSound();

        SpeakDTO dto = new SpeakDTO();
        dto.name = "big cat";
        dto.lives = 7;

        a.speak(dto);
    }
}
0 голосов
/ 27 октября 2011

Вы можете сделать

public void speak(Map ... mappedData)
    {
        System.out.println("My name is " + mappedData.get("name")+ " and I have "+mappedData.get("lives");
    }

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

...