Dúvida quanto ao Prototype e o uso de interfaces

Oi pessoal.
Antes de mais nada, quero chegar pedindo desculpas pela ignorância no assunto. Estou estudando padrões de projeto e não estou achando muito fácil.
Já estudei alguns e agora estou estudando o Prototype, usando o livro do GoF e o Head First (o do GoF eu acho meio complicado de entender).
Peguei o exemplo da Wikipedia (do bolo).
Eu tenho uma classe Bolo, que contém o método clone. Como subclasse, tenho uma class BoloDeCenoura e BoloDeChocolate. Tenho também no projeto a classe MaquinaDeBolos, que, basicamente, chama o bolo usando clone.
Até aí tudo bem. A classe Bolo está assim:



public class Bolo implements Cloneable {
    
    /**
     * Metodo de clonar um objeto
     * @return objeto clonado
     */
    public Object clone() {
        try {
            Bolo copia = (Bolo)super.clone();
            
            return copia;
        } catch (Exception e) {
            System.out.println("Opz!");
            e.printStackTrace();
            return null;
        }
    }
    
    /**
     * Metodo que retorna a descricao de um bolo
     * @retrun descricao
     */
    public String getDescricao() {
        return "";
    }
}

O método getDescricao() retorna o nome do bolo e é sobrescrito pelas duas subclasses. É aqui que tenho uma dúvida (muito boba, por sinal): muitas pessoas (inclusive aqui do guj) são a favor de usar composição ao invés de herança. Eu até li que um dos usuários (Paulo Silveira) falou que prefere usar interfaces ao invés de classes abstratas.
Eu pensei em colocar a classe Bolo como abstrata e deixar o método getDescricao() abstrato (do jeito que está é muito ruim). Porém, quero evitar classes abstratas (dicas do GUJ). Então, se eu fosse ter que tirar a implementação do getDescricao(), eu teria que transformar bolo em interface e escrever o método clone em cada uma das subclasses. Porém isso traz uma desvantagem que é a não-reutilização de código (não digo desvantagem, mas é uma coisa estranha para mim), ou seja, a cada novo bolo, eu teria que praticamente reescrever uma classe inteira (nesse caso são dois métodos, mas se fossem tipo uns 10 ficaria complicado, não?).
Existe um jeito mais fácil de fazer isso? Eu não concordo muito com essa idéa de não usar classes abstratas porque nesse caso, eu acredito que ajuda. Eu não poderia tirar a herança desse caso porque na classe MaquinaDeBolos está usando polimorfismo. A composição, se eu fosse utilizar, quebraria o polimorfismo?

Desculpem, minha dúvida é realmente muito besta, mas isso está me incomodando a alguns dias.

Mas que confusão. Antes de dar ouvidos ao que o pessoal fala vc precisa ter as suas bases de OO bem assentes.
Quando as pessoas dizem que preferem X a Y elas estão invocando trade-offs (escolhas exclusivas) baseadas nas suas opiniões, experiência e conhecimento. Vc precisa conhecer OO para poder fazer essas escolhas por si mesmo e depois comparar com as que os outros fazem para saber se as justificativas que vc deu se aplicam, ou não.

Não ha nenhum mal em a classe ser abstrata. Sobretudo quando existem métodos que têm a mesma implementação para todas as subclasses. Isso tem até um nome : padrão Template.
Então, entenda primeiro que não ha nenhum mal na abordagem de usar classes abstratas. Contudo ha certas propriedades e caracteristicas que estão disponiveis com classes abstratas que não estão com interfaces e vice-versa. E são essas propriedades que marcam a diferença de qual aproximação usar.
A interface permite que qualquer classe a implemente. A interface designa comportamentos esperados pelos objetos. Elas não designam atributos ou implementações padrão (template). Uma hierarquia de interfaces significa que podem haver objetos de classes diferentes de se comportam da mesma forma.
Uma classe abstrata força-o a gastar a única herança que a sua classe final pode ter. Isso pode ser ruim. Contudo vc usa classes abstratas quando quer forçar que o objeto seja dessa classe e de mais nenhuma outra. Ou seja, impedi-lo de “ser duas coisas diferentes ao mesmo tempo”.

A diferença entre as aproximação é conceptual e não prática. Na prática vc pode modelar tudo como interface e criar uma classe abstrata quando perceber que está repetindo codigo demais nas suas classes filhas (isso é o uso do padrão Template ou do Adapter)

Agora, no seu caso, o seu código tem um problema. Cadê a oportunidade do objeto copiar o seu estado ?
super.clone apenas faz um copia superficial (shallow copy). Pode ser necessário fazer mais que isso.

Por outro lado, se vc usa o padrão Prototype não ha herança envolvida. Bolo de cenoura e bolo de chocolate são dois bolos
Ou seja, são dois objetos da classe Bolo, só que com atributos diferentes. A Maquina de bolos irá copiar um bolo padrão (prototipo) e alterar algumas propriedades ( sabor, por exemplo) e isso cria um novo bolo.

Protótipo é uma outra opção a herança, portanto, não usa herança na sua construção.
Dê uma revisada nesse padrão

Clareou bastante as coisas Sergio.
Muito obrigado.
Aproveitando o tópico, você me recomenda algum livro bom de OO?

[quote=Andre Brito]Clareou bastante as coisas Sergio.
Muito obrigado.
Aproveitando o tópico, você me recomenda algum livro bom de OO?
[/quote]
“Sem tomar a palavra do Sergio, queria fazer uma colocação”

Seria fácil eu lhe indicar algum livro, e dificilmente eu lhe indicaria algum curso entretanto fiz o curso na Aspercom de UML 2.0 e UP e digo valeu apena, não estou fazendo propaganda do Rodrigo Yoshima mas realmente mostrou habilidade na condução do curso e explicar o complexo em algo esclarecido.

Uma coisa que eu costumo fazer para evitar utilizar herança de classes é dividir classes abstratas ou coisas que seriam superclasses em classes menores.

Por exemplo, você tem Bolo, BoloDeCenoura extends Bolo e BoloDeChocolate extends Bolo.
Ao invés disso eu criaria uma classe Bolo e uma interface Sabor. Cenoura implements Sabor, Chocolate implements Sabor e Bolo HAS-A Sabor. Elimina-se a herança de classes sem ter que copiar-e-colar um monte de métodos entre classes diferentes!

Ou seja, tentar eliminar a herança e usar composição no lugar. As partes abstratas ou que possam ser redefinidas de uma classe, eu coloco em uma outra classe. Somente quando realmente não houver saída, é que uso alguma classe abstrata ou crio subclasses.

Victor,
Não tinha pensado nessa do Sabor. Parece que fica muito mais coeso o diagrama :slight_smile:
Obrigado!

Abraço.

[quote=victorwss]Uma coisa que eu costumo fazer para evitar utilizar herança de classes é dividir classes abstratas ou coisas que seriam superclasses em classes menores.

Por exemplo, você tem Bolo, BoloDeCenoura extends Bolo e BoloDeChocolate extends Bolo.
Ao invés disso eu criaria uma classe Bolo e uma interface Sabor. Cenoura implements Sabor, Chocolate implements Sabor e Bolo HAS-A Sabor. Elimina-se a herança de classes sem ter que copiar-e-colar um monte de métodos entre classes diferentes!

Ou seja, tentar eliminar a herança e usar composição no lugar. As partes abstratas ou que possam ser redefinidas de uma classe, eu coloco em uma outra classe. Somente quando realmente não houver saída, é que uso alguma classe abstrata ou crio subclasses.
[/quote]
:arrow: Então usária polimorfismo que caracterizaria sabor() , sobre a classe abstratas essas iriam realizar interface sabor e ter uma sub-classe especializada a ter sabor() mas o que não atender a assinatura da ação da interface você poderia colocar por exemplo na classe abstrata coberturaDoBolo na classe abstrata e essa teria um efeito de sua subclasse generalizada, coberturaDeBrigadeiro e colocando outras features.

[quote=Marcio Duran][quote=victorwss]Uma coisa que eu costumo fazer para evitar utilizar herança de classes é dividir classes abstratas ou coisas que seriam superclasses em classes menores.

Por exemplo, você tem Bolo, BoloDeCenoura extends Bolo e BoloDeChocolate extends Bolo.
Ao invés disso eu criaria uma classe Bolo e uma interface Sabor. Cenoura implements Sabor, Chocolate implements Sabor e Bolo HAS-A Sabor. Elimina-se a herança de classes sem ter que copiar-e-colar um monte de métodos entre classes diferentes!

Ou seja, tentar eliminar a herança e usar composição no lugar. As partes abstratas ou que possam ser redefinidas de uma classe, eu coloco em uma outra classe. Somente quando realmente não houver saída, é que uso alguma classe abstrata ou crio subclasses.
[/quote]
:arrow: Então usária polimorfismo que caracterizaria sabor() , sobre a classe abstratas essas iriam realizar interface sabor e ter uma sub-classe especializada a ter sabor() mas o que não atender a assinatura da ação da interface você poderia colocar por exemplo na classe abstrata coberturaDoBolo na classe abstrata e essa teria um efeito de sua subclasse generalizada, coberturaDeBrigadeiro e colocando outras features.[/quote]

Bem, foi difícil entender o que você quis dizer (como sempre), mas eu me esforcei. :slight_smile:

Basicamente, a minha idéia é eliminar o uso de herança de classes. Se a herança de classes for eliminada, não faz sentido haver classes abstratas, concorda?
Tipo, colocaria uma classe concreta CoberturaDoBolo, e essa classe também teria um sabor (composição, HAS-A) e Brigadeiro implements Sabor.

Basicamente, o truque aqui é refatorar as partes polimórficas em classes a parte, de forma que tenhamos apenas classes nada-polimórficas (e possivelmente final) e interfaces completamente polimórficas e abstratas, sem meio-termos, tudo ou nada!

Que bom, estou ficando mais esclarecido :lol:

Você quer atribuir uma situação de polimorfismo para dizer que tem situação de composição, mas não existe composição se eu tenho classes abstratas ? ela não pode realizar interface e ter uma sub-classe generalizada ? essa não pode se ter instancia de outras classes sem participar da herança ou mesmo polimorfica a ela.[quote]
Tipo, colocaria uma classe concreta CoberturaDoBolo, e essa classe também teria um sabor (composição, HAS-A) e Brigadeiro implements Sabor.
[/quote]
Essa situação não poderia estar vinculada a uma classe abstrata que realize interface mas que receba o parametro sabor() e cobertura() mas não participa do polimorfismo, so tem mesmo subclasse sabordeBrigaderio e outras features.[quote]
Basicamente, o truque aqui é refatorar as partes polimórficas em classes a parte, de forma que tenhamos apenas classes nada-polimórficas (e possivelmente final) e interfaces completamente polimórficas e abstratas, sem meio-termos, tudo ou nada![/quote]
Isso não muda deacordo com o contexto(Estado/Comportamento) em particular o caso de uso mais adequado de como o objeto deve comunicar-se para melhor abrigar suas regras a determinada linguagem ou não ? em Ruby a intenção a regra é a mesma ?

[quote=Marcio Duran][quote=victorwss]
Marcio Dura
Bem, foi difícil entender o que você quis dizer (como sempre), mas eu me esforcei. :slight_smile:
[/quote]
Que bom, estou ficando mais esclarecido :lol:

Você quer atribuir uma situação de polimorfismo para dizer que tem situação de composição, mas não existe composição se eu tenho classes abstratas ? ela não pode realizar interface e ter uma sub-classe generalizada ? essa não pode se ter instancia de outras classes sem participar da herança ou mesmo polimorfica a ela.[quote]
Tipo, colocaria uma classe concreta CoberturaDoBolo, e essa classe também teria um sabor (composição, HAS-A) e Brigadeiro implements Sabor.
[/quote]
Essa situação não poderia estar vinculada a uma classe abstrata que realize interface mas que receba o parametro sabor() e combertura() mas não participa do polimorfismo, so tem mesmo subclasse sabordeBrigaderio e outras features.[quote]
Basicamente, o truque aqui é refatorar as partes polimórficas em classes a parte, de forma que tenhamos apenas classes nada-polimórficas (e possivelmente final) e interfaces completamente polimórficas e abstratas, sem meio-termos, tudo ou nada![/quote]
Isso não muda deacordo com o contexto(Estado/Comportamento) em particular o caso de uso mais adequado de como o objeto deve comunicar-se para melhor abrigar suas regras a determinada linguagem ou não ? em Ruby a intenção a regra é a mesma ?[/quote]

Nada te impede de usar classes abstratas e herança de classes, essas features estão no centro da linguagem java. Mas, o que ocorre é que a experiência mostrou que a herança cria mais problemas do que resolve. Embora ela permita reaproveitamento de código, ela o faz gerando um forte acoplamento. E acoplamento forte é ruim. Vi em um blog de um dos programadores ligados a criação do java que se ele o criasse hoje, não teria permitido nenhuma herança de classes (acho que foi o thingol que postou o link em algum tópico por aí).

Composição (bem como associação e agregação) são comportamentos HAS-A, enquanto herança é o IS-A. Basicamente, ao invés de você fazer a classe A herdar da classe B, você coloca um atributo do tipo B (e possivelmente final e não-nulo) dentro da classe A. Ou seja, troca o relacionamento de herança por um de composição (e se for o caso afrouxa para agregação ou associação).

Basicamente o polimorfismo está ligado a métodos abstratos e sobrescrita de métodos, sendo que em interfaces todos os métodos são abstratos. O que ocorre no caso é que, eliminando herança obviamente não há sobrescrita, mas o polimorfismo via interfaces continua existindo.

O refatoramento, obviamente pode não ser simples de realizar, principalmente se o código já foi escrito. Essa idéia é para ser aplicada na modelagem, sendo muito custosa e trabalhosa aplicá-la tardiamente.

EDIT: Quase esqueci. Quanto ao Ruby, não sei ao certo. Não manjo ruby. Ele usa os mixins, que de certa forma, é como injetar implementações de métodos em uma classe. Mas, desconheço detalhes disso.