Usar uma interface vazia é muito horrendo?

23 respostas
R

Boa tarde colegas, vou explanar a situação pra vocês.

Estou desenvolvendo em dupla com um colega um projeto de final de semestre. Em resumo estamos seguindo uma especificação que o professor nos deu, onde precisamos aplicar os conhecimentos de Herança, Polimorfismo, Sobrescrita, Sobrecarga e Interfaces aprendidos durante o semestre.

Porém chegamos em uma parte do sistema, que - pode ser devido ao mau dimensionamento do diagrama de classes, ou algum outro problema que não estamos encontrando - vemos necessidade de usar uma interface vazia.

Interface vazia pra que?

Pra não usar o maldito instanceof e matar todo o polimorfismo que estamos mantendo até então.

O que vocês acham colegas? Essa é uma abordagem muito bizarra? Já presenciaram esse tipo de “técnica” no dia-a-dia?

Li um tópico aqui onde um user encontrou esse caso no trabalho, e inclusive estava com dúvidas sobre o motivo de se ter uma interface vazia.

Vejo que essa é a única solução que vai me ajudar a não meter vários instanceof inúteis no meu código.

Abraços!

23 Respostas

R

Bom, exemplo de interface vazia é a Serializable do próprio Java…
Mas ser do Java não significa que é bom.

Eu acho ruim e sim, horrendo.

Vamos ver o seguinte, você vai usar esses objetos para quê? COMO você vai usar essas classes diferentes, se vc precisar acessar algum método em comum, essa interface pode ser a saída…

No mais, você pode fazer heranças de interface, se for o caso, ai ela pode ficar vazia, mas ela é composta de várias interfaces…

Pode mostrar o código?

R

Rafael Guerreiro:
Bom, exemplo de interface vazia é a Serializable do próprio Java…
Mas ser do Java não significa que é bom.

Eu acho ruim e sim, horrendo.

Vamos ver o seguinte, você vai usar esses objetos para quê? COMO você vai usar essas classes diferentes, se vc precisar acessar algum método em comum, essa interface pode ser a saída…

No mais, você pode fazer heranças de interface, se for o caso, ai ela pode ficar vazia, mas ela é composta de várias interfaces…

Pode mostrar o código?

No meu caso, tenho que simular um Computador.

O meu problema é esse: Tenho uma classe PortaParalela. Na PortaParalela posso acoplar tanto um Leitor(CD ou DVD por exemplo) como um DispositivoArmazenamentoParalelo(um HD por exemplo).

Por isso veio a idéia de criar uma interface que une Leitor e DispositivoArmazenamento, e fazer com que PortaParalela receba qualquer dispositivo que implemente tal interface.

R

Como o colega acima já falou, a própria API java possui marker interfaces, interfaces vazias. Dependendo do motivo dá pra aceitar.

R

Ruttmann:

No meu caso, tenho que simular um Computador.

O meu problema é esse: Tenho uma classe PortaParalela. Na PortaParalela posso acoplar tanto um Leitor(CD ou DVD por exemplo) como um DispositivoArmazenamentoParalelo(um HD por exemplo).

Por isso veio a idéia de criar uma interface que une Leitor e DispositivoArmazenamento, e fazer com que PortaParalela receba qualquer dispositivo que implemente tal interface.


Mas existe algum método que você queira usar, como “salvar(Arquivo arquivo)”, “ler(Arquivo arquivo)”, “remover(Arquivo arquivo)”?

Dê uma olhadinha na Lei de Demeter, acho que ela é a saída que você procura.

Por exemplo, temos a classe Pessoa e a classe Endereço:

class Pessoa {
   private String nome;
   private Endereco endereco;

// getters e setters
}

class Endereco {
   private String rua;
   private int numero;
   private String cep;

// getters e setters
}

Então para acessar o cep de uma pessoa, você precisa fazer assim:

pessoa.getEndereco().getCep();

Isso gera um custo elevado de manutenção. Vamos supor que vamos criar a classe CEP e dentro dela o getCepFormatado() iria formatar o cep para nós… Teríamos que alterar todos os lugares que fazem o getCep() para incluir um .getCepFormatado()…

Portanto, seria menos custoso manter um código que fizesse assim:

pessoa.getCep();

Pois assim, só precisaria mexer na classe Pessoa… Para que isso aconteça, as nossas classes precisam ficar assim:

class Pessoa {
   private String nome;
   private Endereco endereco;

// Construtor para setar

   public String getNome() {
      return nome;
   }

   public String getCep() {
      return endereco.getCep();
   }
}

class Endereco {
   private String rua;
   private int numero;
   private String cep;

// getters e setters
}

E depois de arrumado, ficaria assim:

class Pessoa {
   private String nome;
   private Endereco endereco;

// Construtor para setar

   public String getNome() {
      return nome;
   }

   public String getCep() {
      return endereco.getCepFormatado();
   }
}

class Endereco {
   private String rua;
   private int numero;
   private CEP cep;

// Construtor para setar

   public String getCepFormatado() {
      return cep.getCepFormatado();
   }

// outros getters
}

class CEP {
   private primeirosDigitos;
   private ultimosDigitos;

// Construtor para setar

   public String getCepFormatado() {
      return primeirosDigitos + "-" + ultimosDigitos;
   }
}
I

Porque não cria uma classe abstrata pai? Com isso vc acaba criando uma camada a mais (acima) e caso precise algo que todas suas subclasses forem precisar um dia, fica bem mais manutenívele dará pra fazer o polimorfismo. Se fizer com interfaces, todas as classes que implementam ela vai quebrar, pois vai ter que obrigatoriamente implementar esse novo método.

R

Só aproveitando: No Java 8 vai ser possível adicionar métodos a interfaces sem quebrar código já existente. Serão os chamados “métodos default”. :slight_smile:

Foi a gambiarra que encontraram para evoluir as interfaces da API Collections para acomodar as lambda expressions.

H

rodrigo.uchoa:
Só aproveitando: No Java 8 vai ser possível adicionar métodos a interfaces sem quebrar código já existente. Serão os chamados “métodos default”. :slight_smile:

Foi a gambiarra que encontraram para evoluir as interfaces da API Collections para acomodar as lambda expressions.

Só uma observação, adicionar “método” apenas um e não muitos. [=

A

Hum…tem certeza disso? Não achei nada relacionado a essa limitação de ser apenas um método por interface.

Eu sei que interfaces com apenas um método terão um tratamento especial (acho que consideradas funções, não tenho certeza), mas não vi essa limitação de apenas um método por interface.

A

Respondendo a pergunta do tópico: qual o sentido de você ter diferentes componentes implementando a mesma interface, se você não consegue extrair comportamento de nenhum deles?

No caso da porta paralela, a real, ela deve ter um protocolo padrão para se comunicar com qualquer componente que se conecte a ela. O componente tem que implementar esse protocolo.

O protocolo seria justamente o comportamento que você não está definindo porque a interface está vazia.

H

Hum…tem certeza disso? Não achei nada relacionado a essa limitação de ser apenas um método por interface.

Eu sei que interfaces com apenas um método terão um tratamento especial (acho que consideradas funções, não tenho certeza), mas não vi essa limitação de apenas um método por interface.Foi o que o representante da Oracle disse no TDC 2013. [=

A

Acho que ele se enganou nessa.

Testando aqui o early access release do Java 8, essa funcionalidade já está disponível (default methods ou defender methods).
E ela aceita mais de um método concreto sem problemas.
Espero que não tirem isso…

Tem o conceito agora de Functional Interface, que é uma interface com apenas um método abstrato e que pode ser convertido em lambda.

Então uma Runnable da vida, por exemplo, não precisará mais de uma classe anônima.
Você poderá passar só uma lambda direto.

H

Acho que ele se enganou nessa.

Testando aqui o early access release do Java 8, essa funcionalidade já está disponível (default methods ou defender methods).
E ela aceita mais de um método concreto sem problemas.
Espero que não tirem isso…

Tem o conceito agora de Functional Interface, que é uma interface com apenas um método abstrato e que pode ser convertido em lambda.

Então uma Runnable da vida, por exemplo, não precisará mais de uma classe anônima.
Você poderá passar só uma lambda direto.
Legal. [=
Pode ser então que ele estava falando sobre Functional Interface e eu me confundi.

My Bad. [=

V

Os default methods me parecem uma implementação desajeitada dos extension methods, do C#. E, pelo visto, tem as limitações da herança múltipla do C++. Me parece fugir completamente do conceito de interfaces prover implementações. O estranho é que, apesar de inserirem esses recursos extremamente complexos e sujeitos a erros, a comunidade ainda insiste em manter de fora da linguagem a sobrecarga de operadores. =(

Mas, respondendo ao colega. No caso, o seu uso de interfaces vazias parece correto. Entretanto, não há absolutamente nenhum método que suas interfaces tenham em comum e que tenham que ser implementados? Por exemplo, como a classe que usa as interfaces “conversa” com elas?

Me parece que tanto leitor quando dispositivo de armazenamento poderiam implementar uma interface chamada de “OrigemDeDados”. E essa interface poderia conter, no mínimo, o método “ler()” que traria dados de seja lá o que esses dispositivos representam (mais ou menos na mesma idéia dos InputStreams, do próprio Java). Outros métodos possíveis seriam “ligar()” e “desligar()”, que faria os dispositivos acionarem e desligarem.

R

Poxa, que discussão que essa dúvida já levantou! Muito legal! :slight_smile:

ViniGodoy:
Mas, respondendo ao colega. No caso, o seu uso de interfaces vazias parece correto. Entretanto, não há absolutamente nenhum método que suas interfaces tenham em comum e que tenham que ser implementados? Por exemplo, como a classe que usa as interfaces “conversa” com elas?

Me parece que tanto leitor quando dispositivo de armazenamento poderiam implementar uma interface chamada de “OrigemDeDados”. E essa interface poderia conter, no mínimo, o método “ler()” que traria dados de seja lá o que esses dispositivos representam (mais ou menos na mesma idéia dos InputStreams, do próprio Java). Outros métodos possíveis seriam “ligar()” e “desligar()”, que faria os dispositivos acionarem e desligarem.

Criei a interface vazia, e aliás eu estava com dois casos de interfaces vazias. Já estava até levantando os argumentos pra me defender com o professor na entrega do trabalho.

Porém, ao longo do desenvolvimento (fiquei até as 4 da manhã dessa noite trabalhando nisso :stuck_out_tongue: ) consegui comportamentos em comum entre as classes que implementam essas interfaces.

No fim das contas, tanto faz se eu usar classes abstratas ou interfaces, se fosse pra ficar vazio seria melhor que fosse uma classe abstrata (tenho algumas ocorrências de classes abstratas vazias no projeto) ao invés de interface, acho que fica menos “horrendo”.

Estou montando o diagrama UML, logo edito o post e coloco ele aqui pra vocês darem uma olhada. Foi minha primera experiência em projetar um pequeno sistema, creio que minha estrutura de classes não é a mais correta, mas com a prática que tenho até então vejo que ficou muito bom. :slight_smile:

Obrigado a todos que deram suas opiniões e observações! :wink:

V

Não. Definitivamente as interfaces ficam menos horrendas do que classes abstratas. As classes abstratas representam um conceito forte, de tipos. Na evolução do sistema, elas também criariam compromissos com a implementação - enquanto a interface representa única e exclusivamente um compromisso com comportamento. Por isso é possível implementar mais de uma interface numa classe.

R

Pensando por esse lado conceitual você tem razão mesmo.

Estou terminando e logo vou postar o UML. :slight_smile:

R

Segue o diagrama! Ainda é uma versão beta, quase todas as funcionalidades estão implementadas. Estamos implementando a interface gráfica agora e pode ter alguma alteração, mas a base é essa. :slight_smile:

V

Por que existe na classe computador os métodos criarPortaParalela e criarPortaUSB? Não era melhor ter só um addPorta que recebe a classe mãe Porta? Assim, você não precisará alterar essa classe caso um novo tipo de porta surja.

No caso da pasta e dos arquivos, considere a possibilidade de implementar o padrão de projetos Composite.

De qualquer forma, ficou bem legal. Dá para ver que você se empenhou muito. Parabéns. :slight_smile:

R

ViniGodoy:
Por que existe na classe computador os métodos criarPortaParalela e criarPortaUSB? Não era melhor ter só um addPorta que recebe a classe mãe Porta? Assim, você não precisará alterar essa classe caso um novo tipo de porta surja.

No caso da pasta e dos arquivos, considere a possibilidade de implementar o padrão de projetos Composite.

De qualquer forma, ficou bem legal. Dá para ver que você se empenhou muito. Parabéns. :)

Você tem razão, a responsabilidade de criar portas não é do Computador. Já estou conversando com meu colega, e acho que vamos tirar estes métodos dali. Obrigado pela dica!

Quanto ao Composite, eu não conhecia, aliás faz tempo que adio meus estudos em Design Patterns. :stuck_out_tongue:

Estou lendo sobre ele aqui e tentarei implementar(se der tempo!).

Agradeço novamente! :slight_smile:

A

O Serializable do java não é uma inteface vazia;
Criar uma interface vazia na minha opinião ou é erro de modelagem ou é implementação desnecessária. Como é um trabalho acadêmico, verifique a possibilidade de criar novas funcionalidades para o sistema para aí sim você representar o conceito na prática.

R
alcionj:
rodrigo.uchoa:
Como o colega acima já falou, a própria API java possui marker interfaces, interfaces vazias. Dependendo do motivo dá pra aceitar.

O Serializable do java não é uma inteface vazia;
Criar uma interface vazia na minha opinião ou é erro de modelagem ou é implementação desnecessária. Como é um trabalho acadêmico, verifique a possibilidade de criar novas funcionalidades para o sistema para aí sim você representar o conceito na prática.

Abri ontem o fonte da Serializable, tenho o jdk 7 na minha máquina, e é uma interface vazia.

Veja:

/*
 * @(#)Serializable.java	1.25 05/11/17
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.io;

/**
 * Serializability of a class is enabled by the class implementing the
 * java.io.Serializable interface. Classes that do not implement this
 * interface will not have any of their state serialized or
 * deserialized.  All subtypes of a serializable class are themselves
 * serializable.  The serialization interface has no methods or fields
 * and serves only to identify the semantics of being serializable. <p>
 *
 * To allow subtypes of non-serializable classes to be serialized, the
 * subtype may assume responsibility for saving and restoring the
 * state of the supertype's public, protected, and (if accessible)
 * package fields.  The subtype may assume this responsibility only if
 * the class it extends has an accessible no-arg constructor to
 * initialize the class's state.  It is an error to declare a class
 * Serializable if this is not the case.  The error will be detected at 
 * runtime. <p>
 *
 * During deserialization, the fields of non-serializable classes will
 * be initialized using the public or protected no-arg constructor of
 * the class.  A no-arg constructor must be accessible to the subclass
 * that is serializable.  The fields of serializable subclasses will
 * be restored from the stream. <p>
 *
 * When traversing a graph, an object may be encountered that does not
 * support the Serializable interface. In this case the
 * NotSerializableException will be thrown and will identify the class
 * of the non-serializable object. <p>
 *
 * Classes that require special handling during the serialization and
 * deserialization process must implement special methods with these exact
 * signatures: <p>
 *
 * &lt;PRE&gt;
 * private void writeObject(java.io.ObjectOutputStream out)
 *     throws IOException
 * private void readObject(java.io.ObjectInputStream in)
 *     throws IOException, ClassNotFoundException;
 * private void readObjectNoData() 
 *     throws ObjectStreamException;
 * &lt;/PRE&gt;
 *
 * <p>The writeObject method is responsible for writing the state of the
 * object for its particular class so that the corresponding
 * readObject method can restore it.  The default mechanism for saving
 * the Object's fields can be invoked by calling
 * out.defaultWriteObject. The method does not need to concern
 * itself with the state belonging to its superclasses or subclasses.
 * State is saved by writing the individual fields to the
 * ObjectOutputStream using the writeObject method or by using the
 * methods for primitive data types supported by DataOutput.
 *
 * <p>The readObject method is responsible for reading from the stream and
 * restoring the classes fields. It may call in.defaultReadObject to invoke
 * the default mechanism for restoring the object's non-static and 
 * non-transient fields.  The defaultReadObject method uses information in 
 * the stream to assign the fields of the object saved in the stream with the 
 * correspondingly named fields in the current object.  This handles the case 
 * when the class has evolved to add new fields. The method does not need to 
 * concern itself with the state belonging to its superclasses or subclasses.
 * State is saved by writing the individual fields to the
 * ObjectOutputStream using the writeObject method or by using the
 * methods for primitive data types supported by DataOutput.
 *
 * <p>The readObjectNoData method is responsible for initializing the state of
 * the object for its particular class in the event that the serialization
 * stream does not list the given class as a superclass of the object being
 * deserialized.  This may occur in cases where the receiving party uses a
 * different version of the deserialized instance's class than the sending
 * party, and the receiver's version extends classes that are not extended by
 * the sender's version.  This may also occur if the serialization stream has
 * been tampered; hence, readObjectNoData is useful for initializing
 * deserialized objects properly despite a "hostile" or incomplete source
 * stream.
 *
 * <p>Serializable classes that need to designate an alternative object to be
 * used when writing an object to the stream should implement this
 * special method with the exact signature: <p>
 *
 * &lt;PRE&gt;
 * ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
 * &lt;/PRE&gt;<p>
 *
 * This writeReplace method is invoked by serialization if the method
 * exists and it would be accessible from a method defined within the
 * class of the object being serialized. Thus, the method can have private,
 * protected and package-private access. Subclass access to this method
 * follows java accessibility rules. <p>
 *
 * Classes that need to designate a replacement when an instance of it
 * is read from the stream should implement this special method with the
 * exact signature.<p>
 *
 * &lt;PRE&gt;
 * ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
 * &lt;/PRE&gt;<p>
 *
 * This readResolve method follows the same invocation rules and
 * accessibility rules as writeReplace.<p>
 *
 * The serialization runtime associates with each serializable class a version
 * number, called a serialVersionUID, which is used during deserialization to
 * verify that the sender and receiver of a serialized object have loaded
 * classes for that object that are compatible with respect to serialization.
 * If the receiver has loaded a class for the object that has a different
 * serialVersionUID than that of the corresponding sender's class, then
 * deserialization will result in an {@link InvalidClassException}.  A
 * serializable class can declare its own serialVersionUID explicitly by
 * declaring a field named &lt;code&gt;"serialVersionUID"&lt;/code&gt; that must be static,
 * final, and of type &lt;code&gt;long&lt;/code&gt;:<p>
 *
 * &lt;PRE&gt;
 * ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
 * &lt;/PRE&gt;
 *
 * If a serializable class does not explicitly declare a serialVersionUID, then
 * the serialization runtime will calculate a default serialVersionUID value
 * for that class based on various aspects of the class, as described in the
 * Java(TM) Object Serialization Specification.  However, it is &lt;em&gt;strongly
 * recommended&lt;/em&gt; that all serializable classes explicitly declare
 * serialVersionUID values, since the default serialVersionUID computation is
 * highly sensitive to class details that may vary depending on compiler
 * implementations, and can thus result in unexpected
 * &lt;code&gt;InvalidClassException&lt;/code&gt;s during deserialization.  Therefore, to
 * guarantee a consistent serialVersionUID value across different java compiler
 * implementations, a serializable class must declare an explicit
 * serialVersionUID value.  It is also strongly advised that explicit
 * serialVersionUID declarations use the &lt;code&gt;private&lt;/code&gt; modifier where
 * possible, since such declarations apply only to the immediately declaring
 * class--serialVersionUID fields are not useful as inherited members. Array
 * classes cannot declare an explicit serialVersionUID, so they always have
 * the default computed value, but the requirement for matching
 * serialVersionUID values is waived for array classes.
 *
 * @author  unascribed
 * @version 1.25, 11/17/05
 * @see java.io.ObjectOutputStream
 * @see java.io.ObjectInputStream
 * @see java.io.ObjectOutput
 * @see java.io.ObjectInput
 * @see java.io.Externalizable
 * @since   JDK1.1
 */
public interface Serializable {
}

Tem algo de errado aí?

V

Segundo o javadoc, Serializable é sim uma interface vazia, assim como Cloneable.

H

[quote=alcionj]

rodrigo.uchoa:
O Serializable do java não é uma inteface vazia;
Vc viu o link que você citou pelo menos? O.o

Criado 26 de novembro de 2013
Ultima resposta 27 de nov. de 2013
Respostas 23
Participantes 8