Search code examples
c#.netinversion-of-controldependency-inversion

dependency inversion for data class(classes that define the structure of certain type)


So I have this data class:

    public class RecentProject
    {
    public string ProjectName { get; set; }

    public string ProjectPath { get; set; }

    public DateTime CreationDate { get; set; }

    public string OutputFolder { set; get; } = "";
    }

It just defines the properties of the recent project, and I wanted to apply dependency inversion so I extracted an interface from the class:

    public interface IRecentProject
   {
    DateTime CreationDate { get; set; }
    string OutputFolder { get; set; }
    string ProjectName { get; set; }
    string ProjectPath { get; set; }
    }

then I made an ioc container(inversion of control) and registered the class type as the interface:

Mvx.IoCProvider.RegisterType<IRecentProject, RecentProject>();

so anywhere in my app when I want to create a recent project I just use:

Mvx.IoCProvider.Resolve<IRecentProject>();

but after I did this I ran into some problems that would be hard to solve with the current setup so I thought that maybe this is not the correct way to apply dependency inversion in this class because none of the dependency inversion benefits would apply like:

  • unit testing: as I will not be unit testing a data class
  • The ability to change the class implementation: as any changes in the class will require a change in the interface to be able to use the new features added

So what should I do, I have searched a lot on this topic and could not find a clear answer,
please help and thanks in advance.


Solution

  • If RecentProject is a pure data class, that is it contains no logic, then indeed no need either for unit testing or for abstraction. And of course, IoC is out of scope here.

    Things look different when logic and polymorphism are involved. You may want, for example, to have two types of projects with each implementing name validation in its own (simplistic) way:

    public class RecentCSharpProject : IRecentProject
    {
        .
        .
        .
        public string ProjectName
        {
            get => this.projectName;
            set
            {
                if (!value.EndsWith("csproj"))
                {
                    throw (new Exception("This is not a C# project"));
                }
                this.projectName = value;
            }
        }
    }
    
    public class RecentFSharpProject : IRecentProject
    {
        .
        .
        .
        public string ProjectName
        {
            get => this.projectName;
            set
            {
                if (!value.EndsWith("fsproj"))
                {
                    throw (new Exception("This is not an F# project"));
                }
                this.projectName = value;
            }
        }
    }
    

    You may still choose to skip unit tests, plus IoC still irrelevant (services are to be injected, not data models).

    However, you will now be able to instantiate appropriate class while still "speaking" the interface language to the outside world:

    public IRecentProject AddProjectToFileMenu(string projectName, bool isFSharp)
    {
        IRecentProject project = (isFSharp ? new RecentFSharpProject() : new RecentCSharpProject());
        project.ProjectName = projectName; // Internally validate extension according to concrete class
        // TODO: add project to file-menu
    
        return (project);
    }