Esiste un modo per evitare il controllo dei tipi in questo scenario?

3

Ho una classe SuperClass con due sottoclassi SubClassA e SubClassB . Ho un metodo in una classe diversa che accetta un parametro SuperClass .

Il metodo dovrebbe fare cose diverse a seconda del tipo di oggetto che riceve. Per illustrare:

public void doStuff(SuperClass object){
    // if the object is of type SubClassA, do something.
    // if it's of type SubClassB, do something else.
}

Voglio evitare il controllo dei tipi (ad esempio instanceof ) perché non sembra un buon design OO.

Ma non riesco a capire come utilizzare il polimorfismo per risolvere elegantemente questo problema.

Come posso risolvere questo problema elegantemente?

    
posta Aviv Cohn 18.08.2014 - 19:35
fonte

4 risposte

9

In sostanza, doStuff dovrebbe appartenere a SuperClass invece dell'altra classe, quindi SubClassA e SubClassB hanno ciascuna le proprie implementazioni. A volte potresti essere in grado di tirare solo una piccola parte di doStuff in SuperClass . Potrebbe essere necessario rendere OtherClass un argomento di doStuff .

È difficile dire di più senza ulteriori dettagli. La linea di fondo è che devi provare a spostare le funzionalità in classi diverse fino a trovare un accordo che non sia così difficile.

    
risposta data 18.08.2014 - 20:12
fonte
4

Il problema qui è che o doStuff o SuperClass (o entrambi) è troppo generico per descrivere accuratamente cosa dovrebbe essere fatto in questa azione (metodo). L'unica ragione per cui dovresti controllare i tipi qui è che sono progettati male per cominciare e non offrono abbastanza contratti per intraprendere quell'azione.

Dato che non dici cosa si riferisce effettivamente a doStuff o SuperClass , diciamo che è log(LogEventBase logEvent) . Osservando questa firma del metodo, ci sono lacune di informazioni che devono essere riempite.

  • Cosa viene effettivamente scritto nel log da logEvent ?
  • Dove ci connettiamo?
  • Che cosa significa essere LogMessageBase ?

Qui ci sono una serie di altre potenziali domande senza risposta, ma potresti pensare che la soluzione sia basare il metodo sull'effettivo Type di LogEventBase :

public void log(LogMessageBase logEvent)
{
    switch (logEvent.GetType()):
        case BusinessLogicLogEvent:
            logToUserInterface(logEvent);
        case ErrorLogEvent:
            logToErrorLogs(logEvent);
        ...

Questo è cattivo !

Se è stato progettato correttamente, la risposta a tali domande sarebbe scritta nei Tipi / metodi direttamente in un numero di modi diversi. Un esempio di uno di questi modi è con un'interfaccia meglio definita:

public ILogMessage
{
    public GUID Id;
    public LogLevel Level;
    public string Message;
}

public void log(ILogMessage message)
{
    switch (message.LogLevel):
        case LogLevel.Error:
            logToFile(...

// or

public void logToConsole(ILogMessage message)
{
    Console.WriteLine(string.format("Message: {0}. Id: {1}",
        message.Message,
        message.Id);
}

Il punto è che l'interfaccia descrive un contratto per ciò che significa essere ILogMessage , non il Tipo, che potrebbe crescere rapidamente e diventare non più possibile.

Non esiste una risposta standard per il tuo particolare problema senza conoscere i tipi di dati coinvolti. In effetti, mettere LogLevel direttamente sull'interfaccia ILogMessage potrebbe essere completamente sbagliato per il tuo caso d'uso. Certamente non intendo implicare che dovresti specificare un'interfaccia con una proprietà TypeName e spostare la responsabilità lì. Quella particolare soluzione era solo lì per illustrare un esempio di risoluzione del problema (che LogMessageBase era troppo ampio di un argomento per questo metodo) all'origine quindi non è necessario attivare il tipo per iniziare.

    
risposta data 18.08.2014 - 20:00
fonte
2

Penso che tu possa utilizzare il modello di visitatore qui. La classe con il metodo doStuff dovrebbe implementare l'interfaccia Visitor . Questa interfaccia ha sovraccaricato i metodi doStuff(..) (o visit ) per tutti i sottotipi della SuperClass. La SuperClass ha un metodo accept(Visitor) metodo astratto e tutte le sottoclassi implementano questo da visitor.doStuff(this) , chiamando il metodo corretto sul visitatore.

Questo modello è una correzione per il legame statico che, ad es. Java ha: le chiamate di metodo sono solo dinamiche rispetto al tipo di oggetto su cui sono chiamate, non i tipi di parametri .

    
risposta data 19.08.2014 - 01:18
fonte
1

Sono d'accordo con la risposta di Karl Bielefeldt per spostare doStuff alla SuperClass con diverse implementazioni in SubA e SubB, o più specificamente mantenere doStuff dove ce l'hai e chiama solo metodi sull'oggetto SuperClass passato nelle posizioni in cui l'implementazione è diversa per SubA e SubB:

public void doStuff(SuperClass object){
    // do common stuff here

    object.specificStuff(<whatever parameters>);

    // do more common stuff here

    int bar = object.moreSpecificStuff(<some parameters>);

    // room for still more common stuff, perhabs based on bar's value 
}

Quindi la Superclasse avrebbe metodi astratti (o pure virtuali):

class SuperClass {

   ( ... )

   virtual void specificStuff( <whatever parameters> ) = 0;  // assuming C++

   virtual int moreSpecificStuff(<some parameters>) = 0;  // assuming C++ 

};

e SubA e SubB fornirebbero implementazioni per questi.

Questo è un modo per implementare il modello di strategia (dal Design Patterns di pattern , Go4).

    
risposta data 18.08.2014 - 22:33
fonte

Leggi altre domande sui tag