Sottotipizzazione senza aggiungere stato o comportamento - Cattiva pratica?

7

condizioni

Esistono molti sottotipi di Exception che non hanno nessuno stato o comportamento aggiunto. Ad esempio, ClosedByInterruptException codice .

Domanda

Perché la sottotipizzazione non aggiunge stato o comportamento "accettabile" nel caso di Exception ? Da quello che so, un interface dovrebbe essere usato quando si definisce un nuovo comportamento, e un class dovrebbe essere usato quando si implementa un comportamento o quando si aggiunge uno stato (che può causare un override del comportamento). A parte i tipi Exception , l'applicazione sarebbe accettabile?

Ti preghiamo di non utilizzare marcatori come esempio.

Elaborazione

Di seguito è riportato un disegno corroso:

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
}

class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
}

abstract class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

   //...assume methods that use String name are not overridable
}

Per coloro che non capiscono perché questo è disapprovato, vedi in fondo al post.

Per coloro che capiscono, questo rientrerebbe nella stessa categoria di ClosedByInterruptException : i sottotipi non definiscono nuovo stato o comportamento. Quindi perché esistono eccezioni come questa se il design è disapprovato?

Sebbene l'eccezione sia più descrittiva, è possibile includere una descrizione estesa tramite un messaggio String . Vedendo come viene generata una SocketException nel caso in cui un socket venga chiuso in anticipo, anziché in SocketClosedException o SocketNotConnectedException , perché una nuova API utilizza il progetto in questione?

Per coloro che si chiedono perché il design non è di buon occhio

Esaminando l'esempio Cat e Dog , entrambi eseguono esattamente lo stesso modo, poiché non sono stati aggiunti nuovi comportamenti, né i comportamenti sono stati sovrascritti. L'unica differenza verrebbe dal controllo del tipo tramite instanceof . Se utilizzi instanceof , stai tentando di eseguire un tipo di comportamento in base al tipo, ad esempio:

Animal animal = ...;

if(animal instanceof Dog) {
    //perform some behavior
} else if(animal instanceof Cat) {
    //perform some behavior
}

In un caso come questo, il comportamento dovrebbe essere contenuto nel tipo sostituendo un metodo e il comportamento dovrebbe essere attivato tramite il polimorfismo.

    
posta Vince Emigh 24.12.2016 - 02:18
fonte

4 risposte

16

Il meccanismo di gestione delle eccezioni di Java utilizza la gerarchia delle classi per modellare una tassonomia dei casi di eccezione.

  • Le frasi di cattura fanno effettivamente test di instanceof . In questo modo possono distinguere ClosedByInterruptException da casi più generici AsynchronousCloseException , ClosedChannelException , ecc.
  • Throwable#toString() include il nome della classe. Pertanto, il traceback dello stack mostra il tipo specifico di eccezione che si è verificato.
  • Una clausola throws distingue anche il tipo di eccezione dato dai suoi casi più generali (rappresentati come superclassi).

La linea guida di progettazione a cui si fa riferimento si applica nel caso comune di trattare un'istanza nel modo ordinario orientato agli oggetti - come qualcosa con stato, comportamento e identità. Invialo solo messaggi.

Ma a volte le persone usano la gerarchia delle classi come uno strumento di modellazione. Un altro esempio potrebbe essere le istanze lette da e scritte su un database. Il framework I / O del database può accoppiare le classi con le tabelle del database.

    
risposta data 24.12.2016 - 03:41
fonte
1

Se le eccezioni dovessero essere implementate nel modo che suggerisci, significherebbe che qualsiasi eccezione dovrebbe essere catturata ed eveluata a ogni livello. Ciò potrebbe sconfiggere gran parte del loro attuale scopo e appeal e alla fine non sarebbe molto meglio che restituire i codici di errore e valutarli ad ogni livello dello stack di chiamate, come eravamo soliti fare nell'era pre-OO. Quindi forse, da una prospettiva OO, è un kludge ma i benefici sono notevoli.

Per quanto riguarda l'esempio di gatto animale, questo genere di violazioni viene spesso fatto per coerenza e motivi pratici. Non è possibile avere un'implementazione completa per tutti gli animali concreti, ma la struttura del tuo programma richiede una classe generica e le estensioni, ad esempio quando utilizzi il modello di visitatore, ad esempio.

La tua osservazione è probabilmente la spiegazione del fatto che molte persone non possono essere disturbate a creare una classe di eccezioni personalizzata per qualsiasi tipo di problema di livello di dominio e che questa cattiva pratica è così comune.

    
risposta data 29.12.2016 - 12:29
fonte
0

Why is subtyping without adding state or behavior "acceptable" in the case of Exception? From what I know, an interface should be used when defining new behavior, and a class should be used when implementing behavior or when state is added.

I metodi predefiniti sono stati aggiunti a Java solo nella versione 8, il Exception la classe è stata nella sorgente dalla versione 1, quindi fino alla versione 8, non era possibile passare la funzionalità lungo la struttura a meno che non fosse stata utilizzata una classe.

L'utilizzo di un'interfaccia per un'eccezione contribuirebbe al codice copia-pasta. Dovresti ri-implementare la logica per ogni eccezione (che non è poi così diversa). Una classe ti consente di utilizzare un costruttore o due, e il gioco è fatto.

Estendendo da throwable, stai aggiungendo lo stato: la traccia dello stack mostra l'eccezione specifica che si è verificata e alcuni metodi restituiscono anche il nome dell'eccezione, ad esempio toString() .

Nell'articolo di Wikipedia Schema dell'interfaccia marcatore :

A major problem with marker interfaces is that an interface defines a contract for implementing classes, and that contract is inherited by all subclasses. This means that you cannot "unimplement" a marker. In the example given, if you create a subclass that you do not want to serialize (perhaps because it depends on transient state), you must resort to explicitly throwing NotSerializableException (per ObjectOutputStream docs)

Questa è la definizione di classe per java.lang.Throwable :

public class Throwable implements Serializable

Looking at the Cat and Dog example, both would perform the exact same way, since no new behaviors were added, nor were behaviors overwritten. The only difference would come from type checking via instanceof.

Il meccanismo try / catch di java dipende molto da instanceof . Ti consente di rilevare ampie eccezioni IOException o specifiche eccezioni ArrayIndexOutOfBounds . Permette l'esistenza di eccezioni specifiche e l'aggiunta di nuove funzionalità a un'eccezione, anche se raramente eseguita. Questo non è così facile "sovrascrivendo un metodo e avendo il comportamento innescato dal polimorfismo".

I modelli di progettazione vanno e vengono. Solo perché un pattern è usato da qualche parte, o qualcosa è implementato in un modo specifico, anche nella fonte ufficiale JDK, non significa che è la scelta migliore. Potrebbe essere stato in quel momento, o forse nessuno ha pensato di implementare detto modo.

    
risposta data 29.12.2016 - 02:08
fonte
0

Non penso che sia una cattiva pratica. Si chiama design:

  • Puoi modellare una gerarchia di tipi o un insieme di interfacce in modo da dare ai programmatori junior un punto di partenza.
  • Il software non è scritto in un giorno, quindi forse puoi avere sottotipi senza alcun comportamento aggiunto o stato da completare domani, nello stesso modo in cui puoi scrivere un sacco di interfacce per creare uno scheletro di un disegno e incarnarlo lungo giorni successivi.
  • Nel caso di eccezioni in Java, penso che sia davvero molto utile poiché è possibile rilevare eccezioni specifiche, altrimenti si utilizzerebbero le strutture dei casi per determinare quale eccezione è stata lanciata.

Solo per la cronaca: preferisco la composizione e le interfacce sull'ereditarietà e le classi astratte, ma hanno la loro posizione e i loro usi e la gestione dell'eccezione Java è un esempio di ereditarietà, anche se nessun ulteriore comportamento o stato è aggiunto da sottoclassi.

    
risposta data 29.12.2016 - 12:39
fonte

Leggi altre domande sui tag