如何在 C# 中使用强制结构和无行为对相关不变数据进行分组

Posted

技术标签:

【中文标题】如何在 C# 中使用强制结构和无行为对相关不变数据进行分组【英文标题】:How to Group Related Unchanging Data with Enforced Structure and No Behavior in C# 【发布时间】:2021-12-28 20:09:41 【问题描述】:

我正在用 C# 构建一个 winforms 应用程序,该应用程序与产品交互以读取和写入 EEPROM 寄存器。该产品有多个不同版本,每个版本都对相同范围的 EEPROM 寄存器有独特的用途。 GUI 必须了解每种产品类型的每个寄存器的功能。因此,我需要为每种产品类型存储一个唯一的字典,将 EEPROM 寄存器地址与有关该寄存器如何用于该给定产品类型的数据相关联,我将其称为RegisterDictionary。除了这个RegisterDictionary,还有一些其他产品特定数据SpecificData 以及产品之间的共享数据SharedData,我也必须存储这些数据。 RegisterDictionarySpecificDataSharedData 的值对于给定的产品类型永远不会改变,但将来可能会添加新的产品类型。

我想知道对所有这些数据(类或结构)进行分组的最佳做法是什么,并且我想知道如何在不同产品类型(抽象类或接口)的数据组之间实施一致的结构.我看到的三个解决方案如下:

// Option 1: create static classes for each product type
// Advantage: I don't have to instantiate an object to access data
// Disadvantage: I have no way to enforce the structure of a static class.
// Disadvantage: No inheritance means SharedData must be repeated in each class.
// Potential Disadvantage: I am declaring a class with only unchanging properties and no real
// methods. Is this isolation of data without behavior hostile to object oriented design philosophy?

public static class ProductTypeA

    public static readonly Dictionary<string, string> RegisterDictionary get;

    public static readonly int SpecificData get;

    public static readonly int SharedData get;


public static class ProductTypeB

    public static readonly Dictionary<string, string> RegisterDictionary get;

    public static readonly int SpecificData get;

    public static readonly int SharedData get;

///////////////////////////////////////////////////////////////////////////////////////////////
// Option 2: Create an abstract parent class to enforce consistent ProductTypeX class structure
// Advantage: ProductType derived classes have an enforced structure.
// Advantage: Inheritance from parent means SharedData can be shared by derived classes.
// Disadvantage: I do have to create a ProductTypeX instance to access this data
// Potential Disadvantage: I am declaring a class with only unchanging properties and no real
// methods. Is this isolation of data without behavior hostile to object oriented design philosophy?

public abstract class ProductType


    public abstract Dictionary<string, string> RegisterDictionary get;

    public abstract int SpecificData get;

    public int SharedData get; = 1; //non-abstract to share among derived classes


public class ProductTypeA : ProductType // Only one ProductTypeX class shown for brevity

    public override Dictionary<string, string> RegisterDictionary get;

    public override int SpecificData get;


//////////////////////////////////////////////////////////////////////////////////////////////
// Option 3: Create a struct that implements an interface to enforce consistent ProductTypeX struct structure
// Advantage: ProductTypeX structs that implement IProductType have an enforced structure.
// Advantage: Default implementation from IProductTpe means SharedData can be shared by structs
// that implement this interface
// Potential Advantage: Structs may be more suited to group data with no behavior?
// Disadvantage: I do have to create a ProductTypeX instance to access this data

public interface IProductType


    Dictionary<string, string> RegisterDictionary get;

    int SpecificData get;

    int SharedData // This is my default implementation of SharedData
    
         get => 1;
     


public struct ProductTypeA : IProductType // Only one ProductTypeX struct shown for brevity

    public Dictionary<string, string> RegisterDictionary get;

    public int SpecificData get;


上述实现中的任何一个都比其他实现更好吗?

为了澄清,我的主要困惑围绕以下几点:

对我来说,必须实例化一个类或结构以仅访问它并不完全有意义 独立于实例本身且仅依赖于实例类型的数据。 这就是我考虑使用静态类选项的原因。如果数据存储在静态类中 不必通过一个实例来访问它 1. 对我来说(稍微)更多的工作,并且 2. 似乎它会误导读者认为数据取决于实例而不是 比类型。

存储相关不变的数据是否一定是反面向对象的思想? 类或结构中的行为(方法)?我试图这样做的事实是否表明我 是否已经分离了应该耦合在自己的类中的数据和行为?

如果以上都不是,那么将不变的数据存储在类或结构中哪个更好?在 微软的结构文档,他们说“通常,你使用结构类型来设计 提供很少或不提供行为的以数据为中心的小型类型”。这几乎回答了这个问题, 但没有解决我的结构或类将完全不变的事实;回到 第一点,结构要求我创建一个实例来访问 数据,即使数据不依赖于它绑定到类型的实例。

【问题讨论】:

【参考方案1】:

在类或结构中存储没有行为(方法)的相关不变数据是否一定是反面向对象的思想?

使用静态类来保存常量数据很好,这是 C# 中存在此功能的原因之一。但是,任何访问以这种方式声明的产品类型的代码都不可重用。如果您有一个访问ProductTypeA.SpecificData 的显示方法,那么该方法永远不能用于显示不同类型的产品。

方法二和三基本相同。这绝对是一种更好的方法,因为您可以编写接受基本 ProductType 的代码,而不是将您的方法耦合到一种特定类型。我看到这种方法的主要问题是您还定义了只能用作单例的类。例如。你永远不会创建ProductTypeA 的两个实例。在面向对象编程中,类用于定义特定类型的所有对象的属性和行为。如果该类的每个实例都具有完全相同的属性值和完全相同的行为,那么就不需要类。

为什么不简单地创建一个ProductType 类,并创建该类的多个实例?

public class ProductType

    public IReadOnlyDictionary<string, string> RegisterDictionary  get; 

    public int SpecificData  get; 

    public int SharedData  get; 

    public ProductType(IReadOnlyDictionary<string, string> registerDictionary, int specificData, int sharedData = 1)
    
        RegisterDictionary = registerDictionary;
        SpecificData = specificData;
        SharedData = sharedData;
    

像这样实例化类还可以让您选择将配置存储在 json 文件中,如果您添加更多产品类型,则无需重新编译应用程序

【讨论】:

您好,感谢您的周到回复。我认为您正在解决我关于单例想法的问题的核心。我喜欢您的“只使用 ProductType 类”的想法,我唯一担心的是我需要从多个类中访问 ProductType 实例。我想我可以创建一个静态工厂类,将相关数据传递给构造函数,然后用公共静态属性包装创建的实例。你支持那个方法还是你会用另一种方法?另外,如果我不能使用 JSON 文件,你建议如何存储特定的 ProductType 数据来创建实例? 是的,工厂是允许其他类访问您的产品类型而无需关心数据来自何处的好方法。在一个大项目中,我建议您使用依赖注入来为每个服务提供工厂。在 WinForms 应用程序中,让依赖注入工作可能会很痛苦,因此工厂的静态实例就足够了。 您绝对可以在 WinForm 应用程序中包含一个 json 文件。如果由于其他原因您不能使用 json 文件,我认为这意味着您不能使用任何类型的基于文本的文件。代码内解决方案是创建和初始化名为 ProductTypeAProductTypeB 等的 ProductType 类的静态只读实例。

以上是关于如何在 C# 中使用强制结构和无行为对相关不变数据进行分组的主要内容,如果未能解决你的问题,请参考以下文章

DDD - 如何对跨聚合的集合强制执行不变量

使用 C# 将数据表转换为分层数据结构 (JSON)

C# 中修饰符的基础结构和行为

【MySQL】13|为啥表数据删掉一半,表文件大小不变?

对 C# 中的每个请求强制进行 Azure 重新身份验证

C# 联合结构编组