Как вызвать переопределенные методы в подклассе? Потенциальный кандидат на рефакторинг

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

Таким образом, каждый класс наследовал SuperClass и его метод doSomething(), а SubClassSpecial1 и SubClassSpecial2 переопределяли их собственным методом doSomeThing.

Все было хорошо, пока я не написал метод, который выглядел примерно так:

void fooBar(SuperClass obj) {
    obj.doSomething()
}

и его можно было бы назвать fooBar( new SubClassSpecial1() );

Проблема в том, что класс среды выполнения переменной obj теперь является классом ее суперкласса, и поэтому он будет вызывать методы, определенные в суперклассе. Я мог бы, конечно, создать абстрактный метод doSometing() в суперклассе и заставить каждый подкласс реализовать свою собственную версию, но это дублировало бы код в трех классах. И я хочу избежать этого...

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

if(obj.getClass().getName() == "SubClassSpecial1" )  ((SubClassSpecial1) obj).doSomething()K;
else if ...

Итак, что я должен сделать, чтобы сделать дизайн более элегантным и не хакерским?


person oligofren    schedule 02.11.2009    source источник
comment
Эта статья должна быть по крайней мере помечена языком программирования, на котором вы пишете код.   -  person Percutio    schedule 02.11.2009
comment
исправлено. добавлен тег java. Благодарю. но я думал, что это общая проблема дизайна, которая выходит за рамки языковых барьеров.   -  person oligofren    schedule 02.11.2009
comment
Можете ли вы дать нам фрагменты определений вашего класса?   -  person SMART_n    schedule 02.11.2009
comment
Проблема, похоже, заключалась в том, что я неправильно понял, как работает наследование Java. Я подумал, что если бы у меня было что-то вроде class SuperClass { doIt(){print(In the superclass); } } class SubClass extends SuperClass { doIt(){ print(In the subclass); } } и имел такой фрагмент кода SubClass o = new SubClass(); ((Суперкласс) o).doIt(); это распечатало бы В суперклассе. Оказывается, это не так, и мои предположения были неверными, и полиморфизм действительно работает в моих интересах.   -  person oligofren    schedule 02.11.2009


Ответы (3)


То, что вы описали, должно работать нормально.

Действительно ли obj.doSomething() вызывает реализацию суперкласса? Если это так, вы не переопределяете его должным образом. Убедитесь, что вы не изменили подпись в своих переопределенных версиях.

person Terry Wilcox    schedule 02.11.2009
comment
Ну разве так не должно быть? Тип среды выполнения объекта объявлен как тип его суперкласса, и поэтому он должен вызывать версию суперкласса. Если я принудительно ((SubClassSpecial1) obj).doSomething() вызовет подклассовую версию. Или мои предположения о том, как работают классы наследования и времени выполнения, неверны? - person oligofren; 02.11.2009
comment
Хм... Я написал тестовую программу, которая показывает недостатки в моих предположениях о том, как работают изменения типа Runtime объекта. Мне нужно проверить это, прежде чем я вернусь... - person oligofren; 02.11.2009
comment
Ваше предположение неверно. Вам не нужно приводить obj к SubClassSpecial1, чтобы получить метод doSomething() SubClassSpecial1, это произойдет автоматически. Это полиморфизм. Попробуйте несколько тестов. - person Terry Wilcox; 02.11.2009
comment
Так работает джава. Единственным исключением из этого является во время построения, если конструктор суперкласса вызывает операцию, то операция над суперклассом используется, поскольку производный экземпляр еще не создан, и его поля будут недействительными. Если вам нужно сделать это, вам придется добавить операцию инициализации для выполнения после построения. - person vickirk; 02.11.2009
comment
Я так и сделал, и вы действительно правы. При этом тоже обнаружил тонкую ошибку. Спасибо! - person oligofren; 02.11.2009
comment
@vickirk: даже в конструкторе вызывается метод подкласса. Но проблема, которую вы описываете (т.е. подкласс еще не полностью построен), является точной причиной, по которой вы должны ТОЛЬКО вызывать частные или окончательные методы в конструкторе. - person janko; 04.11.2009

Меня немного смущает эта часть вашего вопроса:

Конечно, я хотел, чтобы каждый объект вызывал свою собственную версию doSomething(), но не понял, что для этого необходимо объявить obj как один из методов подкласса. А сейчас бардак.

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

Поэтому я думаю, что то, что вы пытались сделать, должно работать нормально, например. все эти объявления можно использовать для перехода к методу foobar:

SuperClass sc1 = new SubClassSpecial1();
SubClassSpecial2 sc2 = new SubClassSpecial2();
//etc..
person NickDK    schedule 02.11.2009
comment
Хорошо, я постараюсь отредактировать эту часть и перефразировать себя. Я имел в виду, что я хотел, чтобы объект вызывал свой собственный (если он был переопределен) метод, а не метод из своего суперкласса. И проблема в том, что, поскольку он объявлен (в объявлении метода) как экземпляр суперкласса, его тип класса времени выполнения будет типом его суперкласса и, таким образом, вызовет метод суперкласса. Надеюсь, это имеет какой-то смысл... - person oligofren; 02.11.2009
comment
Нет, ваше предположение немного не так, класс, который вы используете при создании экземпляра нового объекта с ключевым словом «новое», определяет тип вашего объекта во время выполнения. Итак, в моем примере переменная SuperClass sc1 содержит ссылку на объект, тип среды выполнения которого — SubClassSpecial1. Теперь это может быть немного запутанным, надеюсь, вы поняли :-) - person NickDK; 02.11.2009
comment
Спасибо, я начал подозревать, что что-то не так после ответа Терри, и обнаружил, что Java не делает того, что, как я думал, было бы интуитивным способом сделать это, написав некоторый тестовый код. Все еще нахожу это поведение немного странным, но, по крайней мере, теперь я знаю :) - person oligofren; 02.11.2009

Когда у вас есть это:

void fooBar(SuperClass obj) {
    obj.doSomething();
}

тогда тип времени компиляции obj равен SuperClass. Это означает, что компилятор проверит, что SuperClass имеет метод doSomething().
Во время времени выполнения вы можете заменить подкласс SuperClass, это Принцип подстановки Лисков. Метод foobar() не знает и не должен знать, что такое тип среды выполнения obj, а только то, что он является производным от SuperClass и поэтому может быть вызван doSomething().

Что касается вашего примера:

fooBar( new SubClassSpecial1() );

В этом случае вы знаете, что тип параметра среды выполнения — SubClassSpecial1, который переопределяет doSomething(). Во всех случаях вызывается правильный метод.

Несколько слов о рефакторинге. Вы можете подумать о рефакторинге своей иерархии.
Ваш базовый класс SuperClass должен определять doSomething() как абстрактный. Три ваших класса, которым нужна одинаковая реализация doSomething(), должны наследовать ее от промежуточного базового класса, который имеет эту конкретную реализацию. Два ваших специальных класса должны наследоваться непосредственно от SuperClass и иметь собственную реализацию doSomething().

person quamrana    schedule 03.11.2009
comment
Спасибо, quamrama! Для меня это был самый информативный из ответов, хотя на мою главную жалобу уже ответил кто-то другой. Вы заставили меня лучше понять, как это работает, и совет по рефакторингу действительно полезен. Отличный ответ! - person oligofren; 04.11.2009