architecture – Class dependency management guide

I have been programming professionally for over 7 years now, always trying to follow best practices. During this period, my approach to addictions has evolved several times:

  1. Static classes
  2. Create each object by hand, with its dependencies
  3. Create an interface for each class containing logic
  4. ID pure / poor man's DI – it was a nightmare (the existing code base was definitely non-SOLID, classes were numerous and had tons of outbuildings)
  5. IOC container – it was a big relief (actually hiding the problem above)
  6. Ignore interfaces, unless it is really necessary (multiple implementations)
  7. DDD approach instead of stateless services (reducing the number of dependencies, a lot of logic in the entities' value objects)

Looking at Greg Young's presentation 8 Lines of Code has made me rethink my previous approach to class dependencies. Recently I was like:

  • Static classes are bad (no polymorphism)
  • "Well, I've created a new class – it does not matter, just throw it into the IOC container and place it as an add-on for builders from other classes who need it."

Class type

Greg said, "A stateless object using a single method is essentially a function" – it makes a lot of sense. How can we represent it in OOP? With a static class (back to the beginning no?).

But you can not inject dependencies into a static class! – well, you can very easily:

SomeClass static class {
empty static public DoSomething1 (MyDependency1 dependency, MyArg someArg) {...}
empty static public DoSomething2 (dependency MyDependency2, MyArg someArg) {...}
// or even:
blank static public DoAnotherThing (Func<,> addiction, MyArg someArg) {...}
}

This type of dependency injection might even be better (the class has 3 dependencies, but when called Something1 we only need one, so it's useless to create the rest when Something1). Of course, we can divide the class into 3 classes, but what if the methods are closely related? Maybe extract the common part to the 4th class? And what if the common part does not make sense by itself? We are left with a strange abomination that we can not even find a name.

Another problem occurs when the A class is an addiction of some Other class. We can not inject it by the manufacturer, but we can:

  • Inject SomeClass.DoSomething1 as a parameter (a delegate) to the Other class method
  • Wrap it with a non-static class and inject it to the constructor.

But if we want to wrap the static class in a non-static class in order to be able to inject it by constructor, why make it static in the first place? This is a simple solution in case we would have made a bad idea initially that there would be no alternative behavior. Maybe we should never assume that?

DI vs no DI

Now some thoughts on when to use DI. Suppose we have the following class hierarchy to handle different types of documents:

IDocumentHandler interface {
void Handle (string doc);
}

IDocumentParser interface {
Node[] Analysis (doc doc)
}

class XmlParser: IDocumentParser {...}

LatexParser class: IDocumentParser {...}

class XmlHandler: IDocumentHandler {...}

LatexHandler class: IDocumentHandler {...}
  • Managers need parsers to convert a document string to knots, to be able to treat it. But XmlHandler is closely related to XmlParser. It will not work with LatexParser
  • However, the analyzers are used by other classes that do not care about their parser. For example a chain of responsibility (the interface & # 39; IDocumentParser & # 39; is therefore logical)
  • Yes XmlHandler always wants XmlParserDoes the injection of addiction make sense here? – I would say no. I do not want to put this rule very far in the IOC container. There is another problem: and if XmlParser has its own outbuildings?

I see 2 options, 1st:

XmlHandler class: IDocumentHandler {
public XmlHandler () {
this.parser = new XmlParser (new SomeDependency ()) // this could become ugly if the dependency tree is wide or deep
}
}

2nd:

XmlHandler class: IDocumentHandler {
public XmlHandler (dependency on SomeDependency) {
this.parser = new XmlParser (dependency)
}
}

The 2nd option solves the big problem of the dependency tree if we create XmlHandler with an IOC container.

Conclusion

It was very long, but I wanted to explain my thought process. The real purpose of this question is to validate my recommendations for choosing the right ID strategy. This is what I came up with:

  • Class with state → non-static class, instances created by an explicitly calling constructor to keep its life cycle under control
  • Stateless class, unlikely to have alternative behavior → static class, to avoid large dependency trees when they are not needed
  • Stateless class, likely to have an alternative behavior → non-static class with an interface (or ignore the interface if you have a good IDE that will help you to easily introduce it wherever you will need it )
  • If your class requires a specific implementation of an interface → do not inject it, create it explicitly in the constructor of your class
  • Save only the "top (ish) -level" classes in the IOC container, to penalize large dependency trees (this "hurts" you to add a new dependency, so you think about it twice).

What do you think of this guideline? Do you want to change something?