Почему статические методы не должны быть переопределенными?

13

В ответах на этот вопрос общее согласие заключалось в том, что статические методы не предназначены для переопределения (и, следовательно, статические функции в C # не могут быть виртуальными или абстрактными). Это не только случай в C #, хотя; Java также запрещает это, и C ++, похоже, тоже не нравится. Однако я могу вспомнить множество примеров статических функций, которые я хотел бы переопределить в дочернем классе (например, фабричные методы). Хотя в теории есть способы обойти их, ни один из них не является чистым или простым.

Почему статические функции не должны переопределяться?

PixelArtDragon
источник
4
Delphi поддерживает методы класса , которые очень похожи на статические методы и могут быть переопределены. Им передают selfуказатель, который указывает на класс, а не на экземпляр класса.
CodesInChaos
Одно из определений статики: отсутствие изменений. Мне любопытно, почему вы сделали функцию статической, которую хотите переопределить.
Джеффо
4
@JeffO: Это английское определение «static» не имеет абсолютно никакого отношения к уникальной семантике статических функций-членов.
Легкость гонок с Моникой
5
Ваш вопрос "почему статические методы должны использовать статически типизированную диспетчеризацию вместо одиночной виртуальной диспетчеризации?" Когда вы формулируете вопрос таким образом, я думаю, что он сам отвечает.
Эрик Липперт
1
@EricLippert Вопрос может быть лучше сформулирован как «Почему C # предлагает статические методы вместо методов класса?», Но это все еще допустимый вопрос.
CodesInChaos

Ответы:

13

В статических методах нет объекта, обеспечивающего надлежащее управление механизмом переопределения.

Обычный механизм виртуальных методов класса / экземпляра позволяет точно настроить управление переопределениями следующим образом: каждый реальный объект является экземпляром ровно одного класса. Этот класс определяет поведение переопределений; он всегда получает первую трещину в виртуальных методах. Затем он может выбрать вызов родительского метода в нужное время для его реализации. Каждый родительский метод также получает свою очередь для вызова своего родительского метода. Это приводит к хорошему каскаду родительских вызовов, который реализует одно из понятий повторного использования кода, которым известна объектная ориентация. (Здесь код базовых / суперклассов повторно используется относительно сложным способом; другое ортогональное понятие повторного использования кода в ООП - это просто наличие нескольких объектов одного и того же класса.)

Базовые классы могут повторно использоваться различными подклассами, и каждый может комфортно сосуществовать. Каждый класс, который используется для создания экземпляров объектов, диктует свое поведение, мирно и одновременно сосуществуя с другими. Клиент может контролировать, какое поведение он хочет и когда, выбирая, какой класс использовать для создания экземпляра объекта и передачи другим по желанию.

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

Таким образом, если бы мы сделали возможность переопределения для статики без изменения чего-либо еще, у нас были бы трудности с упорядочением переопределений. Было бы трудно определить ограниченный контекст для применимости переопределения, поэтому вы получите переопределение глобально, а не локально, как с объектами. Не существует экземпляра объекта для переключения поведения. Итак, если кто-то вызвал статический метод, который, как оказалось, был переопределен другим классом, переопределение получит контроль или нет? Если есть несколько таких переопределений, кто получает контроль первым? второй? С переопределением объекта экземпляра у всех этих вопросов есть значимые и аргументированные ответы, но со статикой они не имеют.

Переопределения для статики были бы в значительной степени хаотичными, и подобные вещи делались раньше.

Например, в Mac OS System 7 и более ранних версиях использовался механизм исправления ловушек для расширения системы путем получения контроля над системными вызовами приложений перед операционной системой. Вы можете думать о таблице исправлений системного вызова как о массиве указателей на функции, во многом как vtable для объектов экземпляров, за исключением того, что это была одна глобальная таблица.

Это вызвало невыразимое горе для программистов из-за неупорядоченного характера исправления ловушек. Тот, кто в последний раз исправлял ловушку, в основном выиграл, даже если не хотел. Каждый патч ловушки будет захватывать предыдущее значение ловушки для своего рода возможности родительского вызова, которая была чрезвычайно хрупкой. Удаляя патч-ловушку, скажем, когда вам больше не нужно было знать, что системный вызов считается дурным тоном, поскольку у вас не было информации, необходимой для удаления вашего патча (если вы сделали это, вы также удалили бы все остальные патчи, которые следовали вы).

Это не означает, что было бы невозможно создать механизм для переопределений статики, но я бы предпочел вместо этого превратить статические поля и статические методы в поля экземпляров и методы экземпляров метаклассов, чтобы обычный объект затем будут применяться методы ориентации. Обратите внимание, что существуют системы, которые делают это также: CSE 341: классы Smalltalk и метаклассы ; Смотрите также: Что такое Smalltalk-эквивалент статики Java?


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

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

Эрик Эйдт
источник
Если я правильно понимаю: дело не в том, теоретически вы хотели бы или должны делать что-то подобное, но, более того, программно, реализация этого была бы сложной и не обязательно полезной?
PixelArtDragon
@Garan, смотри мое приложение.
Эрик Эйдт
1
Я не покупаю аргумент заказа; вы получаете метод, предоставленный классом, для которого вы вызвали метод (или первым суперклассом, который предоставляет метод в соответствии с выбранной линеаризацией вашего языка).
Хоббс
1
@PierreArlaud: Но this.instanceMethod()имеет динамическое разрешение, поэтому, если у self.staticMethod()него такое же разрешение, а именно динамическое, он больше не будет статическим методом.
Йорг Миттаг
1
семантика переопределения для статических методов должна быть добавлена. Гипотетические метаклассы Java + не нужно ничего добавлять! Вы просто создаете объекты классов, а статические методы становятся обычными методами экземпляров. Вам не нужно что-то добавлять к языку, потому что вы используете только те концепции, которые уже существуют: объекты, классы и методы экземпляров. В качестве бонуса вы фактически можете удалить статические методы из языка! Вы можете добавить функцию, удалив концепцию и, следовательно, сложность из языка!
Йорг Миттаг
9

Переопределение зависит от виртуальной диспетчеризации: вы используете тип времени выполнения thisпараметра, чтобы решить, какой метод вызывать. Статический метод не имеет thisпараметров, поэтому отправлять нечего.

Некоторые языки, в частности Delphi и Python, имеют промежуточную область видимости, которая допускает это: методы класса. Метод класса не является обычным методом экземпляра, но он также не является статическим; он получает selfпараметр (забавно, что оба языка называют этот thisпараметр self), который ссылается на сам тип объекта, а не на экземпляр этого типа. С этим значением теперь у вас есть тип, доступный для виртуальной отправки.

К сожалению, ни у JVM, ни у CLR нет ничего похожего.

Мейсон Уилер
источник
selfЯвляются ли языки, которые используют , имеют классы в качестве актуальных, созданных объектов с переопределяемыми методами - конечно, Smalltalk, Ruby, Dart, Objective C, Self, Python? По сравнению с языками, которые берут после C ++, где классы не являются объектами первого класса, даже если есть некоторый доступ к отражению?
Jerry101
Просто чтобы прояснить, вы говорите, что в Python или Delphi вы можете переопределить методы класса?
Эрик Эйдт
@ErikEidt В Python почти все можно переопределить. В Delphi метод класса может быть явно объявлен virtualи затем переопределен в классе-потомке, как методы экземпляра.
Мейсон Уилер
Итак, эти языки предоставляют какое-то понятие метаклассов, не так ли?
Эрик Эйдт
1
@ErikEidt Python имеет «истинные» метаклассы. В Delphi ссылка на класс - это особый тип данных, а не класс сам по себе.
Мейсон Уилер
3

Ты спрашиваешь

Почему статические функции не должны переопределяться?

я спрашиваю

Почему функции, которые вы хотите переопределить, должны быть статическими?

Определенные языки заставляют вас начать показ в статическом методе. Но после этого вы действительно можете решить очень много задач без каких-либо статических методов.

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

Никто из этих людей не прав.

Если вам нужно, чтобы он был перезаписываемым, просто перестаньте обозначать его как статический. Ничто не сломается, потому что вокруг вас летает объект без гражданства.

candied_orange
источник
Если язык поддерживает структуры, то типом единицы может быть структура нулевого размера, которая передается методу логического ввода. Создание этого объекта является деталью реализации. И если мы начнем говорить о деталях реализации как об истинной истине, то я думаю, что вам также нужно учитывать, что в реальной реализации тип нулевого размера не должен быть фактически создан (поскольку для загрузки не требуется никаких инструкций) это или хранить его).
Теодорос Чатзигианнакис
И если вы говорите еще больше «за кулисами» (например, на уровне исполняемого файла), то аргументы среды могут быть определены как тип в языке, а «реальная» точка входа в программу может быть методом экземпляра этот тип, действующий в любом случае, был передан программе загрузчиком ОС.
Теодорос Чатзигианнакис
Я думаю, это зависит от того, как вы на это смотрите. Например, когда программа запускается, ОС загружает ее в память и затем переходит по адресу, где находится индивидуальная реализация программы двоичной точки входа (например, _start()символ в Linux). В этом примере исполняемый файл - это объект (он имеет свою собственную «таблицу диспетчеризации» и все остальное), а точка входа - это динамически отправляемая операция. Следовательно, точка входа в программу является виртуальной, если смотреть на нее снаружи, и ее можно легко сделать виртуальной, если смотреть на нее изнутри.
Теодорос Чатзигианнакис
1
«Ничто не сломается, потому что вокруг тебя летает объект без гражданства». ++++++
RubberDuck
@TheodorosChatzigiannakis вы делаете сильный аргумент. если ничто иное не убедило меня, линия не вдохновляет мысль, которую я намеревался. Я действительно пытался дать людям разрешение отказаться от некоторых из более привычных практик, которые они слепо связывают со статическими методами. Итак, я обновился. Мысли?
candied_orange
2

Почему статические методы не должны быть переопределенными?

Это не вопрос «должен».

«Переопределение» означает «отправка динамически ». «Статический метод» означает «отправка статически». Если что-то статично, оно не может быть переопределено. Если что-то может быть переопределено, это не статично.

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

Конечно, вполне возможно иметь методы класса, которые могут быть переопределены. Или у вас может быть такой язык, как Ruby, где классы являются объектами, как и любой другой объект, и, таким образом, могут иметь методы экземпляров, что полностью исключает необходимость в методах классов. (У Ruby есть только один вид методов: методы экземпляра. У него нет методов класса, статических методов, конструкторов, функций или процедур.)

Йорг Миттаг
источник
1
"По определению" хорошо поставлено.
Candied_Orange
1

таким образом статические функции в C # не могут быть виртуальными или абстрактными

В C # вы всегда вызываете статические члены, используя класс, например BaseClass.StaticMethod(), нет baseObject.StaticMethod(). Таким образом, в конечном итоге, если у вас есть ChildClassнаследование от BaseClassи childObjectэкземпляр ChildClass, вы не сможете вызывать свой статический метод из childObject. Вам всегда нужно явно использовать реальный класс, так что static virtualпросто не имеет смысла.

Что вы можете сделать, это переопределить тот же staticметод в вашем дочернем классе и использовать newключевое слово.

class BaseClass {
    public static int StaticMethod() { return 1; }
}

class ChildClass {
    public static new int StaticMethod() { return BaseClass.StaticMethod() + 2; }
}

int result;    
var baseObj = new BaseClass();
var childObj = new ChildClass();

result = BaseClass.StaticMethod();
result = baseObj.StaticMethod(); // DOES NOT COMPILE

result = ChildClass.StaticMethod();
result = childObj.StaticMethod(); // DOES NOT COMPILE

Если бы можно было позвонить baseObject.StaticMethod(), тогда ваш вопрос имел бы смысл.

user276648
источник