Знакомимся с конечным автоматом!
Введение
Начнем сразу же с проблемы.
Предположим, вы сталкиваетесь с задачей управления файлом в вашем приложении. Вам необходимо открыть файл, прочитать/изменить его содержимое, а затем закрыть. Эта задача кажется довольно простой, но во время реализации и отладки вступают различные состояния файла: файл уже открыт, файл не существует, файл открыт только для чтения… В общем логика работы с файлом может стать довольно запутанной.
Давайте посмотрим на шаблонный код.
public class FileManager : IDisposable
{
bool _isReadOnly = false;
private FileStream _document;
private string _filePath;
private bool _isOpen;
private FileInfo _fileInfo;
public FileInfo Fileinfo
{
get
{
try
{
if (_document == null)
{
_fileInfo = Read();
if (_fileInfo == null)
{
return null;
}
}
return _fileInfo;
}
catch (Exception ex)
{
return null;
}
}
set
{
if (value == null)
{
return;
}
if (_document == null)
{
_fileInfo = Read();
if (_fileInfo == null)
{
throw new ArgumentException($"FileInfo is null.");
}
}
WriteFileInfo(value);
}
}
public FileManager(string filePath)
{
_filePath = filePath;
Open();
}
private void Open()
{
if (_isReadOnly)
{
_document = new FileStream(_filePath, FileMode.Open, FileAccess.Read);
_isOpen = true;
return;
}
_document = new FileStream(_filePath,
File.Exists(_filePath) ?
FileMode.OpenOrCreate :
FileMode.CreateNew,
FileAccess.ReadWrite);
_isOpen = true;
}
private FileInfo Read()
{
_fileInfo = new FileInfo(_filePath);
return _fileInfo;
}
private void WriteFileInfo(FileInfo fileInfo)
{
// Пример записи метаданных файла
// Здесь просто установим дату последнего доступа для примера
fileInfo.LastAccessTime = DateTime.Now;
}
private void Save()
{
// Пример сохранения изменений в файл
_document.Flush();
}
private void Close()
{
_document.Close();
_isOpen = false;
}
public void Dispose()
{
if (_document == null)
{
return;
}
Save();
if (_isOpen)
{
_document.Close();
}
}
}
Читается тяжело, как убедиться, что мы не пытаемся прочитать файл, который ещё не открыт, или не пытаемся записать в файл, открытый только для чтения? Как корректно обрабатывать ошибки при попытке чтения из несуществующего файла или при записи в файл без прав на запись? Как поддерживать код чистым и понятным, несмотря на все возможные переходы между состояниями файла?
Конечный автомат
Конечный автомат (Finite State Machine, FSM) — это модель поведения, состоящая из определённого числа состояний, переходов между этими состояниями и действий, которые выполняются в каждом состоянии или при переходе из одного состояния в другое. Основное назначение конечного автомата — управление потоком выполнения программы в зависимости от внутреннего состояния и внешних событий.
Наше новое решение
Использование конечного автомата позволяет нам чётко определить все возможные состояния, в которых может находиться файл (например, “Закрыт”, “Открыт”), а также допустимые переходы между этими состояниями и действия, которые необходимо выполнить при каждом переходе.
public interface IFileState
{
void Open(FileContext context);
void ChangeContents(FileContext context, string content);
void Close(FileContext context);
}
public class FileClosed : IFileState
{
public void Open(FileContext context)
{
Console.WriteLine("File is opened.");
context.State = new FileOpen();
}
public void ChangeContents(FileContext context, string content)
{
Console.WriteLine("Cannot change contents. File is not open.");
}
public void Close(FileContext context)
{
Console.WriteLine("File is already closed.");
}
}
public class FileOpen : IFileState
{
public void Open(FileContext context)
{
Console.WriteLine("File is already open.");
}
public void ChangeContents(FileContext context, string content)
{
Console.WriteLine("Changing file contents.");
File.WriteAllText(context.FilePath, content);
}
public void Close(FileContext context)
{
Console.WriteLine("File is closed.");
context.State = new FileClosed();
}
}
public class FileContext
{
public IFileState State { get; set; }
public string FilePath { get; set; }
public FileContext(string filePath)
{
State = new FileClosed();
FilePath = filePath;
}
public void Open()
{
State.Open(this);
}
public void ChangeContents(string content)
{
State.ChangeContents(this, content);
}
public void Close()
{
State.Close(this);
}
}
Теперь наша работа с файлом будет выглядеть примерно так:
FileContext fileContext = new FileContext("C:/example.txt");
fileContext.Open();
fileContext.ChangeContents("Hello, World!");
fileContext.Close();
Вывод
Мы познакомились с концепцией конечного автомата на конкретном примере управления файлом. Применение этого подхода позволило нам сделать код не только более читабельным и понятным, но и значительно повысить его безопасность. Ведь теперь каждое действие с файлом , которое мы хотим совершить, явно контролируется через определённые состояния, что минимизирует риск возникновения ошибок, связанных с неправильным управлением состояниями файла.
Надеюсь пост был для вас познавательным, а на сегодня все, до новых встреч!