Дизайн класса:Обертывание файла данных в класс с точки зрения потокобезопасности и тестируемости.
-
03-07-2019 - |
Вопрос
Я пишу приложение на C# (.net 3.5), и у меня есть вопрос о дизайне классов:
Я хотел бы создать класс, который обращается к файлу (чтение, запись) и предоставляет его содержимое пользователям (создателям) класса.Наиболее распространенной операцией над экземпляром будет получение определенного значения из файла.Фактические операции чтения и записи (io) являются дорогостоящими, поэтому я хотел бы хранить данные файла в памяти и позволить всем экземплярам получать доступ к этим данным.Класс находится в сборке, которая одновременно используется различными приложениями, поэтому, думаю, мне следует побеспокоиться о безопасности потоков.
Как мне это спроектировать с точки зрения потокобезопасности и возможности модульного тестирования (для модульных тестов необходимо использовать другие входные файлы, чем в рабочем коде)?Любая помощь очень ценится.
Решение
Во-первых, сделайте так, чтобы ваш класс реализовал соответствующий интерфейс.Таким образом, клиенты могут тестировать свое поведение вообще без необходимости использования реальных файлов.
Тестировать потокобезопасность сложно — я никогда не видел ничего действительно полезного в этом плане, хотя это не значит, что таких инструментов нет.
Для модульного тестирования вашего класса я бы посоветовал, если возможно, работать с общим потоком, а не просто с файлом.Затем вы можете вставлять различные тестовые файлы в свою тестовую сборку и обращаться к ним с помощью GetManifestResourceStream.В прошлом я делал это несколько раз, с большим успехом.
Другие советы
Использовать ЧитательПисательБлокировка, что, я думаю, соответствует описанию проблемы.
Ниже приводится быстрая и грязная реализация.Приобретение блокировок могло бы быть более разумным, например, несколько попыток перед спасением и т. д.Но вы поняли суть:
public class MyFooBarClass
{
private static ReaderWriterLock readerWriterLock = new ReaderWriterLock();
private static MemoryStream fileMemoryStream;
// other instance members here
public void MyFooBarClass()
{
if(fileMemoryStream != null)
{
// probably expensive file read here
}
// initialize instance members here
}
public byte[] ReadBytes()
{
try
{
try
{
readerWriterLock.AcquireReaderLock(1000);
//... read bytes here
return bytesRead;
}
finally
{
readerWriterLock.ReleaseReaderLock();
}
}
catch(System.ApplicationException ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
public void WriteBytes(bytes[] bytesToWrite)
{
try
{
try
{
readerWriterLock.AcquireWriterLock(1000);
//... write bytes here
}
finally
{
readerWriterLock.ReleaseWriterLock();
}
}
catch(System.ApplicationException ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
}
Что касается потокобезопасности:Безопасность потоков не является проблемой, если только несколько потоков в рамках одного приложения будет одновременно ссылаться на один и тот же экземпляр вашего класса.Если ваш класс не содержится на сервере вне процесса, несколько приложений не смогут одновременно обращаться к одному и тому же экземпляру.Таким образом, конфликты, которые вы, скорее всего, увидите, будут связаны с нарушениями совместного использования файлов, а не с проблемами потоков (другими словами, разные случаи класса, пытающегося читать и писать одно и то же файл).И да, вы должны разработать свой код для правильной работы с общим доступом к файлам.
Один из способов сделать модуль класса доступным для тестирования — предоставить классу поток в конструкторе, а не напрямую обращаться к файлу.Тогда модульный тест может предоставить, например, поток памяти вместо потока файлов.