Создание потокобезопасного класса из абстрактного класса - PullRequest
0 голосов
/ 16 марта 2019

Вопрос о дизайне. Я знаю, что этот метод является потокобезопасным, но с точки зрения дизайна, есть ли лучший способ сделать это?

У меня есть абстрактный класс (не потокобезопасный):

public abstract class Class1
{
    protected someobject myobject;

    public Class1()
    {
       myobject = new someoject();
    }

    public virtual void proc1()
    {
      // do something with my object
    }

   public virtual void proc2()
    {
      // do something with my object
    }

  public virtual void proc3()
    {
      // do something with my object
    }
}

Теперь я хочу сделать потомком этого класса, который должен быть потокобезопасным, поэтому я делаю:

public class Class2: Class1
{
    private static readonly object obj = new object();


    public override void  proc1()
    {
      lock(obj)
      {
          base.proc1();
      }
    }

   public override void proc2()
   {
      lock(obj)
      {
          base.proc2();
      }
    }

   public override void proc3()
    {
      lock(obj)
      {
          base.proc3();
      }
    }
}

Я мог бы сделать базовый поток безопасным, но у меня есть некоторые другие классы, которые наследуются от той же самой базы и не нуждаются в безопасности потоков, поэтому я не хочу навязывать ей безопасность потоков. Есть проблемы с таким дизайном? Это немного утомительно, если на базе много публичных членов ....

Ответы [ 2 ]

2 голосов
/ 16 марта 2019

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

  1. Ответственность.Я смотрю на код Class2 и вижу, что он не делает ничего дополнительного по сравнению с базовым классом, кроме защиты состояния гонки от методов базового класса.Обычно мы стремимся навязать потокобезопасность в отношении конкретного состояния, чтобы обеспечить согласованность состояний в условиях одновременного доступа.Но в этом случае Class2 просто не знает, может ли защищаемое им поведение привести или не привести к условиям гонки.Что если Class1 будет изменен таким образом, когда безопасность потоков больше не требуется - у нас будут избыточные блокировки в классе Class2 (или мы удалим их, имеющие такую ​​косвенную связь с Class1).Что если Class1 будет расширен дополнительными методами или, что еще хуже, кто-то решит добавить дополнительную безопасность потока в Class1 другим объектом блокировки (в худшем случае у нас могут быть взаимоблокировки).Таким образом, каждый раз, когда мы делаем такие изменения в Class1, мы также должны проверять код Class2, чтобы убедиться, что ничего не сломано, иными словами, у нас есть тесная связь между этими классами только потому, что Class2 несет ответственность, которая не должнане принадлежат этому.
  2. LSP.Когда мы говорим об иерархии классов, мы обычно подразумеваем, что не должно быть разных требований использования контракта иерархии независимо от того, какой тип иерархии используется.Наличие в иерархии потоковобезопасных и не потоковобезопасных классов накладывает дополнительные ограничения на использование этой иерархии.В частности, потребитель должен знать, с каким типом экземпляра он имеет дело, при каких обстоятельствах, что потенциально исключает количество сценариев, в которых может использоваться LSP-совместимая иерархия. Например, потребитель не сможет использовать коллекциииз Class1 в общих сценариях, если точно не известно, что сценарии являются потокобезопасными.

В качестве общих рекомендаций:

  1. Я бы постарался избежать введения поведения в sub-классы, которые могут зависеть от контекста, в котором могут использоваться подклассы.Я бы постарался сделать всю иерархию согласованной: либо все классы в иерархии являются поточно-ориентированными, либо не все.
  2. Если некоторые классы в вашей иерархии требуют безопасности потоков, а некоторые - нет, этоможет быть показателем низкой сплоченности иерархии договора.Я бы попытался разложить базовый класс и подклассы на более мелкие части, что может подразумевать несколько контрактов и, возможно, иерархии.
  3. Если базовый класс или другие классы сохраняют состояние, которое потенциально может использоваться в различных контекстах параллелизмаи все еще трудно достичь однородной иерархии с точки зрения безопасности потоков, я бы рассмотрел вопрос о том, чтобы переместить логику синхронизации за пределы классов в иерархии и передать эту ответственность потребителю.
1 голос
/ 16 марта 2019

Если вы хотите работать с Class1 (или классом-потомком в этом отношении) потокобезопасным способом, вы должны использовать инкапсуляцию вместо наследования, как сказал Кевин Госс.Наследование не должно использоваться таким образом, потому что если Class1 имеет больше методов, которые не являются виртуальными (возможно, даже публичными), которые изменят внутреннее состояние объекта, вы не будете иметь никакого контроля над ними.Вы должны взять и инкапсулировать класс, который наследует Class1, а затем предоставить методы, которые будут вызываться как потокобезопасные методы.

Даже если вы управляете Class1 дизайном, подумать будет плохоПотоково-безопасного наследника (Class2) каждый раз, когда вы добавляете или меняете методы Class1

...