I'm finding that IoC simplifies:
- Object life cycle control.
- Object wiring.
- Object construction - Automates code generation for circular dependencies.
Before:
CfaRegionsChangedListenerConduit regionChangedConduit = new CfaRegionsChangedListenerConduit();
ICfaRegions cfaRegions = new CfaRegions(regionChangedConduit, persistenceService);
FormatterListenerConduit formatterListenerConduit = new FormatterListenerConduit();
IncidentGridViewCellFormatter incidentGridViewCellFormatter =
new IncidentGridViewCellFormatter(cfaRegions, formatterListenerConduit);
regionChangedConduit.SetTarget(incidentGridViewCellFormatter);
IncidentsGridViewController incidentsGridViewController = new IncidentsGridViewController();
IncidentsGridView incidentsGridView =
new IncidentsGridView(cfaDataSet, incidentsGridViewController, incidentGridViewCellFormatter);
incidentsGridViewController.Inject(incidentsGridView, new BrowserMapView());
formatterListenerConduit.SetTarget(incidentsGridViewController);
incidentsView = new IncidentsView(new RegionSelectionControl(cfaRegions), incidentsGridView);
hostServices.Show(incidentsView, DockState.Document);
After:
system.HasSubsystem(new IncidentsViewBuilder()).Provides<DockContent>();Although a trivial example, I find the after code to be more readable. It has also separated the object wiring from the usage code. The wiring necessary to build a ContentForm object is hidden in the builder. This makes code reuse easier. It also abstracts the use of common objects (in this case the CFADataSet and the PersistenceService. Traditionally this is done by using a factory, but that would required common objects to be passed explicitly down in a series of constructors/methods. This makes managing object life cycles much easier.
hostServices.Show(system.Get<ContentForm>(), DockState.Document);
:
public class IncidentsViewBuilder : ISubsystemBuilder
{
public void Build(ISystemDefinition system)
{
system.HasSingleton<CfaRegions>().Provides<ICfaRegions>();
system.HasSingleton<IncidentGridViewCellFormatter>().Provides<IncidentGridViewCellFormatter>();
system.HasSingleton<IncidentsGridViewController>().Provides<IncidentsGridViewController>();
system.HasSingleton<IncidentsGridView>().Provides<IncidentsGridView>();
system.HasSingleton<BrowserMapView>().Provides<BrowserMapView>();
system.HasSingleton<RegionSelectionControl>().Provides<RegionSelectionControl>();
system.HasSingleton<IncidentsView>().Provides<ContentForm>();
}
}
What I'm now finding is that I can add a parameter to a constructor and the application 'just works' without any other code changes as the IoC framework just finds the required objects using its wiring rules. No need to work out life cycles and walk up the ladder of factories. This means that the product's architecture is less likely to become corrupted as developers add more features. So it becomes an enabler for less skilled developers to work on the code.
One 'gotcha', and perhaps a future feature for NDependencyInjection is that adding a new parameter to a constructor does mean that unit tests must be updated to provide the mocked object. Wouldn't it be great if NDependencyInjection could be set to a unit testing mode for a given type so that when and instance of the type is requested all required types are generated as mocked objects? Perhaps a 'GetTestObject' method to compliment the current 'Get' method? Further code generation automation ... write less code and reduce the risk of less skilled developers introducing integration tests disguised as unit tests.
NDependencyInjection is well worth the effort. Great work Nigel!.
No comments:
Post a Comment