Проектирование класса C# для приложения управления облачным хранилищем


Резюме

Я хочу поделиться шаблоном проектирования классов на языке C#, который я недавно сделал. Я знаю, что существует множество различных паттернов проектирования и нет единственного лучшего способа, но изучение различных паттернов проектирования помогает мне разрабатывать расширяемые и масштабируемые приложения.

Код примера: blob-console-app

TOC

  • Архитектура
  • Дизайн классов
  • Коды
    • Список экземпляров типов файлов
    • for вместо foreach
    • Инъекция зависимостей
    • Валидация загружаемого файла
    • Общий класс
    • Метаданные в словарь
    • Десериализация Json

Архитектура

  • Операторы размещают файлы, которые они хотят загрузить в Azure Blob Storage.
  • Консольное приложение .NET проверяет указанный каталог через определенный интервал времени и, если файлы действительны, загружает их в Azure Blob Storage.
  • После загрузки файлов приложение удаляет их из локальной файловой системы.
  • Приложение извлекает метаданные и прикрепляет блоб к облаку.
  • В настоящее время типами файлов являются logs и events, которые находятся в разных каталогах локальной файловой системы.

Дизайн классов

Я решил использовать интерфейс, абстракцию и подклассы, потому что:

  • В настоящее время типами файлов являются только Logs и Events, но система будет расширяться. Например, DeviceInfo и Requests будут добавлены в будущем.
  • Для будущих расширенных типов файлов лучше разделить базовые функции и функции с различными требованиями для каждого типа файлов, чтобы система повторно использовала функции базового класса в будущем.
  • Чтобы избежать изменения базового класса при добавлении новых типов файлов, интерфейсный класс и абстрактный класс определяют базовые свойства, а подклассы определяют свойства метаданных.

Коды

Перечислить экземпляр типа файла

  • В настоящее время система имеет только два типа файлов, Logs и Events. Если вы хотите добавить другой тип файла, единственное, что нужно сделать, это реализовать новый класс для этого типа файла и добавить новый экземпляр здесь.

Program.cs

List<IUploadData> uploadDataList = new ()
{
    new Logs(),
    new Events(),
};
Вход в полноэкранный режим Выйти из полноэкранного режима

for вместо foreach

  • for (int i = 0; i < uploadDataList.Count; ++i) обрабатывает по-разному в зависимости от экземпляра класса, добавленного в список.

  • Он использует for вместо foreach, потому что метод класса GetMetadata() получает метаданные и помещает их в собственное свойство

Program.cs

uploadDataList[i] = uploadDataList[i].GetMetadata();
Вход в полноэкранный режим Выход из полноэкранного режима

Инъекция зависимостей

  • Программа не использует инъекцию зависимостей, но присваивает значения свойствам. Это связано с тем, что значение меняется для разных найденных файлов и типов файлов. Например, BlobClient нуждается в имени файла и директории для инстанцирования для разных файлов и разных типов файлов. Он не может создать экземпляр в конструкторе.

Program.cs

List<IUploadData> uploadDataList = new()
{
    new Logs(),
    new Events(),
};

while (true)
{
    for (int i = 0; i < uploadDataList.Count; ++i)
    {
        uploadDataList[i].FileFullPaths = Directory.GetFiles(Path.Combine(localPath, uploadDataList[i].FolderName), "*", SearchOption.AllDirectories);

        foreach (string fileFullPath in uploadDataList[i].FileFullPaths)
        {
            uploadDataList[i].FileFullPath = fileFullPath;
            uploadDataList[i].FileName = Path.GetFileName(fileFullPath);
            uploadDataList[i].BlobClient = containerClient.GetBlobClient($"/{uploadDataList[i].FolderName}/{DateTime.Now.Year}/{DateTime.Now.Month}/{uploadDataList[i].FileName}");
Вход в полноэкранный режим Выход из полноэкранного режима

Проверка загружаемого файла

  • Если файл, найденный в каталоге, не является допустимым для загрузки, программа останавливает цикл for и переходит к следующему файлу.
  • Способ проверки файлов отличается в зависимости от типа файла. Поэтому JudgeValidFile() переопределяется в каждом классе типа файла.

Program.cs

uploadDataList[i].JudgeValidFile();
if (uploadDataList[i].IsValidFile is false) continue;
Вход в полноэкранный режим Выход из полноэкранного режима

Logs.cs

this.IsValidFile = (Path.GetExtension(this.FileName) == ".csv" && this.FileFullPaths.Contains(this.FileFullPath + ".json"));
Вход в полноэкранный режим Выход из полноэкранного режима

События.cs

this.IsValidFile = (Path.GetExtension(this.FileName) == ".json");
Войти в полноэкранный режим Выход из полноэкранного режима

Обобщенный класс

Я хотел использовать общий класс для UploadMetadataAsync(), потому что он должен получать свойства динамически в зависимости от подкласса. Я думал, что UploadMetadataAsync<uploadDataList[i].GetType()>() работает, но на самом деле нет. После некоторого расследования я отказался от использования класса Generic.

Класс Generic

public virtual async Task UploadMetadataAsync<T>() where T : IUploadData
{
    PropertyInfo[] propertyCollection = typeof(T).GetProperties();
    foreach (PropertyInfo property in propertyCollection)
    {
        blobMetadata[property.Name.ToString()] = property.GetValue(this).ToString();
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Вызывающая сторона

await uploadDataList[i].UploadMetadataAsync<uploadDataList[i].GetType()>().ConfigureAwait(true);
Вход в полноэкранный режим Выйти из полноэкранного режима

Метаданные в словарь

  • Я понял, что не могу использовать Generic class, и подумал, как вставить только свойства метаданных в словарь для загрузки. Что я сделал, так это посмотрел разницу свойств между Абстрактным классом и подклассом. Например, Абстрактный класс UploadData имеет только базовые свойства FolderName, IsValidFile, FileFullPaths, FileFullPath, FileName, BlobClient, но подкласс Logs имеет свойства метаданных BeginTime, EndTime, Temperature, Humidity, Location в дополнение к базовым свойствам.

UploadData.cs

this.blobMetadata.Clear();
PropertyInfo[] propertiesIUploadData = typeof(IUploadData).GetProperties();
IEnumerable<PropertyInfo> propertiesThis = this.GetType().GetRuntimeProperties();

foreach (PropertyInfo propertyThis in propertiesThis)
{
    int count = 0;
    foreach (PropertyInfo propertyIUploadData in propertiesIUploadData)
    {
        if (propertyThis.Name == propertyIUploadData.Name) ++count;
    }
    if (count == 0)
    {
        this.blobMetadata[propertyThis.Name.ToString()] = propertyThis.GetValue(this).ToString();
    }
}
await this.BlobClient.SetMetadataAsync(this.blobMetadata).ConfigureAwait(false);
Вход в полноэкранный режим Выход из полноэкранного режима

Десериализация Json

  • Десериализовать значение из json-файла очень элегантно, поскольку мне не нужно менять код JsonSerializer.Deserialize<Logs>(jsonString)!!! для любого свойства модели. Однако проблема была в том, что подкласс имеет и базовое свойство, и свойство метаданных, и при десериализации он устанавливает свойства метаданных в значение из json-файла, а базовые свойства в null.
  • Мне это не нравится, но я не придумал элегантного обходного пути, и то, что я реализовал, это сохранить значение в локальных переменных и затем установить их обратно после десериализации.

пример json-файла

{
  "BeginTime": "2022-03-07T12:21:55Z",
  "EndTime": "2022-03-07T14:01:36Z",
  "Temperature": 25,
  "Humidity": 63,
  "Location": "Tokyo"
}
Вход в полноэкранный режим Выход из полноэкранного режима

Свойство класса Logs

public string FolderName { get; }
public bool IsValidFile { get; set; }
public string[] FileFullPaths { get; set; }
public string FileFullPath { get; set; }
public string FileName { get; set; }
public BlobClient BlobClient { get; set; }
public DateTime BeginTime { get; set; }
public DateTime EndTime { get; set; }
public int Temperature { get; set; }
public int Humidity { get; set; }
public string Location { get; set; }
Вход в полноэкранный режим Выход из полноэкранного режима

Logs.cs

public override IUploadData GetMetadata()
{
    bool IsValidFile = this.IsValidFile;
    string[] FileFullPaths = this.FileFullPaths;
    string FileFullPath = this.FileFullPath;
    string FileName = this.FileName;
    BlobClient BlobClient = this.BlobClient;

    string jsonString = File.ReadAllText(this.FileFullPath + ".json");
    Logs log = JsonSerializer.Deserialize<Logs>(jsonString)!;

    log.IsValidFile = IsValidFile;
    log.FileFullPaths = FileFullPaths;
    log.FileFullPath = FileFullPath;
    log.FileName = FileName;
    log.BlobClient = BlobClient;

    return log;
}
Войти в полноэкранный режим Выход из полноэкранного режима

Оцените статью
Procodings.ru
Добавить комментарий