Резюме
Я хочу поделиться шаблоном проектирования классов на языке 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;
}