Quali alternative ci sono per usare un oggetto di un'interfaccia controvariante polimorficamente?

4

L'essenza di ciò che sto cercando di fare è ottenere un'istanza del servizio utente appropriato, quindi passare qualsiasi sottotipo di User con cui stiamo lavorando.

Modelli:

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Student : User
{
    public int Grade { get; set; }
}

public class Staff : User
{
    public DateTime HireDate { get; set; }
}

Servizi:

public interface IUserService<T> where T : User
{
    void Save(T user);
}

public class StudentService : IUserService<Student>
{
    public void Save(Student user)
    {
        // Student-specific code
    }
}

public class StaffService : IUserService<Staff>
{
    public void Save(Staff user)
    {
        // Staff-specific code
    }
}

Il mio primo tentativo è stato di rinnovare il servizio di matching e up-cast. Ho scoperto che non funzionava dal momento che violerebbe la sicurezza del tipo: ad esempio, il compilatore non sarebbe in grado di dire se stavo passando in seguito in un utente compatibile.

public void Process(User user)
{
    IUserService<User> service;

    if (user is Staff)
        service = (IUserService<User>)new StaffService(); // InvalidCastException
    else if (user is Student)
        service = (IUserService<User>)new StudentService(); // InvalidCastException
    else
        throw new ArgumentException("...");

    service.Save(user);
}

Capisco perché è il caso ora, ma non sono riuscito a trovare un'alternativa che permetta ancora:

  1. Applicazione di un contratto (qualsiasi servizio utente deve implementare questi metodi)
  2. Chiamare i metodi in modo generico (basta passare l'oggetto utente)
  3. Come evitare il codice duplicato

Quello che davvero non voglio fare è finire con qualcosa di simile:

if (user is Staff)
{
    var service = new StaffService();
    service.Save(user);
}
else if (user is Student)
{
    var service = new StudentService();
    service.Save(user);
}
// etc.

Non così male con due tipi di utenti e una chiamata al metodo, ma il codice effettivo è più complesso.

    
posta Eric Eskildsen 01.12.2016 - 21:45
fonte

1 risposta

3

Vuoi abbinare il sottotipo UserService al sottotipo User, che è abbastanza semplice da fare con lo stesso parametro di tipo che usi in IUserService.

public void Process<T>(T user) where T : User
{
    IUserService<T> service;

    if (user is Staff)
        service = new StaffService(); // No more cast
    else if (user is Student)
        service = new StudentService(); // No more cast
    else
        throw new ArgumentException("...");

    service.Save(user);
}

Questo ha ancora il problema che Process deve sapere quale classe di Servizi istanziare, cosa che possiamo aggirare leggermente in modo imbarazzante in questo modo:

public void Process<T, S>(T user) where T : User where S : IUserService<T>
{
    IUserService<T> service = new S(); // No more "if ... is ..." chain   
    service.Save(user);
}

Tuttavia ciò complicherà i siti di chiamata di Process

    
risposta data 02.12.2016 - 15:07
fonte

Leggi altre domande sui tag