First let me say that I’m not that experienced with OO patterns, practices, clean code etc.
I’m actually learning all these techniques.
The most loosely coupled way would be to use primitive types for constructing new objects or executing methods but I think it´s not practicable, is this correct?
Because it is more prone to errors. I could give over lets say integers that represent Ids that do simply not exist. If I would use an object I actually know that there is valid data for it otherwise the object would not have been created (exception) or would be in an invalid state which I have to check for.
This article says that it is evil to use concrete objects for this, instead I should hand over their interfaces (as you all know, I guess).
Changes to the concrete type (not the interface) would cause the dependent type to “breakdown”. Is that so in all cases? Is this also true for a closed single project environment?
I would only understand that, if interfaces are – once written – untouchable and never be modified/refactored again.
You should use everything when needed. Use a primitive only if it makes sense to. Same with concrete types. It’s true that it’s better to be ‘coupled’ to an abstraction however in many cases you won’t have one and you don’t need one. You can extract interfaces from all the objects you’re using but it would be pointless if you don’t have a real reason to do so (polymorphism).
You have to think. You can start using a concrete type but then you observe that you’d really need an abstraction not really a concrete type ( for example, abstracting the storage is a very common occurence if you know the storage can change). Instead of depending on a Mysql Database object, an object can depend on a Database abstraction (abstract class or an interface) which allows you to switch any implementation (Mysql, MMSql or even a NoSql). However if you’re coding a small app where you’ll use only mysql, just use the concrete type directly.
There is one more reason you might want to extract interfaces and that is testing. Of course, extract an interface only if the concrete type will be used as a dependency and it has complex enough behavior. If a dependecy is just a simple DTO (Data Transfer Object) you don’t need to abstract it.
Most of this stuff comes with experience, but a rule of the thumb is start with concrete types then abstract it if needed. If an object already implements an interface that includes the functionality you want use that interface directly .