1

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.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
Ahmed Fawzy
  • 309
  • 2
  • 8
  • 1
    With every abstraction you must ask yourself what the added value is. There is no such thing as no-cost abstraction, so don't just do it because you can (or because you think 'everybody else is doing it'). So: why would you want to do it here? – JHBonarius Mar 20 '21 at 11:34
  • 1
    There isn't much point to interfacing a class that has no behavior. Is the class really just basic properties? – Crowcoder Mar 20 '21 at 11:42
  • @JHBonarius there is no particular reason I thought that this is the way to implement dependency inversion "di states that high level modules should not depend on low level modules; both should depend on abstractions", isn't it? – Ahmed Fawzy Mar 20 '21 at 13:26
  • @Crowcoder (although this might change in the future) but for the time being yes it is just basic properties – Ahmed Fawzy Mar 20 '21 at 13:28
  • 1
    Well, I don't really see the added value here. I can think of a reason for these kind of property-only classes (models?) to have interfaces: if you access multiple different of these models collectively through the same interface. I.e. you have `class MyType1 : IInt` and `class MyType2:IInt`, and you can use a common method that is `void MyMethod(IInt object)` for both. You could use normal inheritance for that simple though. But a class can have only one parent and multiple interfaces, so that could matter in a more complex situation. – JHBonarius Mar 20 '21 at 13:39
  • Please read also why [resolving instances this way everywhere in your app](https://freecontent.manning.com/the-service-locator-anti-pattern/) is a bad thing – Peter Bons Mar 20 '21 at 19:20

1 Answers1

1

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);
}
desertech
  • 911
  • 3
  • 7
  • 1
    Note that (AFAIK) it is a general consensus that getters shouldn't throw... (and you forgot to add `:IRecentProject ` to both classes) – JHBonarius Mar 20 '21 at 13:44
  • You meant setters I assume... well, not sure for the consensus part, but I agree that a SetProjectName method would have been more elegant. – desertech Mar 20 '21 at 13:52
  • No.. i was talking about getters. Well, it's more of an ongoing debate: the original idea of getters and setters is access to encapsulated fields. Thus they should not have any side-effects. If somebody tries to get a field value through a getter, they don't expect an exception. That should be handled before (before setting the field with an invalid value even) – JHBonarius Mar 20 '21 at 14:00
  • I apologize, but I am truly missing something.. the getters in my example are not throwing an exception, I am pretty sure of that.. – desertech Mar 20 '21 at 14:11