Sono stato colpevole di avere una relazione 1 a 1 tra le mie interfacce e le classi concrete quando si utilizza l'iniezione di dipendenza. Quando ho bisogno di aggiungere un metodo a un'interfaccia, finisco per rompere tutte le classi che implementano l'interfaccia.Iniezione di dipendenza con interfacce o classi
Questo è un semplice esempio, ma supponiamo di dover iniettare uno ILogger
in una delle mie classi.
public interface ILogger
{
void Info(string message);
}
public class Logger : ILogger
{
public void Info(string message) { }
}
Avere una relazione 1-a-1 come questo sembra un odore di codice. Dal momento che ho una sola implementazione, ci sono potenziali problemi se creo una classe e contrassegno il metodo Info
come virtuale per sovrascrivere i miei test invece di dover creare un'interfaccia solo per una singola classe?
public class Logger
{
public virtual void Info(string message)
{
// Log to file
}
}
Se mi serviva un'altra implementazione, posso sovrascrivere il metodo Info
:
public class SqlLogger : Logger
{
public override void Info(string message)
{
// Log to SQL
}
}
Se ciascuna di queste classi hanno proprietà o metodi specifici che potrebbero creare un'astrazione che perde, ho potuto estrarre su una base classe:
public class Logger
{
public virtual void Info(string message)
{
throw new NotImplementedException();
}
}
public class SqlLogger : Logger
{
public override void Info(string message) { }
}
public class FileLogger : Logger
{
public override void Info(string message) { }
}
il motivo per cui non ho contrassegnare la classe base come astratta perché se ho sempre voluto aggiungere un altro metodo, non mi rompere exis implementazioni Ad esempio, se il mio FileLogger
necessitava di un metodo Debug
, è possibile aggiornare la classe base Logger
senza interrompere lo SqlLogger
esistente.
public class Logger
{
public virtual void Info(string message)
{
throw new NotImplementedException();
}
public virtual void Debug(string message)
{
throw new NotImplementedException();
}
}
public class SqlLogger : Logger
{
public override void Info(string message) { }
}
public class FileLogger : Logger
{
public override void Info(string message) { }
public override void Debug(string message) { }
}
Ancora, questo è un semplice esempio, ma quando dovrei preferire un'interfaccia?
_Il motivo per cui non ho contrassegnato la classe base come astratto è perché se mai avessi voluto aggiungere un altro metodo_ Hm, una classe astratta può contenere un'implementazione. Puoi aggiungere il metodo Debug alla tua classe Logger astratta. –
Rompere le implementazioni esistenti è solo un problema se si sta scrivendo una libreria riutilizzabile. Tu sei? O stai semplicemente scrivendo una linea di applicazione aziendale? – Steven
Questa non è la portata della domanda, ma l'ereditarietà è sopravvalutata. Un 'SqlLogger' è solo un' Logger' concreto con una 'SqlLogPersistenceStrategy'. La composizione è molto meglio dell'eredità nella maggior parte dei casi. Anche per il tuo problema, per quanto riguarda l'ISP? 'ILogInfo',' ILogError', ecc. – plalx