IoC allows us to decouple the interface from the implementation, and can allow external forces to determine the implementation.
Consider the implications for
testing method foo:
We can only test
foo() with an instance of a
Bar. If we need to test the return value of
foo(), we now have to re-implement our
Bar.baz() to return each of the possible conditions
foo() could encounter. If, on the other hand, we allow the
Bar to be set, our unit test can decide which condition we're testing:
IoC also allows for strong configurability. Consider a program intended to allow easy extension or modification of key components. Instead of going in and changing the original source code, we can just tell the program at startup which implementation to use. This might allow us to customize how HTTP requests are handled, provide a plugin mechanism, and so on.