Код для экспорта объектов в различные форматы при составлении отчетов о ходе выполнения, что делает его более расширяемым

#c# #.net #refactoring #solid-principles

#c# #.net #рефакторинг #solid-принципы

Вопрос:

Описание

Приложение WinForms имеет функцию для экспорта объектов следующего типа в различные форматы:

 class Item
{
    public int id { get; set; }
    public string description { get; set; }
}
  

При нажатии кнопки в окне отображается SaveFileDialog , и в настоящее время он предоставляет возможность сохранить данные в формате .txt, .csv или .xlsx. Поскольку иногда существуют сотни или тысячи объектов, и пользовательский интерфейс не должен зависать, для выполнения этой операции используется Task . Эта реализация работает, но может быть улучшена.

Код

 public partial class ExportWindow : Form
{
    // objects to be exported
    List<Item> items;

    // event handler for the "Export" button click
    private async void exportButton_click(object sender, System.EventArgs e)
    {
        SaveFileDialog exportDialog = new SaveFileDialog();
        exportDialog.Filter = "Text File (*.txt)|*.txt|Comma-separated values file (*.csv)|*.csv|Excel spreadsheet (*.xlsx)|*.xlsx";
        exportDialog.CheckPathExists = true;
        DialogResult result = exportDialog.ShowDialog();
        if (result == DialogResult.OK)
        {
            var ext = System.IO.Path.GetExtension(saveExportFileDlg.FileName);

            try
            { 
                // update status bar
                // (it is a custom control)
                statusBar.text("Exporting");

                // now export it
                await Task.Run(() =>
                {
                    switch (ext.ToLower())
                    {
                        case ".txt":
                            saveAsTxt(exportDialog.FileName);
                            break;

                        case ".csv":
                            saveAsCsv(exportDialog.FileName);
                            break;
                    
                        case ".xlsx":
                            saveAsExcel(exportDialog.FileName);
                            break;

                        default:
                            // shouldn't happen
                            throw new Exception("Specified export format not supported.");
                    }
                });
            }
            catch (System.IO.IOException ex)
            {
                 statusBar.text("Export failed");
                 logger.logError("Export failed"   ex.Message   "n"   ex.StackTrace);

                 return;
            }
        }
    }

    private delegate void updateProgressDelegate(int percentage);

    public void updateProgress(int percentage)
    {
        if (statusBar.InvokeRequired)
        {
            var d = updateProgressDelegate(updateProgress);
            statusBar.Invoke(d, percentage);
        }
        else
        {
            _updateProgress(percentage);
        }
    }

    private void saveAsTxt(string filename)
    {
        IProgress<int> progress = new Progress<int>(updateProgress);
        
        // save the text file, while reporting progress....
    }

    private void saveAsCsv(string filename)
    {
        IProgress<int> progress = new Progress<int>(updateProgress);
        
        using (StreamWriter writer = StreamWriter(filename))
        {
            // write the headers and the data, while reporting progres...
        }
    }

    private void saveAsExcel(string filename)
    {
        IProgress<int> progress = Progress<int>(updateProgress);

        // EPPlus magic to write the data, while reporting progress...
    }
}
  

Вопросы

Как это можно переработать, чтобы сделать его более расширяемым? То есть, если бы я хотел добавить поддержку для большего количества типов файлов, упростить и ускорить изменение. Инструкция switch может стать очень длинной. По сути, как соблюсти принцип Open / Closed?

Ответ №1:

Создание класса для каждого из расширений могло бы быть способом. Вместо простого перебора по какому-либо списку или с помощью отражения, поэтому, если вы захотите добавить поддержку нового расширения, вам придется просто создать новый класс, а не трогать ExportWindow