2011-12-28 2 views
5

Википедия определяет virtual methods как:Методы в объектно-ориентированных парадигмах могут быть переопределены методами с одинаковой сигнатурой в наследующих классах. Переменные однако не могут. Зачем?

В объектно-ориентированном программировании, виртуальная функция или виртуальный метод является функция или метода, поведение которого может быть переопределена в пределах наследуемого класса с помощью функции с той же подписью [предоставить полиморфное поведение].

Согласно определению, каждый не-статический метод в Java по умолчанию виртуальной кроме конечных и частных методов. Метод, который не может быть унаследован для полиморфного поведения, является не виртуальным методом.

Статические методы в Java никогда нельзя переопределить; следовательно, бессмысленно объявлять статический метод как final в Java, потому что сами статические методы ведут себя точно так же, как и конечные методы. Они могут просто быть скрытыми в подклассах методами с той же подписью. Очевидно, это связано с тем, что статические методы никогда не могут иметь полиморфного поведения: переопределенный метод должен обеспечивать полиморфизм, что не соответствует статическим методам.

Из предыдущего абзаца можно сделать важный вывод. Все методы в C++ по умолчанию статичны, потому что никакие методы в C++ не могут вести себя полиморфно до тех пор, пока они не будут явно объявлены виртуальными в суперклассе. Напротив, все методы в Java, за исключением конечных, статических и частных методов, по умолчанию являются виртуальными, поскольку по умолчанию они имеют полиморфное поведение (нет необходимости явно объявлять методы как виртуальные в Java и, следовательно, у Java нет ключевого слова типа "виртуальный").

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

class Super 
{ 
    public int a=5; 
    public int show() 
    { 
     System.out.print("Super method called a = "); 
     return a; 
    } 
} 

final class Child extends Super 
{ 
    public int a=6; 

    @Override 
    public int show() 
    { 
     System.out.print("Child method called a = "); 
     return a; 
    } 
} 

final public class Main 
{ 
    public static void main(String...args) 
    { 
     Super s = new Child(); 
     Child c = new Child(); 

     System.out.println("s.a = "+s.a); 
     System.out.println("c.a = "+c.a);   

     System.out.println(s.show()); 
     System.out.println(c.show()); 
    } 
} 

Выход производства вышеуказанного фрагмента кода состоит в следующем.

 
s.a = 5 
c.a = 6 
Child method called a = 6 
Child method called a = 6 

В этом примере, как называет s.show() и c.show() сделаны методом show() через переменные типа Super и типа Child, соответственно, вызвать метод show() в Child классе. Это означает, что метод show() в классе Child переопределяет метод show() в классе Super, поскольку оба они имеют идентичную подпись.

Это, однако, не может применяться к переменной экземпляра a, объявленной в обоих классах.В этом случае s.a будет относиться к a в Super классе и отображения 5 и c.a будет ссылаться на a в Child классе и отображения 6 означает, что a в Child класса просто скрывает (и не отменяет, как это случилось с нестатическая методы) a в классе Super.

После этого продолжительного обсуждения есть только один вопрос. Почему переменные экземпляра (и остальные тоже) не переопределены? Каковы были особые причины для реализации такого механизма? Были бы какие-то преимущества или недостатки, если бы они были переопределены?

+1

Не виртуальные методы C++ не являются статическими. это методы экземпляров, которые нельзя переопределить. Они похожи на конечные методы в Java: методы экземпляров, которые нельзя переопределить. –

+0

'В соответствии с вышеприведенным абзацем можно сделать важный вывод. Все методы в C++ (также в C) по умолчанию статичны, потому что никакие методы в C++ не могут вести себя полиморфно до тех пор, пока они не будут явно объявлены виртуальными в базе': статический метод не может получить доступ к объекту [no 'this' для этих методов ], в то время как не виртуальная функция [по умолчанию] в C++ может – amit

+1

. Я считаю, что слово «переопределенное» не относится к переменным. Вы переопределяете поведение, но не состоите. Переменные могут быть доступны из подкласса, поэтому нет необходимости «переопределять» (вы можете изменить его значение и иметь одно и то же имя). Также есть что-то, называемое затенением, поэтому, если вы переопределите другую переменную с тем же именем, это другая переменная во время выполнения. –

ответ

3

Я думаю, что целью переопределения является изменение функциональности без изменения подписи. Это относится только к методам: метод может иметь одну и ту же подпись, но иметь другое поведение. Поля имеют только «подпись», которая также ограничена типом и именем. У них нет поведения, поэтому ничто не может быть отменено.

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

+0

Согласно Принципу подписи Лискова, подписи не обязательно должны быть одинаковыми: тип возврата должен быть ковариантным (тот же или производный тип), а аргументы должны быть контравариантными (одинаковыми или базальными) в сигнатуре переопределяющих методов. Аналогично, тип поля с тем же именем в потомке должен быть ковариантным, поэтому имеет смысл «переопределить» поля. – outis

+0

@outis: тип поля аналогичен * both * для возвращаемого типа getter ('Number x = this.x' аналогичен« Number x = this.getX() ') * и * параметру сеттера -type ('this.x = (Number) x' аналогичен' this.setX ((Number) x) '). Поэтому он должен быть инвариантным. (И это верно, даже если поле «final», так как конструктор суперкласса - это то, что будет устанавливать его в этом случае.) – ruakh

2

Существует другое использование термина «статические» при обращении к разрешению имен: статическое разрешение - это когда объявленный тип переменной используется для разрешения имени, динамический, когда используется тип данных, хранящихся в переменной. Статическое разрешение может быть выполнено во время компиляции, а динамическое разрешение должно выполняться во время выполнения. Разрешение виртуальных членов (что является формой динамического разрешения) требует дополнительного уровня косвенности, поиска в таблице. Разрешение «виртуальных» полей приведет к затратам времени исполнения.

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

class Super(object): 
    a=5 
    def show(self): 
     print("Super method called a = ", end='') 
     return self.a 

class Child(Super): 
    a=6 
    def show(self): 
     print("Child method called a = ", end='') 
     return self.a 

# there's no indication that 's' is supposed to be a Super... 
s = Child(); 
c = Child(); 

# so it's no surprise that s.a is Child.a 
print("s.a =", s.a) 
# result: "s.a = 6" 
print("c.a =", c.a) 
# result: "c.a = 6" 
print(s.show()) 
# result: "Child method called a = 6" 
print(c.show()) 
# result: "Child method called a = 6" 

Общие листы, следует отметить, имеют как декларации типов, так и динамическое разрешение.

(defgeneric show (o)) 

(defclass super() 
    ((a :accessor super-a 
    :initform 5 
    :initarg :a)) 
) 

(defmethod show ((o super)) 
    (list "Super method called a = " 
     (slot-value o 'a)) 
) 

(defclass child (super) 
    ((a :accessor child-a 
    :initform 6 
    :initarg :a)) 
) 

(defmethod show ((o child)) 
    (list "Child method called a = " 
     (slot-value o 'a)) 
) 

(defun test (s c) 
    (declare (type super s)) 
    (declare (type child c)) 
    (list (list "s.a =" (slot-value s 'a)) 
     (list "c.a =" (slot-value c 'a)) 
     (show s) 
     (show c) 
     ) 
) 

(test (make-instance 'child) (make-instance 'child)) 
;; result: '(("s.a =" 6) ("c.a =" 6) ("Child method called a = " 6) ("Child method called a = " 6)) 

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

Все методы в C++ (и в C) по умолчанию являются статическими [...]

Нет, потому что static означает больше, чем «не может быть отменено» в Java, и дон Это означает, что вообще не на C++. В обоих случаях основным свойством статических методов является метод класса, доступ к которому осуществляется через класс, а не экземпляр (хотя Java позволяет им получить доступ к ним).

2

Посмотрите на мир C#. Использование переменных публичных экземпляров строго запрещено - см. StyleCop. Единственный рекомендуемый способ - использовать свойства, которые можно переопределить.

Я думаю, что это правильный подход. Просто не используйте переменные public instance и используйте аналогию со свойствами.

Относительно того, почему это так на Java, я думаю, что какой-то стиль на C++, который был принят в то время, когда Java была разработана как способ облегчить переход.

+0

C# - как Java. публичные поля также считаются рекламными объявлениями на Java. C# просто имеет синтаксический сахар для доступа к свойствам с использованием синтаксиса доступа к полям, а Java использует геттеры и сеттеры, но они - то же самое. –

+0

Несомненно.Чтобы быть откровенным, я ожидаю лучшего синтаксиса для getters и seters в Java в следующей версии. –

+0

@Jiri Pik Текущий синтаксис в порядке. Если вы не хотите, чтобы эти поля были общедоступными. Я думаю, что синтаксис C# для свойств ужасен. –

1

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

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

Переопределение на самом деле только о методах, независимо от языка. Java не имеет таких свойств, как C#, которые (ограниченные) классы сами по себе имеют методы, которые необходимо изменить. Поля могут выглядеть и действовать как свойства, но они совсем не то же самое.