Por que as interfaces exigem métodos sobre os membros?


8

... Como isso nos força a criar getters e setters, que na prática geralmente são totalmente estranhos? Existe algum bom motivo de design de linguagem para que as interfaces na maioria dos idiomas (todos?) Não permitam que os campos de membros ajudem a atender às especificações da interface?

Na maioria dos idiomas existentes, os membros são tratados fundamentalmente de maneira diferente dos métodos, mas será que isso acontece? Em uma linguagem teórica, não poderíamos ter uma interface que (a) especificou um método zero-args como getter, mas aceitou um membro legível como implementador e (b) especificou um método de argumento único como setter, mas aceitou um campo de membro gravável como seu implementador, considerando um idioma que suportava a especificação de controle de acesso direto a variáveis ​​quando elas são declaradas? Uma linguagem prototípica que permita herança múltipla (para abordar o ponto muito válido que alguns entrevistados fizeram) seria ideal para algo assim.

Por favor, informe-me se algo assim já existir.


2
Os métodos são membros. Presumo que você queira dizer "campos membros"?
Aaronaught 6/08/11

OK para usar a terminologia Java apropriada, sim, "campos de membros". Obrigado por apontar isso.
Engenheiro

1
OOP é péssimo, mas tente C # se necessário.
Job

Respostas:


3

Obviamente, na maioria dos idiomas existentes, os membros são tratados fundamentalmente de maneira diferente dos métodos, mas será que isso acontece?

Não por qualquer motivo teoricamente fundamentado. Eu acho que as razões pelas quais você não vê muitas vezes são práticas:

A primeira é como você lida com essas coisas em tempo de execução. Em Java ou C ++, foo.x = 5é fácil lidar com uma atribuição a um membro público do tipo primitivo, como : coisas 5em algum registro, descobrir onde na memória o xmembro foovive e colocar o registro lá. Em um idioma como C #, em que as classes podem encapsular tarefas de interceptação para membros ou pré-calcular como esses membros avaliarão , o compilador não terá a prioriconhecimento sobre se a implementação de uma classe disponibilizada em tempo de execução usa esse recurso. Isso significa que todas as atribuições e avaliações de membros públicos devem ser adiadas para a implementação. * As operações que envolvem muito acesso a esses membros acabam sofrendo um impacto no desempenho porque estão constantemente fazendo chamadas de sub-rotina que podem ser tratadas mais rapidamente com regularidade. atribuições em linha. Deixar a avaliação e a tarefa separadas dos getters e setters dá ao desenvolvedor algum controle sobre como eles são tratados em uma determinada situação.

O segundo é a idade. Com a possível exceção do F #, nenhum dos idiomas atualmente em uso muito amplo tem menos de uma década e, mesmo recentemente, os ciclos adicionais necessários para possibilitar a avaliação e a tarefa interceptadas podem não ter sido aceitáveis.

Terceiro é a inércia. Eu acho que, até certo ponto, ainda há algum estigma associado à atribuição direta a membros do público, mesmo que a linguagem permita que a classe os manipule e as pessoas ainda pensem em termos de melhoras e melhoras. Isso vai se desgastando com o tempo, assim como aconteceu com todas as pessoas que estavam com o nariz no ar sobre as pessoas que escreveram programas em idiomas de nível superior em vez de assembly.


* Existe uma desvantagem que vem com isso: uma linguagem tem que ficar muito complicada ao adiar avaliações / atribuições para a classe ou recusar em tempo de execução se o código que usa avaliações / atribuições em linha para uma determinada classe tenta se vincular a uma implementação que as intercepta . Pessoalmente, acho que a última é uma má ideia, porque força a recompilação de código que usa uma API inalterada.


Obrigado, você entendeu o impulso da minha pergunta e entrou nos detalhes sombrios do PORQUÊ. Eu tinha um palpite de que a velocidade seria uma das razões - como sempre acontece quando você segue o caminho de "mais dinâmico". Como estamos vendo agora em linguagens como JS e Python, a flexibilidade geralmente supera a velocidade, então a "inércia" mencionada em relação a certas tendências de design de linguagem com base na otimização (que antes era necessária) se dissolve lentamente.
Engenheiro de

O F # é fortemente baseado no Objective CAML - pode ser mais novo que uma década, mas a maioria (provavelmente todas) das idéias é mais antiga.
Steve314

1
Estou meio curioso por que os dois abaixadores pressionaram o botão vermelho.
Blrfl

@Blrfl: Meu ponto de crítica seria que sua resposta se concentre muito nos detalhes da implementação, em vez de analisá-la do ponto de vista do design da linguagem. Eu não te votei por isso, mas talvez outros o tenham.
Niels van der Rest

Scala também é relativamente jovem e é amplamente utilizado
Nome para exibição

10

Na maioria das linguagens existentes, os membros [fields?] São tratados fundamentalmente de maneira diferente dos métodos, mas será que isso acontece?

Sim.

Os campos de membros são dados por instância. Os dados devem realmente existir em algum lugar da memória para cada instância - deve haver um endereço ou uma sequência de endereços reservados para ele. Em Java, .NET e muitas outras linguagens OO, esses dados vão para a pilha.

Métodos são dados por classe. Eles são indicadores de uma tabela. É assim que os métodos obtêm despacho virtual. Na verdade, existe apenas uma instância alocada, por classe.

Uma interface é essencialmente um tipo especial de classe que contém apenas métodos, sem dados de instância. Isso faz parte da definição de interface. Se contivesse algum dado, não seria mais uma interface, seria uma classe abstrata. Ambas as opções são boas, é claro, dependendo do tipo de padrão de design que você está tentando implementar. Mas se você incluísse dados da instância nas interfaces, removeria tudo o que as torna interfaces.

OK, então por que um método de interface não pode apontar para um campo de membro? Porque não há nada no nível da classe para apontar. Novamente, uma interface nada mais é que uma tabela de métodos. O compilador pode gerar apenas uma tabela e todos os métodos nele devem apontar para o mesmo endereço de memória. Portanto, é impossível para os métodos de interface apontar para os campos da classe, porque o campo da classe é um endereço diferente para cada instância.

Talvez o compilador possa aceitar isso de qualquer maneira e gerar um método getter para o qual a interface aponte - mas isso essencialmente se torna uma propriedade . Se você quer saber por que o Java não tem propriedades, bem ... essa é uma história muito longa e tediosa na qual eu prefiro não entrar aqui. Mas se você estiver interessado, você pode querer ter um olhar para outras linguagens OO que fazer implementar propriedades, como C # ou Delphi. A sintaxe é realmente fácil:

public interface IFoo
{
    int Bar { get; }
}

public class Foo : IFoo
{
    public int Bar { get; set; }
}

Sim, é só isso. Importante: Esses não são campos, a "barra int" da Fooclasse é uma propriedade automática e o compilador está fazendo exatamente o que acabei de descrever acima: gerando automaticamente getters e setters (assim como o campo de membro de suporte).

Então, para recapitular a resposta à sua pergunta:

  • As interfaces requerem métodos porque seu objetivo inteiro não é conter dados;
  • A razão pela qual não há açúcar sintático para facilitar a implementação de uma interface por uma classe é simplesmente que os designers de Java não querem - e, nesse momento, a compatibilidade com versões anteriores é um problema. Os designers de idiomas fazem esse tipo de escolha; você apenas terá que lidar com isso ou mudar.

Esta é uma boa resposta, em particular achei as informações sobre a pesquisa de funções informativas. Obrigado.
Engenheiro de

9

isso é resultado da ocultação de informações, ou seja, esses membros não devem estar visíveis como campos, mas como propriedades que podem ou não ser armazenadas / armazenadas em cache localmente

um exemplo é o código de hash que muda quando o objeto é alterado, portanto, é melhor calculá-lo quando solicitado e não sempre que o objeto é alterado

Além disso, a maioria das linguagens é herança única + multi-implementos e permitir que os campos façam parte de interfaces coloca você em território de herança múltipla (só falta uma implementação padrão de funções), que é um campo minado de casos extremos para acertar


campos de implementação múltipla é o problema aqui.
DeadMG

Devo dizer que, pensando mais sobre isso, não concordo realmente com herança múltipla. Porque o que realmente estou perguntando é se é possível uma representação de membros de dados em interfaces, e não se você pode usar membros de dados para construir interfaces (que é essencialmente uma classe abstrata de C ++). Há uma diferença fundamental lá.
Engenheiro

Não há realmente nenhum problema com o MI; uma linguagem precisaria se recusar a implementar duas interfaces com tipos conflitantes para o mesmo membro.
Blrfl

6

As variáveis ​​de membro são a implementação da classe, não a interface. Você pode alterar a implementação; portanto, outras classes não devem ter permissão para se referir a essa implementação diretamente.

Considere uma classe com os seguintes métodos - sintaxe C ++, mas isso não é importante ...

class example
{
  public:
    virtual void Set_Mode (int p_mode);
    virtual int  Mode () const;
};

Parece bastante óbvio que haverá uma variável membro chamada modeou m_modesimilar que armazena diretamente esse valor de modo, fazendo o que uma "propriedade" faria em alguns idiomas - mas isso não é necessariamente verdade. Há muitas maneiras diferentes de lidar com isso. Por exemplo, o método Set_Mode pode ...

  1. Identifique o modo usando uma instrução switch.
  2. Instancie uma subclasse de alguma classe interna, dependendo do valor desse modo.
  3. Armazene um ponteiro para essa instância como uma variável de membro.

Feito isso, muitos outros métodos da classe de exemplo podem chamar métodos apropriados dessa classe interna por meio do ponteiro, obtendo um comportamento específico do modo sem precisar verificar qual é o modo atual.

O ponto aqui é menos que há mais de uma maneira possível de implementar esse tipo de coisa e muito mais que em algum momento você possa mudar de idéia. Talvez você tenha começado com uma variável simples, mas todas as instruções do switch para identificar o modo em todos os métodos estão sofrendo. Ou talvez você tenha começado com a coisa de apontador de instância, mas isso acabou sendo muito pesado para a sua situação.

Acontece que coisas assim podem acontecer para qualquer dado de membro. Pode ser necessário alterar as unidades nas quais um valor é armazenado de milhas para quilômetros, ou você pode achar que o seu enumerar de identificação exclusiva do caso não pode mais identificar exclusivamente todos os casos sem considerar algumas informações extras ou o que for.

Isso também pode acontecer para métodos - alguns são puramente para uso interno, dependem da implementação e devem ser privados. Mas muitos métodos fazem parte da interface e não precisam ser renomeados ou o que quer que seja, apenas porque a implementação interna da classe foi substituída.

De qualquer forma, se sua implementação for exposta, outro código começará inevitavelmente a depender dela, e você ficará preso a mantê-la. Se sua implementação estiver oculta de outro código, essa situação não poderá acontecer.

O bloqueio do acesso aos detalhes da implementação é chamado de "ocultação de dados".


"Código para uma interface, não uma implementação" ... Certo. Às vezes sabemos essas coisas ultimamente, mas é preciso que alguém as discuta como você fez, para realmente solidificar os conceitos mais uma vez.
Engineer

OK, espere um minuto. Após uma reflexão mais aprofundada, consulte minha edição da pergunta.
Engineer

É um pouco difícil descobrir qual foi sua edição - por algum motivo, as diferenças para algumas edições mais antigas não são exibidas. O que eu acho que você está dizendo, porém, é "por que uma propriedade não pode ter uma variável de membro como uma implementação padrão?" Uma propriedade se parece (interface) com uma variável membro, mas é implementada através de métodos getter / setter. E, na IMO, não razão para que, por padrão, esses métodos getter / setter não implementem uma leitura / gravação em uma variável membro - ou porque as chamadas getter e setter não devem ser otimizadas quando é seguro - permitindo herança, separação compilação etc.
Steve314

@ Nick - Então sim, contanto que seja possível dizer "o getter e / ou o setter para essa propriedade devem ser puros" ou " não devem ser os padrão". Acho que é uma boa idéia - eliminar parte da bagunça de um caso comum com propriedades. Não sei se existe um idioma que já faz isso - não usei propriedades muito mesmo nos idiomas em que existem - mas ficaria surpreso se isso já não for suportado.
Steve314

3

Como outros observaram, uma interface descreve o que uma classe faz, não como ela faz. É essencialmente um contrato ao qual as classes herdadas devem estar em conformidade.

O que você descreve é ​​um pouco diferente - uma classe que fornece alguma implementação, mas talvez não toda, como uma forma de reutilização. Elas são geralmente conhecidas como classes abstratas e são suportadas em várias linguagens (por exemplo, C ++ e Java). Essas classes não podem ser instanciadas por si mesmas, apenas podem ser herdadas por classes concretas (ou abstratas).

Novamente, como já foi observado, as classes abstratas tendem a ter maior valor em idiomas que suportam herança múltipla, pois podem ser usadas para fornecer partes de funcionalidade adicional a uma classe maior. Geralmente, isso é considerado um estilo ruim, pois geralmente um relacionamento de agregação ("tem-a") seria mais apropriado


Boa resposta. Costumo favorecer a composição quase inteiramente no tipo de tarefas que realizo atualmente. Pessoalmente, não acredito no tratamento separado de classes abstratas e interfaces em certas linguagens, como outros membros deste fórum declararam, embora haja razões para isso, geralmente essas razões se devem a limitações fundamentais de linguagem (como mencionado no meu comentário) na resposta da catraca aberração).
Engineer

1
Costumo concordar com o seu comentário sobre: ​​abstract / interfaces. Quando eu olhar para trás, código antigo onde eu usei um monte de classes abstratas, é claro para mim com mais experiência 10-15 anos que eu estava usando recursos de linguagem para o código em torno do projeto mau
Steve Mallam

1

Por favor, informe-me se algo assim já existir.

As propriedades do Objective-C fornecem esse tipo de funcionalidade com seus acessadores de propriedades sintetizados. Uma propriedade Objective-C é essencialmente apenas uma promessa de que uma classe fornece acessadores, normalmente do formulário foopara o getter e setFoo:para o setter. A declaração de propriedade especifica certos atributos dos acessadores relacionados ao gerenciamento de memória e aos comportamentos de encadeamento. Há também uma @synthesizediretiva que diz ao compilador para gerar acessadores e até a variável de instância correspondente, para que você não precise se preocupar em escrever getters e setters ou declarar um ivar para cada propriedade. Há também algum açúcar sintático que faz com que o acesso à propriedade pareça acessar os campos de uma estrutura, mas na verdade resulta em chamar os acessadores de propriedade.

O resultado disso é que posso declarar uma classe com propriedades:

@interface MyClass : NSObject
{}
@property (assign) int foo;
@property (copy) NSString *bar;
@end

e então posso escrever um código que parece acessar diretamente ivars, mas na verdade usa métodos de acessador:

MyClass *myObject = [[MyClass alloc] init];  // create myObject

// setters
myObject.foo = 127;                 // same as [myObject setFoo:127];
myObject.bar = @"Hello, world!";    // same as [myObject setBar:@"Hello, world!"];

// getters
int i = myObject.foo                // same as [myObject foo];
NSString *s = myObject.bar          // same as [myObject bar];

Já foi dito que os acessadores permitem separar a interface da implementação, e o motivo que você deseja fazer é preservar sua capacidade de alterar a implementação no futuro sem afetar o código do cliente. A desvantagem é que você precisa ser disciplinado o suficiente para escrever e usar acessadores apenas para manter suas opções futuras em aberto. O Objective-C facilita a geração e o uso de acessadores, assim como o uso direto de ivars. De fato, como eles cuidam de grande parte do trabalho de gerenciamento de memória, o uso de propriedades é muito mais fácil do que acessar diretamente os ivars. E quando chegar a hora de alterar sua implementação, você sempre poderá fornecer seus próprios acessadores em vez de permitir que o compilador os gere para você.


1

Por que as interfaces exigem métodos sobre os membros?

... Como isso nos força a criar getters e setters, que na prática geralmente são totalmente estranhos? Existe algum bom motivo de design de linguagem para que as interfaces na maioria dos idiomas (todos?) Não permitam que os membros ajudem a atender às especificações da interface?

"A maioria" dos idiomas? Quais idiomas você investigou? Em linguagens dinâmicas, não há interfaces. Existem apenas três ou quatro linguagens OO de tipo estatístico e com popularidade: Java, C #, Objective-C e C ++. C ++ não possui interfaces; em vez disso, usamos classes abstratas, e uma classe abstrata pode conter um membro de dados público. C # e Objective-C suportam propriedades, portanto, não há necessidade de getters e setters nesses idiomas. Isso deixa apenas o Java. Presumivelmente, o único motivo pelo qual o Java não suporta propriedades é que tem sido difícil introduzi-las de maneira compatível com versões anteriores.


Os membros deveriam se referir a "campos de membros" em oposição a "funções de membros", aos quais me refiro simplesmente como métodos. Não tenho certeza de qual idioma (se houver) aceitei esta convenção. Portanto, considerando o que eu escrevi, vejo sua resposta, mas acho que um comentário na minha pergunta para esclarecer teria sido mais construtivo.
Engenheiro

Em C ++, toque. No entanto, na verdade existem interfaces em linguagens dinâmicas, JS e Python sendo duas que imediatamente vêm à mente. Se um proponente de linguagens estaticamente chamadas chamaria essas "interfaces", é outra discussão em que não vejo sentido em entrar. Pessoalmente, mostro pouca preferência pelos dois lados dessa cerca, usando uma ampla variedade de idiomas para diferentes fins.
Engenheiro

@ Nick: JS e Python têm herança, mas eu não diria que eles têm interfaces; pode-se chamar qualquer método em qualquer objeto.
kevin Cline

1

Algumas das respostas mencionam 'ocultação de informações' e 'detalhes de implementação' como os métodos de interface da razão de preferência dos campos. Eu acho que o principal motivo é mais simples que isso.

As interfaces são um recurso de linguagem que permite trocar o comportamento, fazendo com que várias classes implementem uma interface. O comportamento de uma classe é definido por seus métodos. Os campos, por outro lado, refletem apenas o estado de um objeto.

Obviamente, os campos ou propriedades de uma classe podem fazer parte do comportamento, mas não são a parte importante do contrato definido pela interface; eles são apenas um resultado. É por isso que, por exemplo, o C # permite definir propriedades em uma interface e em Java você define um getter na interface. É aqui que um pouco de ocultação de informações entra em jogo, mas esse não é o ponto principal.

Como eu disse antes, as interfaces permitem que você troque o comportamento, mas vamos dar uma olhada na parte da troca. Uma interface permite alterar a implementação subjacente , criando uma nova classe de implementação. Portanto, ter uma interface composta por campos não faz muito sentido, porque você não pode mudar muito sobre a implementação de campos.

O único motivo pelo qual você deseja criar uma interface somente de campo é quando você quer dizer que alguma classe é um determinado tipo de objeto. Eu enfatizei a parte 'é uma', porque você deve usar herança em vez de interfaces.


Nota lateral: Eu sei que a composição preferida em relação à herança e composição geralmente só pode ser alcançada usando interfaces, mas o uso de interfaces apenas para 'marcar' uma classe com algumas propriedades também é ruim. A herança é excelente quando usada adequadamente, e desde que você se atenha ao Princípio da Responsabilidade Única, não deverá ter problemas.

Quero dizer, nunca trabalhei com uma biblioteca de controle de interface do usuário, por exemplo, que não usa herança. A única área em que as interfaces entram em jogo aqui é no provisionamento de dados, por exemplo, ITableDataAdapterpara vincular dados a um controle de tabela. Esse é exatamente o tipo de coisa para a qual as interfaces se destinam, para ter um comportamento intercambiável .


0

Em Java, o motivo é que as interfaces permitem apenas especificar métodos .

Portanto, se você quiser algo com um campo no contrato de interface, precisará fazê-lo como método getter e setter.

Consulte também esta pergunta: Quando os Getters e Setters são justificados


1
Obrigado por vir responder, na verdade, foi a sua resposta sobre esse mesmo tópico que me levou a fazer essa pergunta :) Mas a pergunta permanece: "-Por que eles apenas permitiram métodos?" O objetivo das interfaces não é especificar métodos, é lógica cíclica. O objetivo das interfaces é garantir que algum grau básico de funcionalidade seja cumprido. Os membros podem fazer parte dessa funcionalidade. Então, por que não permitir que eles participem do preenchimento da interface?
Engenheiro

Eu não sei. Talvez se você perguntasse diretamente a Gosling ou Steele?

-1

O objetivo de uma interface é especificar funções que você deve implementar para reivindicar o suporte a essa interface.


2
Isso não responde à pergunta. A questão é: por que eles fizeram dessa maneira? Você já pensou em não escrever getters e setters para todas as variáveis ​​e ainda conseguir cumprir uma interface? Isso economizaria tempo!
Engenheiro de

Você continua perguntando sobre caçadores e caçadores, e eu não tenho idéia do porquê. Observe uma interface simples: download.oracle.com/javase/6/docs/api/java/util/… Observe como não há getters e setters?
Scott C Wilson

1
O que isso prova? Esse exemplo prova que você nunca precisará de acesso simples / direto aos membros? Não. O ponto é que é bobagem ter membros de agrupamento em getters / setters como parte da interface, se o próprio idioma pudesse lidar com isso automaticamente.
Engineer

Existem centenas de outros exemplos como este. Você pode me dar um exemplo da situação de que está falando?
Scott C Wilson

-1

Os membros não são permitidos nas interfaces porque, então, esses idiomas teriam que lidar com o mal horrendo que é a herança múltipla.


2
Não tenho certeza se eu sigo. Por que permitir variáveis ​​de membro em uma interface exigiria ou implicaria suporte para herança múltipla?
Steve Mallam
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.