Mi chiedevo se utilizzare (quasi) le classi derivate vuote per dare semantica aggiuntiva a una gerarchia di classi fosse una buona idea (o pratica)?
Poiché un esempio (non così breve) è sempre meglio di un discorso lungo, supponiamo di scrivere una gerarchia di classi per la generazione SQL. Vuoi rappresentare un comando di manipolazione dei dati (INSERT, UPDATE o DELETE), quindi hai due alternative:
Prima versione, con istruzione switch
enum CommandType {
Insert,
Update,
Delete
}
class SqlCommand {
String TableName { get; }
String SchemaName { get; }
CommandType CommandType { get; }
IEnumerable<string> Columns { get; }
// ... and so on
}
class SqlGenerator {
string GenerateSQLFor(SqlCommand command)
{
switch (command.CommandType)
{
case CommandType.Insert:
return generateInsertCommand(command);
case CommandType.Update:
return generateUpdateCommand(command);
case CommandType.Delete:
return generateDeleteCommand(command);
default:
throw new NotSupportedException();
}
}
}
Seconda versione con VisitorPattern (nota che la domanda NON riguarda l'utilizzo del pattern Visitor o l'aggiunta di nuove operazioni)
abstract class SqlCommand {
String TableName { get; }
String SchemaName { get; }
IEnumerable<string> Columns { get; }
// ... and so on
abstract void Accept(SqlCommandVisitor visitor);
}
class InsertCommand : SqlCommand
{
overrides void Accept(SqlCommandVisitor visitor)
{
visitor.VisitInsertCommand(this);
}
}
class DeleteCommand : SqlCommand
{
overrides void Accept(SqlCommandVisitor visitor)
{
visitor.VisitDeleteCommand(this);
}
}
class SqlCommandVisitor {
void InitStringBuffer() { ... }
string GetStringBuffer() { ... }
string GenerateSQLFor(SqlCommand command)
{
InitStringBuffer();
command.Accept(this);
return GetStringBuffer();
}
void Visit(SqlCommand command) { ... }
void VisitInsertCommand(InsertCommand command) { ... }
void VisitDeleteCommand(DeleteCommand command) { ... }
void VisitUpdateCommand(UpdateCommand command) { ... }
}
Con entrambi gli esempi otteniamo lo stesso risultato, ma:
- Nella prima versione, il mio codice sembra più SECCO, anche se il polimorfismo è generalmente preferibile rispetto a un'istruzione switch.
- Nella seconda versione, mi sento come se stavo inutilmente derivando SqlCommand solo perché il codice abbia più significato.
Un altro caso simile deriva una classe da una classe generica, solo per dare un significato addizionale alla raccolta, ad esempio:
class CustomerList : List<Customer> { }
Non mi chiedo se sia una buona idea. Ci sono delle insidie nel farlo?
Personalmente preferisco la seconda versione esattamente perché aggiunge significato al codice e mi aiuta quando leggo il mio codice più tardi ... Ma mi manca chiaramente l'esperienza per vedere gli svantaggi di questo modello.