Is isstance è un buon modo per identificare il tipo di un oggetto?

1

Ho un sacco di classi che ereditano da Violation . Queste sottoclassi violano il modello a regole diverse: UsedTimeslot , TeamConstraint , ecc ...

Devo verificare quale tipo di violazione è avvenuta per rispondere ad essa:

if team_constraint_violation_ocurred:
    corresponding_action()

Tuttavia, l'unico meccanismo che ho di controllare quale tipo di violazione si è verificato è il controllo del tipo di classe:

if isinstance(violation, TeamConstraintViolation):
        corresponding_action()

Questo è un modo corretto di identificare il tipo di un oggetto? O dovrei introdurre un attributo type , come enum ? O dovrei usare un design diverso del tutto?

    
posta dabadaba 23.06.2017 - 13:43
fonte

3 risposte

8

Se hai davvero bisogno di controllare il tipo di oggetti, certo, usa isinstance - è per questo che è stato inventato.

Tuttavia, se il requisito reale è fare qualcosa di diverso a seconda del sottotipo di un oggetto , di solito è un'idea migliore implementare questo codice come metodo in ogni sottoclasse e chiama semplicemente quel metodo sull'oggetto di tipo dinamico. In questo modo, l'invio dinamico si prende cura dei condizionali e non è necessario scriverli a mano. Ad esempio, puoi definire un metodo astratto respond() in Violation e poi fare in modo che ogni specifico tipo di Violation lo sovrascriva per eseguire una risposta specifica per tipo.

Può capitare che la classe polimorfica sia manifestamente il posto sbagliato in cui inserire quel codice. In tal caso, puoi compromettere e scrivere un metodo inviato dinamicamente che fa solo una parte del lavoro. Ad esempio, potrebbe generare una descrizione dettagliata dell'errore e consegnarlo al chiamante per la spedizione tramite il meccanismo di registrazione. Oppure potrebbe decidere quale linea di condotta è appropriata (ad esempio SyntaxError: fail, LexicalError: fail, TimeoutError: retry) e segnalare al chiamante cosa fare. Nel caso estremo, Violation potrebbe avere almeno un metodo getType() dinamico, in modo che tu possa cambiare il suo valore di ritorno, piuttosto che dover scrivere isinstance N volte.

    
risposta data 23.06.2017 - 13:46
fonte
1

Ciò di cui hai bisogno non è sapere quale tipo di violazione viene lanciata, ma piuttosto quale azione da invocare. Ci sono vari modi per farlo. @KilianFoth ha suggerito un modo molto ragionevole per farlo. Tuttavia, se non vuoi rendere ExecuteAction un metodo della classe Violation, puoi utilizzare un pattern Visitor, che dovrebbe essere simile a questo:

    public interface IVisitor
{
    void VisitViolationA(ViolationA a);
    void VisitViolationB(ViolationB b);
    void VisitViolationC(ViolationC c);

}

public abstract class Violation
{
    public abstract void Accept(IVisitor visitor);
}

public class ViolationA : Violation
{
    public override void Accept(IVisitor visitor)
    {
        visitor.VisitViolationA(this);
    }

}

public class ViolationB : Violation
{
    public override void Accept(IVisitor visitor)
    {
        visitor.VisitViolationB(this);
    }

}

public class ViolationC : Violation
{
    public override void Accept(IVisitor visitor)
    {
        visitor.VisitViolationC(this);
    }

}

public class ActionForViolation : IVisitor
{
    public void VisitViolationA(ViolationA a)
    {
        //Corresponding action for ViolationA
    }

    public void VisitViolationB(ViolationB b)
    {
        //Corresponding action for ViolationB
    }

    public void VisitViolationC(ViolationC c)
    {
        //Corresponding action for ViolationC
    }
}


public class SomeOtherActionForViolation : IVisitor
{
    public void VisitViolationA(ViolationA a)
    {
        //Some other corresponding action for ViolationA
    }

    public void VisitViolationB(ViolationB b)
    {
        //Some other corresponding action for ViolationB
    }

    public void VisitViolationC(ViolationC c)
    {
        //Some other corresponding action for ViolationC
    }
}


public class Client
{
    public void MethodWithViolations()
    {
        ViolationA violationA = new ViolationA();
        ActionForViolation afv = new ActionForViolation();
        violationA.Accept(afv);
    }

    public void SomeOtherMethodWithViolations()
    {
        ViolationA violationA = new ViolationA();
        SomeOtherActionForViolation soafv = new SomeOtherActionForViolation();
        violationA.Accept(soafv);
    }
}

Con questo approccio, hai disaccoppiato le azioni intraprese quando le violazioni si verificano dalle violazioni stesse e ti sei dato la possibilità di definire azioni diverse per le stesse violazioni, nel caso si verificasse la necessità. I derivati della classe Violazione sono responsabili solo di invocare il metodo IVisitor appropriato. L'ovvio svantaggio di questo è che l'interfaccia di IVisitor può essere molto grande nel caso ci siano molte violazioni, ma in tal caso, dovresti davvero riconsiderare la gestione delle violazioni nella classe Violazione e le sue derivate.

    
risposta data 23.06.2017 - 16:55
fonte
1

Puoi anche utilizzare type(violation).__name__ e se sai cosa stai cercando, puoi definire varie azioni:

# define actions and mapping
def actionOne():
    #some action

def actionTwo():
    #some action

def actionThree():
    #some action

violations = {
    TeamConstraintViolation: actionOne,
    UnauthorizedViolation: actionTwo,
    MaxQuotaViolation: actionTwo,
    NaughtyLanguageViolation: actionThree
}

# check violation and take action
violations[type(violation).__name__]()

Il vantaggio di questo approccio è che potresti avere un numero finito di azioni da intraprendere e molte violazioni potrebbero comportare la stessa azione.

    
risposta data 23.06.2017 - 18:10
fonte

Leggi altre domande sui tag