Sto scrivendo un programma che è simile a Migration Record Migration di Ruby , in cui ogni migrazione ha sia un "Su" e "Giù" in termini di creazione di una modifica al database, "Up" significa portare avanti il database in tempo e "Giù" invertire tale modifica.
Il mio programma ha due funzioni principali per raggiungere i suoi casi d'uso
- Ottieni un elenco di script dal filesystem
- Esegui le migrazioni appropriate sul database
Le migrazioni "Su" e "Giù" sono archiviate in file separati, ma corrispondono a uno schema di denominazione simile
- 2014000000000001_CreateTable_up.sql
- 2014000000000001_CreateTable_down.sql
Esiste una correlazione 1: 1 tra gli script su e giù. Avere solo un up senza lo script down corrispondente non è valido (e viceversa).
Ho due classi nel mio progetto per rappresentare questi file del filesystem.
public sealed class UpScript // (and DownScript, which looks identical)
{
public UpScript(long version, string name, string content)
{
Content = content;
Version = version;
Name = name;
}
public long Version { get; private set; }
public string Name { get; private set; }
public string Content { get; private set; }
}
Il motivo per cui non ho creato una singola classe chiamata Script
è che non volevo creare ambiguità sullo scopo di un'istanza Script
specifica. Le classi UpScript
e DownScript
non devono essere utilizzate come intercambiabili in quanto non conformi al Principio di sostituzione di Liskov.
Il problema che ho qui è che alcune parti del codice che hanno a che fare con entrambe le istanze UpScript
o DownScript
sembrano quasi identiche. Come posso ridurre la duplicazione senza perdere l'espressività di avere classi diverse?
Le mie attuali idee con preoccupazioni sono:
Classe base astratta
public abstract class Script
{
protected Script(long version, string name, content)
{
// code
}
// properties
}
public sealed UpScript : Script
{
public UpScript(long version, string name, string content)
: this(version, name, content)
{}
}
public sealed DownScript : Script
{
public DownScript(long version, string name, string content)
: this(version, name, content)
{}
}
Questo riduce il codice ridondante nelle due classi, tuttavia, il codice che crea e consuma è ancora molto duplicato.
Singola classe con Direction
proprietà
public enum Direction { Up, Down }
public sealed class Script
{
protected Script(Direction direction, long version, string name, string content)
{
// ...
}
public Direction Direction { get; private set; }
// ...
}
Questo codice riduce la duplicazione, tuttavia, aggiunge on on consumes per garantire che venga passato lo script corretto e crea ambiguità se ignori la proprietà Direction
.
Singola classe con due% proprietà% di%
public sealed class Script
{
protected Script(long version, string name, string upContent, string downContent)
{
// ...
}
public string UpContent { get; private set; }
public string DownContent { get; private set; }
}
Questo riduce la duplicazione e non è ambiguo, tuttavia, l'uso del programma è quello di eseguire lo script su, o eseguire gli script in basso. Con questo schema, il codice che raccoglie e crea queste istanze di Content
sta facendo il doppio del lavoro, non ritengo che non si tratti di ottimizzazione prematura, perché se l'utente vuole eseguire una migrazione up, non ha senso guardare giù gli script.
Con tutto ciò che ho detto, potrei considerare il problema sbagliato da risolvere completamente.
Se ulteriori informazioni potrebbero essere utili per formulare un suggerimento, puoi visualizzare ScriptMigrations su GitHub