OOD: herança de Java e acesso a métodos filhos via conversão

8

Tenho várias classes Parente Child1... Child9implementado em Java. Parenté uma classe abstrata, contendo todas as variáveis ​​comuns das classes filho (muito, que é a principal razão pela qual eu criei Parentuma classe abstrata e não uma interface), alguns métodos abstratos e alguns implementados.

Algumas das classes filho têm métodos personalizados que são específicos para eles. Muitas vezes, me pego chamando um método filho usando downcasting:

Parent p = new Child1();
((Child1) p).child1SpecificMethod();

De alguma forma, tenho a sensação de que essa é uma prática ruim de OOD, mas não tenho certeza se esse é realmente o caso, respectivamente, de como melhorar o design.

- Editar - O que eu provavelmente deveria mudar de qualquer maneira é o fato de usar a classe Parent para organizar muitas (por enquanto) variáveis ​​comuns, tornando-as (ou um objeto de contêiner) membros das classes concretas.

jpmath
fonte
6
Se você sabe que o valor de p é do tipo Criança1, por que não fazer p do tipo Criança1, portanto, não há necessidade de converter?
Benni 10/10
2
Geralmente sim, isso é uma prática ruim. Se você se encontra geralmente descartando coisas, provavelmente já fez algo errado. No entanto, é realmente difícil dizer mais sem mais informações sobre sua situação.
Aviv Cohn
4
Por que você está declarando pcomo um Parentem primeiro lugar? A adesão a tipos gerais é uma boa prática para APIs e tipos de retorno, mas não dentro do mesmo escopo em que você aloca uma classe concreta.
Kilian Foth
Parece que você está herdando a herança do seu caso de uso. Se as crianças não podem usar a herança para tirar proveito de um pai comum, você está indo a necessidade de rever o seu design de classe
Kolossus
2
possível duplicata de Como evitar downcasting?
mosquito

Respostas:

11

Isso não é apenas má prática , é desnecessário e complicado.

Por que você usa herança em geral?

Ao usar a herança, você tem um conjunto comum de comportamentos, que deseja disponibilizar para muitas operadoras diferentes. Isso inclui herança de classe e também herança de interface . O herdeiro , por assim dizer, é muitas vezes uma especialização da classe da qual herdou; o que é verdade principalmente para a herança de classe.

Pense em um carro de classe e uma porsche de subclasse (o típico é um relacionamento). Você tem um comportamento geral, como ligar / desligar o motor , direção e assim por diante. Se você trata uma Porsche como um carro, está vinculado a esse aspecto de seu comportamento. Se você sabe que só quer uma porsche e lida com ela como uma porsche , é redundante instanciar uma porsche como um carro e obter um comportamento da porsche através da transmissão .

O polimorfismo faz sentido ao contrário:

Você tem uma porsche e precisa tratá-la do aspecto de um carro; por exemplo, dirigir

Desde que a sua Porsche aceite direção à esquerda , direção à direita , mudança para cima , mudança para baixo , etc., você poderá usar o polimorfismo / substituição um pelo outro.

É melhor instanciar seus objetos em sua forma especializada. Então você pode aproveitar ao máximo o polimorfismo e usá-lo somente quando precisar .

Dito isto: Parent p = new Child1();não faz sentido para mim.

Edit: Eu implementaria o Porsche diferente (via composição ), mas pelo bem do exemplo, é um carro.

Thomas Junk
fonte
4

Seu sentimento está certo ao considerá-lo uma má prática. Imagine seu código de exemplo um pouco diferente:

Parent p = createObject();
((Child1) p).child1SpecificMethod();

Como você sabe que o valor de p é realmente Child1? Você não faz isso e isso pode causar uma ClassCastException em tempo de execução. Se você precisar chamar child1SpecificMethod () no seu código, verifique se p é do tipo Child1. Se isso não for possível porque o Objeto p é passado para o seu código (por exemplo, como um parâmetro do método) como o tipo Parent, você pode considerar o uso de uma variante do Visitor-Pattern e executar child1SpecificMethod no handle-Method do objeto do visitante, que manipula Criança1.

Benni
fonte
"você precisa garantir que p seja do tipo Child1" - isso não é possível porque o objeto p é passado como parâmetro de método. O padrão do visitante é uma boa dica.
jpmath
4

Use a pesquisa de recursos. Não dê acesso às classes filho, considere-as implementações da classe Pai.

Em seguida, defina interfaces especificando alguma capacidade , recurso.

interface Child1Specific {
    void child1SpecificMethod();
}

Uso:

Parent parent = ...
Child1Specific specific = parent.lookup(Child1Specific.class);
if (specific1 != null) {
    specific1.child1SpecificMethod();
}

Esse mecanismo de descoberta é muito flexível. Usar delegação em vez de herança pode ser bastante gratificante. Observe que não é mais necessário ter aulas filhas.

Ou no java 8 (onde várias variações são possíveis e a interface também pode ser funcional):

Optional<Child1Specific> specific = parent.lookup(Child1Specific.class);
if (specific1.isPresent()) {
    specific1.get().child1SpecificMethod();
}

Faça na classe Parent uma pesquisa por algum recurso:

public class Parent {
    protected final Map<Class<?>, Object> capabilities = new HashMap<>();
    protected final <T> void registerCapability(Class<T> klass, T object);

    public <T> T lookup(Class<T> klass) {
        Object object = capabilities.get(klass);
        return object == null ? null : klass.cast(object);
    }

Ou no java 8:

    public <T> Optional<T> lookup(Class<T> klass) {
        Object object = capabilities.get(klass);
        return Optional.ofNullable(klass.cast(object));
    }

A classe filho:

class Child1 extend Parent implements Child1Specific {
    Child1() {
        registerCapability(Child1Specific.class, this);
    }
}

Ou mais dinâmico:

class Child1 extends Parent {
    private Child1Specific specific = new Child1Specific() {
        ... Parent.this ...
    };
    Child1() {
        registerCapability(Child1Specific.class, specific);
    }
}
Joop Eggen
fonte
-3

Adicione um método abstrato child1SpecificMethod () na classe Parent (você precisa marcar a classe como abstrata) e forneça sua implementação na respectiva classe filho.

Niraj
fonte