How to Group Related Unchanging Data with Enforced Structure and No Behavior in C#

Viewed 40

I am building a winforms application in C# that interfaces with a product to read and write EEPROM registers. There are multiple different versions of this product, which each have unique uses for the same range of EEPROM registers. The GUI must have knowledge of the functions of each register for each product type. Because of this I need to store a unique Dictionary for each product type that relates the EEPROM register address with data about how that register is used for that given product type, I'll call that the RegisterDictionary. In addition to this RegisterDictionary there is some other product specific data SpecificData as well as shared data between products SharedData, that I have to store as well. The values of RegisterDictionary, SpecificData, SharedData will never change for a given product type, though new product types may be added in the future.

I want to know what the best practice is for grouping all of this data (either class or struct) and I want to know how I can enforce a consistent structure among the data groups for different product types (abstract class or interface). The three solutions I see are as follows:

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

Are any of the above implementations better than the others?

To clarify, my main points of confusion revolve around the following:

  • It doesn't quite make sense to me to have to instantiate a class or a struct to only access data that is independent of the instance itself and only dependent on the type of the instance. This is why I am considering the static class option. If the data is stored in a static class I don't have to access it through an instance which 1. would be (slightly) more work for me and 2. seems like it would mislead a reader into thinking the data depends on the instance rather than the type.

  • Is it necessarily anti-object oriented thinking to store related unchanging data with no behavior (methods) in a class or struct? Does the fact that I am trying to do this indicate I have already separated data and behaviors that should be coupled in their own class?

  • If no to the above then which is better to store unchanging data a class or struct? In Microsoft's documentation for structs, they say "Typically, you use structure types to design small data-centric types that provide little or no behavior". This almost answers the question, but doesnt address the fact that my struct or class would be completely constant; going back to the first point, the struct requires me to create an instance to access the data even though the data is not dependent on the instance it is bound to the type.

1 Answers

Is it necessarily anti-object oriented thinking to store related unchanging data with no behavior (methods) in a class or struct?

Using static classes to hold constant data is fine, it's one of the reasons this feature exists in C#. However, any code that accesses a product type declared this way is not reusable. If you have a display method which accessses ProductTypeA.SpecificData, then that method can never be used to display a product of a different type.

Approaches number two and three are basically identical. This is definitely a better approach, because you can write code which accepts the base ProductType, rather than coupling your methods to one specific type. The main problem I see with this approach is that you are also defining classes which can only be used as singletons. E.g. you would never create two instances of ProductTypeA. In Object-Oriented Programming, a class is used to defined the properties and behaviour of all objects of a specific type. If every instance of that class has the exact same property values, and the exact same behaviour, then there isn't any need for a class.

Why not simply make a ProductType class, and create multiple instances of that class?

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

Instantiating the class like this also gives you the option of storing the configurations in a json file, and you wouldn't have to recompile the application if you add more product types

Related