I metodi concreti stanno scavalcando un odore di codice?

29

È vero che il superamento dei metodi concreti è un odore di codice? Perché penso che sia necessario sovrascrivere i metodi concreti:

public class A{
    public void a(){
    }
}

public class B extends A{
    @Override
    public void a(){
    }
}

può essere riscritto come

public interface A{
    public void a();
}

public class ConcreteA implements A{
    public void a();
}

public class B implements A{
    public void a(){
    }
}

e se B vuole riutilizzare a () in A può essere riscritto come:

public class B implements A{
    public ConcreteA concreteA;
    public void a(){
        concreteA.a();
    }
}

che non richiede ereditarietà per sovrascrivere il metodo, è vero?

    
posta ggrr 11.04.2016 - 04:08
fonte

5 risposte

34

No, non è un odore di codice.

  • Se una classe non è definitiva, consente di essere sottoclasse.
  • Se un metodo non è definitivo, può essere sovrascritto.

Si trova all'interno delle responsabilità di ogni classe di considerare attentamente se la sottoclasse è appropriata e quali metodi possono essere sovrascritti .

La classe può definire se stessa o qualsiasi metodo come finale, oppure può porre restrizioni (modificatori di visibilità, costruttori disponibili) su come e dove è sottoclasse.

Il solito caso per i metodi di sovrascrittura è una implementazione predefinita nella classe base che può essere personalizzata o ottimizzata nella sottoclasse (specialmente in Java 8 con l'avvento dei metodi predefiniti nelle interfacce).

class A {
    public String getDescription(Element e) {
        // return default description for element
    }
}

class B extends A {
    public String getDescription(Element e) {
        // return customized description for element
    }
}

Un'alternativa per il comportamento di override è il Pattern di strategia , in cui il comportamento è astratto come un'interfaccia e l'implementazione può essere impostata nella classe.

interface DescriptionProvider {
    String getDescription(Element e);
}

class A {
    private DescriptionProvider provider=new DefaultDescriptionProvider();

    public final String getDescription(Element e) {
       return provider.getDescription(e);
    }

    public final void setDescriptionProvider(@NotNull DescriptionProvider provider) {
        this.provider=provider;
    }
}

class B extends A {
    public B() {
        setDescriptionProvider(new CustomDescriptionProvider());
    }
}
    
risposta data 11.04.2016 - 10:49
fonte
24

Is it true that overriding concrete methods is a code smell?

Sì, in generale, il superamento dei metodi concreti è un odore di codice.

Poiché il metodo base ha un comportamento ad esso associato che gli sviluppatori di solito rispettano, cambiando ciò porterà a bug quando l'implementazione fa qualcosa di diverso. Peggio ancora, se cambiano il comportamento, l'implementazione corretta in precedenza potrebbe esacerbare il problema. E in generale, è piuttosto difficile rispettare il Principio di sostituzione di Liskov per metodi concreti non banali.

Ora, ci sono casi in cui questo può essere fatto ed è buono. I metodi semi-banali possono essere superati un po 'in sicurezza. I metodi di base che esistono solo come un tipo di comportamento "predefinito" che significa per essere sovrascritto hanno alcuni buoni usi.

Quindi, questo mi sembra un odore - a volte buono, di solito cattivo, dai un'occhiata per essere sicuro.

    
risposta data 11.04.2016 - 04:21
fonte
7

if you need to override concrete methods [...] it can be rewritten as

Se può essere riscritto in questo modo significa che hai il controllo su entrambe le classi. Ma poi sai se hai progettato la classe A per essere derivata in questo modo e se lo hai fatto, non è un odore di codice.

Se, d'altra parte, non hai il controllo sulla classe base, non puoi riscrivere in quel modo. Quindi o la classe è stata progettata per essere derivata in questo modo, nel qual caso basta andare avanti, non è un odore di codice, o non lo era, nel qual caso si hanno due opzioni: cercare un'altra soluzione del tutto, o andare avanti comunque , perché il trionfo di lavoro è purezza.

    
risposta data 11.04.2016 - 07:58
fonte
3

Innanzitutto, vorrei solo sottolineare che questa domanda è solo applicabile in alcuni sistemi di battitura. Ad esempio, in Digitazione strutturale e Duck typing systems - una classe semplicemente avendo un metodo con lo stesso nome & firma significherebbe che quella classe era tipo compatibile (almeno per quel particolare metodo, nel caso di Duck typing.

Questo è (ovviamente) molto diverso dal regno delle lingue tipicamente , come Java, C ++, ecc. Oltre alla natura di Strong typing , come usato sia in Java che in amp; C ++, così come C # e altri.

Suppongo che tu venga da uno sfondo Java, in cui i metodi devono essere esplicitamente contrassegnati come ultimi se il designer di contratti intende che non venga sottoposto a override. Tuttavia, in lingue come C #, l'opposto è l'impostazione predefinita. I metodi sono (senza alcuna parola chiave speciale di decorazione, " sealed " (versione di C # di final ), e devono essere dichiarati esplicitamente come virtual se sono permessi per essere sovrascritti.

Se un'API o una libreria sono state progettate in queste lingue con un metodo concreto che è overridable, allora deve (almeno in alcuni casi) fare senso per essere sovrascritto. Pertanto, non è un odore di codice per sovrascrivere un metodo concreto non sigillato / virtuale / non final . (Ciò presuppone naturalmente che i progettisti dell'API stiano seguendo le convenzioni e marcando come virtual (C #), o contrassegnando come final (Java) i metodi appropriati per ciò che stanno progettando.)

Vedi anche

risposta data 11.04.2016 - 08:14
fonte
2

Sì, è perché può portare a chiamare super anti-pattern. Può anche snaturare lo scopo iniziale del metodo, introdurre effetti collaterali (= > bug) e fare fallire i test. Useresti una classe in cui i test unitari sono rossi (faillare)? Non lo farò.

Andrò anche oltre dicendo che l'odore del codice iniziale è quando un metodo non è abstractfinal . Perché permette di alterare (sovrascrivendo) il comportamento di un'entità costruita (questo comportamento può essere perso o alterato). Con abstract e final l'intento non è ambiguo:

  • Astratto: tu devi fornire un comportamento
  • Finale: sei non autorizzato a modificare il comportamento

Il modo più sicuro per aggiungere un comportamento a una classe esistente è ciò che mostra il tuo ultimo esempio: utilizzando il pattern decoratore.

public interface A{
    void a();
}

public final class ConcreteA implements A{
    public void a() {
        ...
    }
}

public final class B implements A{
    private final ConcreteA concreteA;
    public void a(){
        //Additionnal behaviour
        concreteA.a();
    }
}
    
risposta data 11.04.2016 - 08:48
fonte

Leggi altre domande sui tag