Использование типа суперкласса для экземпляра подкласса

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

учитывая следующую иерархию классов:

class SuperClass{}
class SubClass extends SuperClass{}

почему люди используют этот шаблон для создания экземпляра подкласса:

SuperClass instance = new SubClass();

вместо этого:

SubClass instance = new SubClass();

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

void aFunction(SuperClass param){}

//somewhere else in the code...
...
aFunction(instance);
...

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

SubClass instance = new SubClass();
aFunction(instance);

фактически, типы переменных AFAIK бессмысленны во время выполнения. Они используются только компилятором!

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

5 ответов


основным аргументом для этого типа кодирования имеет, поскольку Принцип Замены Лискова, в котором говорится, что если X является подтипом типа T, тогда любой экземпляр T должен быть выгружен с X.

преимущество этого просто. Предположим, у нас есть программа с файлом свойств, который выглядит так:

mode="Run"

и ваша программа выглядит так:

public void Program
{
    public Mode mode;

    public static void main(String[] args)
    {
        mode = Config.getMode();
        mode.run();
    }
}

так коротко, эта программа будет использовать файл конфигурации для определения режима, в котором эта программа будет загружаться. В Config класса, getMode() может выглядеть так:

public Mode getMode()
{
    String type = getProperty("mode"); // Now equals "Run" in our example.

    switch(type)
    {
       case "Run": return new RunMode();
       case "Halt": return new HaltMode();  
    }
}

почему это не сработало бы иначе

сейчас, потому что у вас есть ссылка типа Mode, вы можете полностью изменить функциональность вашей программы, просто изменив значение mode собственность. Если бы ты ... --11-->, вы не смогли бы использовать этот тип функциональность.

почему это хорошо

эта картина так хорошо прижился, потому что он открывает программы для расширения. Это означает, что данный вид желаемой функциональности возможен с наименьшим количеством изменений, если автор пожелает реализовать этот вид функциональности. И я имею в виду, давай. Вы изменяете одно слово в конфигурационном файле и полностью изменяете поток программы, не редактируя ни одной строки кода. То есть желательный.


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

обновление:

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

я расширю пример Криса:

считайте, что у вас есть следующий:

RunMode mode = new RunMode();

...

теперь вы можете положиться на тот факт, что mode это RunMode.

однако позже вы можете изменить эту строку на:

RunMode mode = Config.getMode(); //breaks

Ой, это не компилируется. Хорошо, давайте изменим это.

Mode mode = Config.getMode(); 

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


это называется polymorphis и это суперкласс ссылка на объект подкласса.

In fact, AFAIK variable types are meaningless at runtime. They are used 
only by the compiler!

Не уверен, где вы это читаете. Во время компиляции компилятор знает только класс ссылочного типа(так что супер класс в случае полиморфизма, как вы заявили). Во время выполнения java знает фактический тип объекта(.getClass()). Во время компиляции компилятор java проверяет только, находится ли вызванное определение метода в классе ссылочного типа. Какой метод вызвать(функция перегрузки) определяется во время выполнения на основе фактического типа объекта.

Why polymorphism?

Ну google, чтобы найти больше, но вот пример. У вас есть общий метод draw(Shape s). Теперь форма может быть Rectangle, a Circle любой CustomShape. Если вы не используете ссылку в форму draw() метод вам придется создавать разные методы для каждого типа(подклассов) формы.


SuperClass instance = new SubClass1()

после некоторых строк, вы можете сделать instance = new SubClass2();

но если ты пишешь, SubClass1 instance = new SubClass1();

после некоторых строк, вы не можете сделать instance = new SubClass2()


Это с точки зрения дизайна, у вас будет один супер класс, и может быть несколько подклассов, в которых вы хотите расширить функциональность.

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