Also, these global objects force us to do a bunch of hacks in unit
tests. We need to do tricks to ensure the object is initialized as
we want. We also need to save and restore its state between runs.
I don't agree with the statement that they force "a bunch of hacks,"
clearing
state is a perfectly normal thing to do, it is done for any servers
that get
started, any mocks that are made, and every test database. Making sure
that
modifications to a configuration object are cleaned up is no
different: there
is no "save and restore" just always start from a blank slate and set
things
as required, same as in the non-global model.
But ensuring that we are back at tabular rasa is difficult with global
objects and running tests in a single process space. Many of the test
cases are dependant on second order effects of function calls to
configure internal objects, instead of being true unit tests that only
call on a single object.
This kind of refactoring is the norm in large projects, and leads to
better tested code paths, reusuable objects, and objects that are easier
to understand and track.
One way that these types of things have been described is via the SOLID
acronym. THis is a collection of best practices:
http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29
The Global config violates quite a few of these principals. By reading
from a configuration object, you implicitly violate Single
responsibility. Now it has at least two, one of which is figuring out
how to construct and initialize itself. It also doesn't really follow
the Open Closed principal: Once you get state from a Config object, you
can no longer easily extend it, unless that extended object also gets
its state from the same config. The big one is D: Dependency. By using a
global config, you make dependencies on implementations, not
abstractions. Dependency injection is pretty much impossible with a
global config.