Por que os métodos estáticos não deveriam ser substituíveis?

13

Em respostas a essa pergunta, o consenso geral era que métodos estáticos não deveriam ser substituídos (e, portanto, funções estáticas em C # não podem ser virtuais ou abstratas). Este não é apenas o caso em C #; O Java também proíbe isso e o C ++ também não parece gostar. No entanto, posso pensar em muitos exemplos de funções estáticas que gostaria de substituir em uma classe filho (por exemplo, métodos de fábrica). Enquanto, em teoria, existem maneiras de contorná-las, nenhuma delas é limpa ou simples.

Por que as funções estáticas não devem ser substituíveis?

PixelArtDragon
fonte
4
O Delphi suporta métodos de classe , que são bastante semelhantes aos métodos estáticos e podem ser substituídos. Eles passam por um selfponteiro que aponta para a classe e não para uma instância da classe.
CodesInChaos
Uma definição de estática é: falta de mudança. Estou curioso para saber por que você tornou estática uma função que deseja substituir.
JeffO 6/09/16
4
@ Jeff: A definição em inglês de "estático" não tem absolutamente nada a ver com a semântica exclusiva das funções-membro estáticas.
Lightness Races com Monica
5
Sua pergunta é "por que os métodos estáticos devem usar o envio estaticamente digitado em vez do envio virtual único ?" Quando você formula a pergunta dessa maneira, acho que ela se responde.
Eric Lippert
1
@EricLippert A pergunta pode ser melhor formulada como "Por que o C # oferece métodos estáticos em vez de métodos de classe?", Mas ainda é uma pergunta válida.
CodesInChaos

Respostas:

13

Com os métodos estáticos, não há objeto para fornecer o controle adequado do mecanismo de substituição.

O mecanismo normal do método virtual de classe / instância permite o controle preciso de substituições da seguinte maneira: cada objeto real é uma instância de exatamente uma classe. Essa classe determina o comportamento das substituições; sempre obtém o primeiro acesso aos métodos virtuais. Em seguida, ele pode optar por chamar o método pai no momento certo para sua implementação. Cada método pai também recebe sua vez de chamar seu método pai. Isso resulta em uma bela cascata de invocações pai, que realiza uma das noções de reutilização de código pelas quais a orientação a objetos é conhecida. (Aqui o código das classes base / superclasses está sendo reutilizado de uma maneira relativamente complexa; outra noção ortogonal de reutilização de código no OOP é simplesmente ter vários objetos da mesma classe.)

As classes base podem ser reutilizadas por várias subclasses e cada uma pode coexistir confortavelmente. Cada classe usada para instanciar objetos ditando seu próprio comportamento, coexistindo pacificamente e simultaneamente com as outras. O cliente tem controle sobre quais comportamentos deseja e quando, escolhendo qual classe usar para instanciar um objeto e repassar para outras pessoas, conforme desejado.

(Este não é um mecanismo perfeito, pois sempre é possível identificar recursos que não são suportados, é por isso que é por isso que padrões como o método de fábrica e a injeção de dependência são colocados em camadas)

Portanto, se tivéssemos um recurso de substituição de estática sem alterar mais nada, teríamos dificuldade em ordenar as substituições. Seria difícil definir um contexto limitado para a aplicabilidade da substituição, portanto, você obteria a substituição globalmente, e não localmente, como nos objetos. Não há objeto de instância para alterar o comportamento. Portanto, se alguém invocou o método estático que foi substituído por outra classe, a substituição deve ter controle ou não? Se houver várias substituições, quem obtém o controle primeiro? segundo? Com a substituição de objetos de instância, todas essas perguntas têm respostas significativas e bem fundamentadas, mas com estática elas não.

Substituições de estática seriam substancialmente caóticas e, coisas como essa já foram feitas antes.

Por exemplo, o Mac OS System 7 e versões anteriores usavam um mecanismo de correção de traps para estender o sistema, obtendo o controle das chamadas do sistema feitas pelo aplicativo antes do sistema operacional. Você poderia pensar em tabela de correção a chamada de sistema como uma matriz de ponteiros de função, bem como uma vtable para objetos de instância, exceto que ele era uma única tabela global.

Isso causou um sofrimento incalculável para os programadores, devido à natureza não ordenada da correção de armadilhas. Quem conseguiu consertar a armadilha venceu basicamente, mesmo que não quisesse. Cada corretor da armadilha capturaria o valor anterior da armadilha para um tipo de recurso de chamada pai, que era extremamente frágil. A remoção de um patch de interceptação, digamos que, quando você não precisava mais saber sobre uma chamada de sistema, era considerada péssima, porque na verdade não tinha as informações necessárias para remover o patch (se o fizesse, também desataria outros patches que se seguiram). você).

Isso não quer dizer que seria impossível criar um mecanismo para substituições de estática, mas o que eu provavelmente preferiria fazer é transformar os campos estáticos e métodos estáticos em campos de instâncias e métodos de metaclasses, para que o objeto normal técnicas de orientação seriam aplicadas. Observe que existem sistemas que fazem isso também: CSE 341: Classes e metaclasses de Smalltalk ; Veja também: Qual é o equivalente do Smalltalk à estática do Java?


Estou tentando dizer que você precisaria criar um design sério de recurso de linguagem para fazê-lo funcionar razoavelmente bem. Por exemplo, uma abordagem ingênua foi feita, mancou, mas era muito problemática e, sem dúvida (eu diria), com falhas arquiteturais, fornecendo uma abstração incompleta e difícil de usar.

Quando você terminar de projetar o recurso de substituições estáticas para funcionar bem, você poderá ter inventado alguma forma de metaclasses, que é uma extensão natural do OOP para / para métodos baseados em classes. Portanto, não há razão para não fazer isso - e alguns idiomas realmente fazem. Talvez seja apenas um requisito adicional que vários idiomas optem por não fazer.

Erik Eidt
fonte
Se estou entendendo corretamente: não é uma questão de, teoricamente, você querer ou fazer algo assim, mas mais que programaticamente, a implementação seria difícil e não necessariamente útil?
PixelArtDragon
@ Garan, veja meu adendo.
Erik Eidt
1
Eu não compro o argumento de pedido; você obtém o método fornecido pela classe na qual você chamou o método (ou a primeira superclasse que fornece um método de acordo com a linearização escolhida pelo seu idioma).
Hbbs #
1
@PierreArlaud: Mas this.instanceMethod()tem resolução dinâmica, por isso, se self.staticMethod()tiver a mesma resolução, ou seja, dinâmica, não seria mais um método estático.
Jörg W Mittag
1
a substituição da semântica para métodos estáticos precisa ser adicionada. Um hipotético Java + metaclasses não precisa adicionar nada! Você apenas cria objetos de classe e os métodos estáticos tornam-se métodos de instância regulares. Você não precisa adicionar algo à linguagem, porque você usa apenas conceitos que já existem: objetos, classes e métodos de instância. Como bônus, você realmente remove métodos estáticos da linguagem! Você pode adicionar um recurso removendo um conceito e, portanto, complexidade do idioma!
Jörg W Mittag
9

A substituição depende do envio virtual: você usa o tipo de tempo de execução do thisparâmetro para decidir qual método chamar. Um método estático não tem thisparâmetro, portanto não há nada para despachar.

Algumas linguagens, especialmente Delphi e Python, têm um escopo "intermediário" que permite isso: métodos de classe. Um método de classe não é um método de instância comum, mas também não é estático; ele recebe um selfparâmetro (divertidamente, os dois idiomas chamam o thisparâmetro self) que é uma referência ao tipo de objeto em si, e não a uma instância desse tipo. Com esse valor, agora você tem um tipo disponível para o envio virtual.

Infelizmente, nem a JVM nem o CLR têm algo comparável.

Mason Wheeler
fonte
São as linguagens usadas selfque possuem classes como objetos instanciados reais com métodos substituíveis - Smalltalk, é claro, Ruby, Dart, Objective C, Self, Python? Comparado com as linguagens usadas após o C ++, onde as classes não são objetos de primeira classe, mesmo que haja algum acesso à reflexão?
precisa saber é o seguinte
Só para esclarecer, você está dizendo que, em Python ou Delphi, você pode substituir os métodos de classe?
Erik Eidt
@ErikEidt No Python, praticamente tudo é substituível. No Delphi, um método de classe pode ser declarado explicitamente virtuale substituído em uma classe descendente, assim como os métodos de instância.
Mason Wheeler
Então, essas linguagens estão fornecendo algum tipo de noção de metaclasse, não?
Erik Eidt
1
O @ErikEidt Python possui metaclasses "verdadeiras". No Delphi, uma referência de classe é um tipo de dados especial, não uma classe em si mesma.
Mason Wheeler
3

Você pergunta

Por que as funções estáticas não devem ser substituíveis?

eu pergunto

Por que as funções que você deseja substituir sejam estáticas?

Certos idiomas forçam você a iniciar o show de forma estática. Mas depois disso, você realmente pode resolver muitos problemas sem mais métodos estáticos.

Algumas pessoas gostam de usar métodos estáticos sempre que não há dependência de estado em um objeto. Algumas pessoas gostam de usar métodos estáticos para a construção de outros objetos. Algumas pessoas gostam de evitar métodos estáticos, tanto quanto possível.

Nenhuma dessas pessoas está errada.

Se você precisar que seja substituível, pare de rotulá-lo como estático. Nada vai quebrar porque você tem um objeto apátrida voando por aí.

candied_orange
fonte
Se o idioma suportar estruturas, um tipo de unidade pode ser uma estrutura de tamanho zero, que é passada para o método de entrada lógica. A criação desse objeto é um detalhe de implementação. E se começar a falar sobre detalhes de implementação como a verdade real, então eu acho que você também precisa considerar que, em uma implementação real, um tipo de tamanho zero não tem que ser realmente instanciado (porque não são necessários instruções para carregar ou armazene).
Theodoros Chatzigiannakis
E se você está falando ainda mais "nos bastidores" (por exemplo, no nível do executável), os argumentos do ambiente podem ser definidos como um tipo na linguagem e o ponto de entrada "real" do programa pode ser um método de instância esse tipo, atuando em qualquer instância que foi transmitida ao programa pelo carregador do SO.
Theodoros Chatzigiannakis
Eu acho que depende de como você olha para isso. Por exemplo, quando um programa é iniciado, o sistema operacional o carrega na memória e depois vai para o endereço em que reside a implementação individual do ponto de entrada binário do programa (por exemplo, o _start()símbolo no Linux). Nesse exemplo, o executável é o objeto (possui sua própria "tabela de despacho" e tudo) e o ponto de entrada é a operação despachada dinamicamente. Portanto, o ponto de entrada de um programa é inerentemente virtual quando visto de fora e pode ser facilmente visualizado quando visto de dentro também.
Theodoros Chatzigiannakis
1
"Nada vai quebrar porque você tem um objeto apátrida voando por aí." + + + + + +
RubberDuck
@TheodorosChatzigiannakis você faz um argumento forte. se nada mais me convenceu, a frase não inspira a linha de pensamento que pretendia. Eu estava realmente tentando dar às pessoas permissão para ignorar algumas das práticas mais habituais que elas associam cegamente a métodos estáticos. Então eu atualizei. Pensamentos?
Candied_orange 7/09/16
2

Por que os métodos estáticos não deveriam ser substituíveis?

Não é uma questão de "deveria".

"Substituir" significa "despachar dinamicamente ". "Método estático" significa "despachar estaticamente". Se algo é estático, não pode ser substituído. Se algo pode ser substituído, não é estático.

Sua pergunta é bastante semelhante à pergunta: "Por que os triciclos não deveriam ter quatro rodas?" A definição de "triciclo" é que tem três rodas. Se é um triciclo, não pode ter quatro rodas, se tem quatro rodas, não pode ser um triciclo. Da mesma forma, a definição de "método estático" é que ele é enviado estaticamente. Se é um método estático, não pode ser despachado dinamicamente, se pode ser despachado dinamicamente, não pode ser um método estático.

É claro que é perfeitamente possível ter métodos de classe que possam ser substituídos. Ou você pode ter uma linguagem como Ruby, onde classes são objetos como qualquer outro objeto e, portanto, podem ter métodos de instância, o que elimina completamente a necessidade de métodos de classe. (Ruby possui apenas um tipo de método: métodos de instância. Ele não possui métodos de classe, métodos estáticos, construtores, funções ou procedimentos.)

Jörg W Mittag
fonte
1
"Por definição" Bem colocado.
Candied_orange 7/09/16
1

portanto, funções estáticas em C # não podem ser virtuais ou abstratas

Em C #, você sempre chama membros estáticos usando a classe, por exemplo BaseClass.StaticMethod(), não baseObject.StaticMethod(). Portanto, eventualmente, se você ChildClassherdou de BaseClasse childObjectuma instância de ChildClass, não poderá chamar seu método estático de childObject. Você sempre precisará usar explicitamente a classe real, para que static virtualsimplesmente não faça sentido.

O que você pode fazer é redefinir o mesmo staticmétodo na sua classe filho e usar a newpalavra - chave.

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

Se fosse possível ligar baseObject.StaticMethod(), sua pergunta faria sentido.

user276648
fonte