Vengo da un background autodidatta e sto cercando di sostenere le mie debolezze in OOP, in particolare C # e design di classe. Ho letto Code Complete 2 e ho capito che non sto seguendo i principi di progettazione di buona classe. Attualmente ho una classe ReportHandler che genera un rapporto basato su un enumerato ReportType impostato quando il gestore viene istanziato.
Quindi ora sto rifattorizzando il codice e sto iniziando considerando i report e il modo in cui differiscono:
Tutti condividono le seguenti proprietà, che saranno tutte dello stesso tipo:
- ID rapporto (guida)
- Data di inizio (DateTime)
- Data di fine (DateTime)
- Risultati totali (int)
Devono anche recuperare tutti i dati da un database (attualmente lo stesso database, potrebbe cambiare in futuro) e analizzare i dati in un oggetto, ed essere in grado di convertire quell'oggetto in un JSON. L'oggetto dati e l'analisi saranno diversi per ogni rapporto.
Quindi mi chiedo quale sarebbe il miglior design per questo. Se avessi creato una classe astratta avrei potuto farlo in questo modo:
public abstract class Report
{
public abstract void GetData();
public abstract void ParseData();
public abstract string ToJson();
public Guid ReportId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int TotalResults { get; set; }
//Not sure if this is correct, I want to make sure we can't instantiate a derived class without these values.
protected Report(Guid reportId, DateTime startDate, DateTime endDate)
{
if (startDate > endDate)
throw new ArgumentException("Start date cannot be after end date.");
ReportId = reportId;
StartDate = startDate;
StartDate = endDate;
}
}
Credo che ciò aiuterebbe a mantenere le lezioni organizzate. Potrei creare qualcosa di simile a questo:
public class SpecificReport : Report
{
//The raw data type can change between reports.
private List<string> _rawData;
public SpecificReportObject ParsedReport { get; private set; }
public SpecificReport(Guid reportId, DateTime startDate, DateTime endDate)
: base(reportId, startDate, endDate)
{
}
public override void GetData()
{
var rawData = new List<string>();
//Connect to a database and assign the results to rawData
_rawData = rawData;
}
public override void ParseData()
{
var parsedReport = new SpecificReportObject();
foreach (var result in _rawData)
{
//Parse RawData into a specific report object
}
ParsedReport = parsedReport;
}
public override string ToJson()
{
return JsonConvert.SerializeObject(ParsedReport);
}
public class SpecificReportObject
{
//Specific specific properties and logic
}
}
Ora mi sembra più organizzato e logicamente sembra avere più senso. È possibile creare un'istanza di un report, chiamare GetData (), ParseData () e infine ToJson () per ottenere il report in un formato portatile.
Tuttavia mi chiedo se i report derivati debbano contenere questa funzionalità. Non mi piace che debba ricordare di chiamare GetData () e ParseData (), ma mettere queste funzioni nel costruttore sembra una cattiva idea. Non vorrei mettere qualcosa in un costruttore che può fallire, specialmente un'operazione potenzialmente lunga come interrogare un database.
Sarebbe meglio separare GetData dalla classe? Quindi potrei recuperare prima i dati e caricarli nella funzione ParseData come argomento