0

I'm very new to DI, and must admit, am having a hard time understanding to what extent it should be used in an application.

Even in a basic project, you can have hundreds of classes that have constructors which take in one or more dependencies. Should all these types be registered by your DI container? or only the root application objects? and the dependencies filtered through?

I've been practising by creating a simple WPF MVVM application that creates Nodegraphs. Something along the lines of :

enter image description here

  • There is a main viewmodel
  • There can be multiple NodeGraphs (documents) being edited at one time (each in a tab)
  • Each document has its own UndoRedo stack
  • Each document has its own logger
  • Each document has a collection of Nodes

What I have come up with, is the following. And would like some feedback as to whether I am following the right patterns. Specially, I am trying to make sure that the ServiceProvider never leaves the App configuration file. Which means that I have to make use of factories.

  • Should the node types be part of DI?
  • Instead of AddSingleton factories, should I be creating classes instead? so that they're more descriptive?
  • In an MVVM project do both Models and ViewModels types get registered in the DI container?
    public partial class App : Application    {        private ServiceProvider _serviceProvider;        public App()        {            IServiceCollection services = new ServiceCollection();            services.AddTransient<ILogger, Logger>();            services.AddSingleton<Func<ILogger>>(x => () => x.GetRequiredService<ILogger>());            services.AddTransient<IUndoRedo, UndoRedo>();            services.AddSingleton<Func<IUndoRedo>>(x => () => x.GetRequiredService<IUndoRedo>());            services.AddSingleton<IDocumentFactory, DocumentFactory>();            services.AddSingleton<INodeFactory, NodeFactory>();            services.AddSingleton<MainWindow>();            services.AddSingleton<MainViewModel>();            _serviceProvider = services.BuildServiceProvider();        }        protected override void OnStartup(StartupEventArgs e)        {            base.OnStartup(e);            var window = _serviceProvider.GetRequiredService<MainWindow>();            var viewModel = _serviceProvider.GetRequiredService<MainViewModel>();            window.DataContext = viewModel;            window.Show();        }    }
    public enum NodeType    {        Add,        Subtract,        Multiply        // ...    }    public interface IUndoRedo { }    public class UndoRedo : IUndoRedo { }    public interface ILogger { }    public class Logger : ILogger { }    public interface IDocumentFactory    {        public IDocument CreateDocument(ILogger logger, IUndoRedo undoRedo, INodeFactory nodeFactory);    }    public class DocumentFactory : IDocumentFactory    {        public IDocument CreateDocument(ILogger logger, IUndoRedo undoRedo, INodeFactory nodeFactory)        {            return new Document(logger, undoRedo, nodeFactory);        }    }    public interface IDocument    {        public ILogger Logger { get; }        public IUndoRedo UndoRedo { get; }        public INodeFactory NodeFactory { get; }        public ObservableCollection<INode> Nodes { get; }        public void AddNode(NodeType nodeType);    }    public class Document : IDocument    {        public ILogger Logger { get; }        public IUndoRedo UndoRedo { get; }        public INodeFactory NodeFactory { get; }        public ObservableCollection<INode> Nodes { get; } = new();        public Document(ILogger logger, IUndoRedo undoRedo, INodeFactory nodeFactory)        {            Logger = logger;            UndoRedo = undoRedo;            NodeFactory = nodeFactory;        }        public void AddNode(NodeType nodeType)        {            Nodes.Add(NodeFactory.CreateNode(this, nodeType));        }    }    public interface INodeFactory    {        public INode CreateNode(IDocument document, NodeType nodeType);    }    public class NodeFactory : INodeFactory    {        public INode CreateNode(IDocument document, NodeType nodeType)        {            switch (nodeType)            {                case NodeType.Add: return new AddNode(document);                case NodeType.Subtract: return new SubtractNode(document);                case NodeType.Multiply: return new MultiplyNode(document);                default:                    throw new NotImplementedException(nodeType.ToString());            }        }    }    public interface INode    {        public IDocument Document { get; }    }    public abstract class Node : INode    {        public NodeType NodeType { get; }        public IDocument Document { get; }        public Node(IDocument document, NodeType nodeType)        {            Document = document;            NodeType = nodeType;        }    }    public class AddNode : Node    {        public AddNode(IDocument document)            : base(document, NodeType.Add) { }    }    public class SubtractNode : Node    {        public SubtractNode(IDocument document)            : base(document, NodeType.Subtract) { }    }    public class MultiplyNode : Node    {        public MultiplyNode(IDocument document)            : base(document, NodeType.Multiply) { }    }    public class MainViewModel    {        private readonly IDocumentFactory _documentFactory;        private readonly INodeFactory _nodeFactory;        private readonly Func<ILogger> _provideLogger;        private readonly Func<IUndoRedo> _provideUndoRedo;        public ObservableCollection<IDocument> Documents { get; } = new();        public ILogger Logger { get; }        public MainViewModel(IDocumentFactory documentFactory, INodeFactory nodeFactory, Func<ILogger> provideLogger, Func<IUndoRedo> provideUndoRedo)        {            _documentFactory = documentFactory;            _nodeFactory = nodeFactory;            _provideLogger = provideLogger;            _provideUndoRedo = provideUndoRedo;            Logger = _provideLogger(); // global logger            CreateDocument();            CreateDocument();        }        public void CreateDocument() // this would be a command        {            ILogger logger = _provideLogger(); // per document logger            IUndoRedo undoRedo = _provideUndoRedo();            IDocument document = _documentFactory.CreateDocument(logger, undoRedo, _nodeFactory);            Documents.Add(document);            document.AddNode(NodeType.Add);        }    }
askedJun 24, 2023 at 12:06
wforl's user avatar
11
  • 4
    Is there a specific problem you have with your current design? A good question for this community should focus on a single problem with enough information so we can agree on a single comprehensive answer. Also now that asking for general feedback results in opinion-based answers, which is not something the StackExchange network supports.CommentedJun 24, 2023 at 13:08
  • I'm guessing your problem is that because you decided to DI the Ilogger, which is standard practice, you now have to DI everything to get the loggers in. You don't. You can just inject it into your top level constructor and pass it down the chainCommentedJun 24, 2023 at 13:57
  • @Ewan, as mentioned, I want one global logger and one local logger per documentCommentedJun 24, 2023 at 15:43
  • @wforl what does DI specifically provide your loggers that you care about? Do you need something that the typical resource locator method doesn’t provide?CommentedJun 24, 2023 at 17:15
  • 1
    @wforl I would focus on unit testing each of your application's distinct, separate behaviours; (Note: the term "unit testing" is often misunderstood as treating each class as a unit, however the original definition of a unit is a unit of behaviour, and not a property of the code itself). Unit tests help identify "seams" in your code by forcing you to think about how to isolate each behaviour; Isolating behaviour highlights dependencies behind the seams (If you need to substitute something to make testing easier, then the thing being substituted is a dependency).CommentedJun 30, 2023 at 8:19

0

Know someone who can answer? Share a link to thisquestion viaemail,Twitter, orFacebook.

Your Answer

Sign up orlog in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

By clicking “Post Your Answer”, you agree to ourterms of service and acknowledge you have read ourprivacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.