"IoC", or more descriptive, "Inversion of Control", is advertised as a mechanism to dynamically insert "dependencies" into modules. In an abstract fashion, an implementation could look something like
define module m(a, b, c) define lookup(x: a -> a_1, b -> b_1, c -> c_1) define perform_ioc(lookup, m, profile) m(lookup(profile));
where a, b and c are interfaces that represent other modules, and a_1, b_1 and c_1 are implementations of those module interfaces.
lookup is a function that takes a profile and returns a data structure that maps the set of all interfaces to a specific implementation.
perform_ioc is a function that takes a profile and the desired module and attempts to create the module using the interfaces defined in the module against the interfaces defined in the profile, and alocate the result.
That's *all IoC is*. It is *nothing more*. It doesn't have some magical properties that people who sell books want you to believe. All it is is a data-structure replacement for "malloc". It is basiclaly a replacement for converting functions of the type:
define create_module_m(profile) { a my_a; b my_b; c my_c; if(profile == p) { my_a = new a_1(); my_b = new b_1(); my_c = new c_1(); } /* etc... */ return new m(my_a, my_b, my_c); }
This is essentially a replacement of a factory method. All it does is replace hand-rolled functions that specialize in creating special objects into a data-structure lookup function, which is good. Remember, the golden rule behind all of computer science is
The replacement is great, because it essentially makes changing object dependencies easier, by flattening all changes into only one level, rather than changing multiple factory functions all over the place and introducing more places for error. The tradeoff is increased debugging complexity (because if the wrong object is put into the right module, we have to take apart two languages instead of one, the one that constructs the mappings/instances and the one that looks up the mappings/instances), increased run-time complexity, and a blindness/ignorance in arbitrarily changing things just for the sake of change, and not realizing the impact of the change until much later.
The only limitation here is that we can't customize at run-time the arguments passed into each sub-malloc call or otherwise customize the instantiation. We would have to go back to multiple levels to do this or we'd have to provide a tree of options to pass for each profile. Presumably, that's what each profile is supposed to do. However, having lots of configurations may get a bit hairy. That depends on your use case, though.
We don't have the limit the implementation to a global lookup function/data structure or only defining maps, we can include instances as well. Since it's just yet another data structure, we can add/remove to it as we wish. We can change profiles at run-time (even on the global ones, if we really want to confuse people) or we can force the global one to be read-only by reading it from a mini-language file, like XML. We can redefine all this stuff. That doesn't change the fact that the end result is essentially a "malloc". It's a fancy malloc (and time-consuming, as a result), but it's just "malloc".
All it does is essentially shove an instance or a mapping into one memory location, and then allow that location to be queried if a malloc request is made for a type that is defined to use that instance or mapping! It's a cute idea, but it's nothing more than that.
And also, it's not AOP. Sorry. Unless you're willing to say malloc or Factories are AOP. Which means *everything* is AOP.