Dependency Injection Breaks Encapsulation

[quote=“TomB, post:77, topic:113596”]

If you couple the code to the interface rather than the concrete class ,e.g. Person then you can just slot in any other class that has the same interface. In my example:

dbobject = new Person(); 
$result = $dbobject->updateRecord($_POST);

It’s impossible to reuse this code with any object other than Person, which is as you say, tightly coupled.[/quote]

It is tightly coupled by virtue of the fact in my original example all those $dbobject->setXXX() methods access data which is local to the Person object, and it is THIS which produces tight coupling as per the description in that wikipedia article.

This second example is EXACTLY what I use in my reusable controllers. This means that the controller can be used with any class which implements that method, which means ALL of my Model classes.

You are providing examples of DI which I have actually employed in my framework for the simple reason it is in my Controllers where I instantiate the Person class in that way. This is because all my 40+ Controllers can be reused with any of my 300+ Models. Each Controller is not tied to a particular Model class, so at runtime I have to “inject” the name of the class(es) which I want it to use.

The other example, where within the Person object I am making a call to the Address object, is slightly different because here I cannot supply an alternative to the Address object because there is no alternative, and because there is no alternative there is no point in using DI as DI is all about providing the ability to inject alternative objects. No alternatives == No DI.

The first example is a Controller initialising a Model (which could be any Model) whereas the second example is a Model class (Person) which is initialising the only class in the entire system (Address) from which an address can be obtained.

Does this help to clear your confusion?

That’s true but the simple presence of the new Person() line has the same effect. The code is tightly coupled to the person object because of the hardcoded class name, the code cannot ever be used with any other class regardless of the class API. Even if you have another class with the same API the same code cannot be used. As soon as you have a concrete “new X” line, the code is tightly coupled. This is why injecting dependencies is preferred.

And this makes sense, my argument was that you can take that a step further. This is basically DI. the only problem is that this line:

$dbobject = new $table_id;

is needlessly inflexible. If instead of passing in $table_id you passed in a fully constructed object so:

$dbobject = new $table_id;
$result = $dbobject->updateRecord($_POST);

Becomes

$result = $dbobject->updateRecord($_POST);

And wherever you had

$table_id = 'Person';

Becomes

$dbobject = new Person;

You are now free to have different constructor arguments for each model.

I disagree. According to the Wikipedia article tight coupling produces an undesirable characteristic where a change in one module usually forces a ripple effect of changes in other modules. With my example I can change the contents of either the consumer or the dependent without having any sort of ripple effect on the other. The consumer does not now the names of any field names which exist in the dependent, so I can change the contents of the dependent without having to make any corresponding changes in the consumer. I believe that it the lack of this ripple effect which produces loose coupling.

This would not be workable for me because it would mean changing 2,400+ component scripts so that they passed in an object instead of a class name. This would be an enormous amount of effort with absolutely no benefit.

It would also be unworkable because most of my page controllers contain code similar to the following:

require_once "classes/$table_id.class.inc";
if (isset($script_vars['dbobject'])) {
    // use data from previous instance for this script
    $dbobject = unserialize($script_vars['dbobject']);
} else {
    // create new instance for initial activation of this script
    $dbobject = new $table_id;
} // if

Here I am either instantiating a new object or obtaining the state of the object from the previous iteration. Moving this code from the 40+ controllers to my 2,400+ component scripts would be a huge amount of effort with absolutely no benefit, other than satisfying someone’s definition of “this is how it should be done”.

Although all the descriptions of DI on the internet and in books state that it is an object which must be injected, what is the difference with instead passing the name of a class which can be instantiated later. Perhaps this is not a possibility in other languages, but it is certainly possible in PHP, so I am taking advantage of that facility.

I do not actually need that facility as none of my Model classes use any constructor arguments. None of these classes needs to be “configured” before they can be used, they can just be used.

Once again. Nobody is asking you to change anything. I was pointing out how you could make it more flexible, not saying you should Please stop with this “MY CODE” nonsense.

It just seems you have created a situation where you cannot supply constructor arguments to your models and now you’re coming here looking for use to pat you on the back and tell you it will be ok. The whole thing stinks of post-rationalisation.

Hypothetically: If you didn’t have to change all 2000+ files and you were advising someone who was writing a new project from scratch, what is the reasoning you would give for not allowing models to have constructor arguments?

But you argument that it could be more flexible is only relevant if the circumstances permit it. All I am saying is that I have encountered circumstances where such flexibility would be lost and therefore not worth the effort. Just as I have described circumstances where I don’t believe that DI would provide any benefits.

You should realise that programming involves trading off one design decision against another where you need to balance cost against benefit. I have made decisions which I feel are best for me, but you are free to make your own decisions. Just because we make different decisions does not mean that one of us is automatically wrong. I have been using my methodology for over 10 years and it has not let me down yet, so it can’t be that bad.

It is not that I don’t allow my Model classes to have constructor arguments, it is because I don’t need any. Each class starts off by being empty, and I populate it with data either either by pushing data from the Controller using the insertRecord() method or pulling data from the database using the getData() method. There is no rule which says that I MUST use constructor arguments, so I don’t.

I am going to capitulate and say this one last comment.

This whole discussion reminds me of an old cartoon I saw as a kid many moons ago. For a bit of light heartedness. :smile:

Scott

5 Likes

So you are not going to discuss the idea that while there are some circumstances where DI provides benefits there are also some circumstances where DI does not provide benefits?

If you are not adding anything constructive to the discussion then why are you filling it with noise?

The problem is, you’re still discussing the efficacy of DI without taking into account the efficacy of the alternative. It’s a zero-sum game. Whether or not DI provides any specific benefit is offset by any negative traits of alternative approaches. If DI is neutral, as in no benefit and no negative, then the alternative has to give a positive benefit in order to be “better”. All of the situations you have shown fail to do this. Even in your case where DI provides “no benefit”. The drawbacks of alternative approaches provide a negative benefit. Even in situations where DI doesn’t directly add a benefit it’s still preferable any of the other approaches you have listed.

There is no discussion about this. Nobody has claimed DI is the perfect solution for every situation. It is you who is saying it is only good the way you use it or it is “evil”. The way DI is used in other frameworks is also very good and it is a core part of decoupling code, which means upholding encapsulation, which you’ve said breaks when using DI. And then when people tell you are wrong and even show you why, instead of taking a step back and possible going, “Oh yeah, maybe I am wrong.” You keep going on and on and on, despite being wrong. Here is some more noise. This just came to mind.

Sorry you don’t like my attempt at a bit of light hearted humor. Too bad. I thought it was quite fitting. I got a good chuckle from it, which is more than this thread has given me. Next time you want to discuss something you aren’t willing to keep an open mind about, please don’t mention me. Thank you.

Scott

5 Likes

Sorry, had to make myself a liar.

Since you’ve come to this conclusion, can you please then change your “DI is evil” post to say people who made the decision to use DI are also good developers using a good design pattern probably making a good decision, instead of calling them boss following mindless trained monkeys? To refresh your memory, please change this paragraph.

Unfortunately most OO programmers do not have the brain power to evaluate DI to see if it would actually provide any benefit in their particular circumstances. Either they are told by a supposed “superior” that they should always implement it, in which case they obey without question just like a bunch of trained monkeys, or they hear about a new ‘cool’ design pattern and they rush to implement it just to prove to their peers that they are pattern experts.

Scott

A reminder to everyone that while disagreements are ok, personal attacks are not. Discuss ideas, not personal characteristics, please.

1 Like

This in response to the first question posed, Are there circumstances where using DI would not provide any benefits and therefore should not be used?

Let me just give an example of a situation where (IMO) it absolutely should not. Here is the situation with the particular technology we use on 2 of or projects. When you want to expose a class to the DI system, you put an attribute above the class (thereby creating a dependency on DI of course). The DI system is a monstrosity that can’t co-exist with our unit testing technology (at least, we’ve never been able to figure out how to do it). So now, you cannot unit test it. I was forced to remove things from DI wherever unit tests were critical. I doubt anyone would disagree that this is a situation where DI should not be used.

It sounds like you’re describing a DI container. DI by itself is just a design pattern. A DI container is a piece of software, and there are many to choose from, each with their own pros and cons.

OK, but with our technology, there really is no choice; the community uses one system for DI and related.

I’m confused here. How does DI relate to re-use? I have subsystems in my projects that I’ve shared with the world. If my subsystem were using DI, then anyone using my subsystem would have to install/configure my DI system. As it is, the subsystem is self-contained, uses no DI, and would not benefit from DI. People can just use it.

BTW, I’m clicking the “reply” button in specific locations, but my replies are showing at the bottom of the entire thread. I don’t understand how to use the forum I guess.

Off Topic:

You’re doing the right thing; Discourse software takes a bit of getting used to.

You’ll see in the top corner of your post that you replied to a post by s_molinari:

Clicking on the arrow or name should take you to that post. (I confess that it frequently doesn’t seem to work for me.) It also works the other way, in that his post now has a “replies” button, and clicking that will show you the direct replies to his post.

Edit: That ^ is an image - before folk start clicking it and wondering why nothing happens…

No you’re doing it right :slight_smile: your replies are shown linked to the post that you responded to.

Edit: Ninja’d by TechnoBear :wink:

1 Like

Because they don’t have to reuse everything. If X creates Y and Y creates Z it’s difficult to reuse Y or Z. It’s also impossible to use a custom implementation of Y such as MyY with your class X because I can’t supply a subclass instead. I have to use the exact implementation that X creates Y internally, which might not be right for my project… meaning I cannot reuse any of your classes in my project.

I can’t just take one class and use it in my project, I have to also take every single dependency that class has (And every dependency those have, and every dependency those have, ad nauseam)

1 Like