One of my beefs with most OOP languages is that constructors can run arbitrary code. It makes it really hard to know what is valid and what is not in them. I much prefer ML-family languages where constructors are really just allocating space and setting values, and that’s it. If you want to do something special to construct then it’s just a regular function.
One of my beefs with most OOP languages is that constructors can run arbitrary code.
If you’re not a fan of code in constructors, I’d stay away from LLVM. I’m not sure if this is still the case, but there are entire transformations that get done in constructors in some cases.
I expect this is because people have little interest in writing multiple classes to represent the different stages of getting to the final result. Although anecdotal, I’ve noticed it done more in C++ than in other languages.
That’s not to say I advocate for the way LLVM does these things, but I will admit, in my latest project (which is C++), the team has agreed that doing work in the constructor is not all that bad. Pulling the work out of the constructor would end up over-engineering things and the desire to go back and refactor it is low.
How would it be overengineering things? The style I advocate is do everything other than name binding in functions, so one would simply call a function rather than a ctor directly, this shouldn’t be any kind of engineering change. Maybe C++ has some limitation in terms of this though.
C++ mostly encourages you to do things in the constructor with the RAII mantra. If you don’t use the constructor to initialize, then you end up with an init function or you have a static creation function that probably needs access to private instance members, which means you’re writing getters/setters. So you end up with this sort of thing.
Foo f(1, 2);
f.init();
or
Foo f = Foo::create(1, 2);
In the former case you now have to go through the process of calling init all the time and it makes inheritence, if you end up using it, painful. In the latter case, you have to carefully manage move and copy constructors or risk paying for the overhead (or using pointers for everything, and then you’re right back to the destruction problem that constructors/destructors help deal with). In either case, you end up fighting the language.
I’ve been getting pretty excited about DI as I dive further and further into the Magento 2 landscape. Magento 2 has a lot of legwork involved with DI but once it clicks with you, you realize how powerful it is. Its set up with a bunch of XML files, and you use typehints in the class constructor to point to interfaces you’d like your class to work with. When you compile the DI, it read all the XML and associates interfaces with concrete classes (a global di.xml file), and then does some static analysis to see what class needs what other class to be constructed. That’s all abstracted away into an object manager.
What makes this powerful to me is that you can overwrite the global di.xml with a “local” di.xml which lives in your module’s namespace. The overwrite redefines which interfaces point to which classes, so you can implement say, your own LoggerInterface, and inject that into your own Observer.
Another very effective way of thinking about this is “constructor” was a bad choice of name.
They never should have been called that.
The fact that “Too much Work being done in a Constructor” is a code smell is a clue.
They should be called “Name Binders”.
They take a collection of values and bind them to instance variable names.
One of my beefs with most OOP languages is that constructors can run arbitrary code. It makes it really hard to know what is valid and what is not in them. I much prefer ML-family languages where constructors are really just allocating space and setting values, and that’s it. If you want to do something special to construct then it’s just a regular function.
If you’re not a fan of code in constructors, I’d stay away from LLVM. I’m not sure if this is still the case, but there are entire transformations that get done in constructors in some cases.
I expect this is because people have little interest in writing multiple classes to represent the different stages of getting to the final result. Although anecdotal, I’ve noticed it done more in C++ than in other languages.
I haven’t messed with LLVM and have no plans on it as of yet, but thanks for the tip :)
Function and data please, C got this right long ago while getting so much else wrong!
That’s not to say I advocate for the way LLVM does these things, but I will admit, in my latest project (which is C++), the team has agreed that doing work in the constructor is not all that bad. Pulling the work out of the constructor would end up over-engineering things and the desire to go back and refactor it is low.
How would it be overengineering things? The style I advocate is do everything other than name binding in functions, so one would simply call a function rather than a ctor directly, this shouldn’t be any kind of engineering change. Maybe C++ has some limitation in terms of this though.
C++ mostly encourages you to do things in the constructor with the RAII mantra. If you don’t use the constructor to initialize, then you end up with an
initfunction or you have a static creation function that probably needs access to private instance members, which means you’re writing getters/setters. So you end up with this sort of thing.or
In the former case you now have to go through the process of calling
initall the time and it makes inheritence, if you end up using it, painful. In the latter case, you have to carefully manage move and copy constructors or risk paying for the overhead (or using pointers for everything, and then you’re right back to the destruction problem that constructors/destructors help deal with). In either case, you end up fighting the language.I’ve been getting pretty excited about DI as I dive further and further into the Magento 2 landscape. Magento 2 has a lot of legwork involved with DI but once it clicks with you, you realize how powerful it is. Its set up with a bunch of XML files, and you use typehints in the class constructor to point to interfaces you’d like your class to work with. When you compile the DI, it read all the XML and associates interfaces with concrete classes (a global
di.xmlfile), and then does some static analysis to see what class needs what other class to be constructed. That’s all abstracted away into an object manager.What makes this powerful to me is that you can overwrite the global
di.xmlwith a “local”di.xmlwhich lives in your module’s namespace. The overwrite redefines which interfaces point to which classes, so you can implement say, your ownLoggerInterface, and inject that into your ownObserver.