Дизайн класса: объединение файла данных в класс с учетом потоковой безопасности и тестируемости

Я пишу приложение на C# (.net 3.5), и у меня есть вопрос о дизайне классов:

Я хотел бы создать класс, который обращается к файлу (чтение, запись) и предоставляет его содержимое пользователям (создателям экземпляров) класса. Наиболее распространенной операцией с экземпляром будет получение определенного значения из файла. Фактические операции чтения и записи (io) очень дороги, поэтому я хотел бы сохранить данные файла в памяти и позволить всем экземплярам обращаться к этим данным. Класс расположен в сборке, которая используется одновременно из различных приложений, поэтому я думаю, мне следует беспокоиться о безопасности потоков.

Как это спроектировать с учетом потоковой безопасности и возможности модульного тестирования (для модульных тестов должны использоваться другие входные файлы, чем в операционном коде)? Любая помощь приветствуется.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
163
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

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

Тестировать безопасность потоков сложно - я никогда не видел ничего действительно полезного в этом отношении, хотя нельзя сказать, что инструментов там нет.

Для модульного тестирования вашего класса я бы посоветовал, если возможно, работать с общим потоком, а не просто с файлом. Затем вы можете встраивать разные тестовые файлы в свою тестовую сборку и ссылаться на них с помощью GetManifestResourceStream. Я делал это несколько раз в прошлом, и с большим успехом.

Не могли бы вы предоставить более подробную информацию? Я до сих пор не знаю, как спроектировать класс. Как должен выглядеть интерфейс? Только GetFilePortionX / SetFilePortionY? Как я могу избежать повторной загрузки файловых данных при каждой установке. Потерпите меня, я начинающий и действительно зациклился на этом.

Mats 16.10.2008 16:01

Ах, я не заметил, что вам нужен доступ как для записи, так и для чтения. Это мучительно с точки зрения тестирования, хотя вы можете использовать MemoryStream для его поддержки. Что касается интерфейса, проработайте то, что нужно клиентам. Все ли Get / SetPortion? Если да, то это ваш интерфейс.

Jon Skeet 16.10.2008 18:13

Что касается безопасности потоков: безопасность потоков не является проблемой, если несколько потоков в одном приложении не будут одновременно ссылаться на один и тот же экземпляр вашего класса. Если ваш класс не содержится на внепроцессном сервере, у нескольких приложений нет возможности одновременно ссылаться на один и тот же экземпляр. Следовательно, конфликты, которые вы, вероятно, увидите, будут происходить из-за нарушений совместного использования файлов, а не из-за проблем с потоками (другими словами, разные экземпляры класса пытаются читать и записывать один и тот же файл). И да, вы должны разработать свой код таким образом, чтобы он соответствовал обмену файлами.

Один из способов сделать тестируемый модуль класса - предоставить классу поток в конструкторе вместо того, чтобы класс напрямую обращался к файлу. Затем модульный тест может предоставлять поток памяти, например, вместо предоставления потока файлов.

ad thread-security: означает ли это, что даже если я сделаю класс статическим, различные приложения не получат один и тот же статический объект? Возможность тестирования рекламы: я подумал о вашем подходе, но тогда мне нужно будет читать данные из файла каждый раз, когда создается экземпляр, чего я бы хотел избежать.

Mats 16.10.2008 16:26

Отдельные домены приложений (или приложения) никогда не используют общие экземпляры. Единственный способ поделиться экземпляром - с сервером вне процесса.

Craig Stuntz 16.10.2008 18:04

Используйте ReaderWriterLock, что, как мне кажется, соответствует описанию проблемы.

Ниже приводится быстрая и грязная реализация. Получение блокировок может быть более разумным, например, несколько попыток перед спасением и т. д. Но вы поняли суть:

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);
     }
   }
}

Другие вопросы по теме