This is exactly the reason why DI containers exist. I will give you an example for Castle Windsor, but it should apply to other containers, with some modifications.
Class definitions:
public class A1
{
public A1(decimal temperature)
{
_temperature = temperature;
}
private readonly decimal _temperature;
// what follows is specific to the possible methods that make use of temperature
}
public class B1
{
public B1(A1 a1)
{
_a1 = a1;
}
private readonly A1 _a1;
// possible methods that make use of a1
}
Container configuration:
// this assumes a C# environment
var temperature = Convert.ToDecimal(ConfigurationManager.AppSettings["temperature"]);
IWindsorContainer container = new WindsorContainer();
container.Register(Component.For<A1>().DependsOn(Property.ForKey<decimal>(temperature)));
container.Register(Component.For<B1>());
container.Register(Component.For<MainForm>());
This is a very basic example of what you can do with a container. The types used by the application must be made known to the container. When asked to create an instance the container will search the constructor for dependencies and will create them from the types it knows about. This is called constructor injection because the constructor is used to specify the dependencies for a class.
Type resolving:
Dependency injection introduces a concept called composition root, which is the place where all the components of your application are put together (the configuration section above is placed in here). The container should (I'm using should because there are some exceptions to this rule) be used explicitly only in the composition root.
None of the classes of your application that are not related to infrastructure are allowed to have the container as a dependency. If you need to break this rule then it's a clear indication of a design flaw.
Let's assume that we have a desktop application, with a main form that uses our B1
class.
public class MainForm : Form
{
public MainForm(B1 b1)
{
}
...
// do something with b1
}
The composition root for this application is the entry point (for .net windows applications the entry point is Program.cs
). I have made MainForm
known to the container in the configuration code above. All I have to do now is obtain an instance of MainForm
and show it to the user.
public static class Program
{
// container configuration goes here
// type resolving follows
var mainForm = container.Resolve<MainForm>();
// show main form the the user
...
}
At this point, the container searches its list of known types for MainForm
, looks in the constructor for any dependencies and it tries to resolve them. It will move through the hierarchy down to the last type. By the time it finishes it will have created instances for all the types in the hierarchy (if the types are found in the container).
If the temperature is variable then I would use a class with a simple method called GetTemperature
and inject that class wherever I need it. The method presumably performs some logic to retrieve the temperature. If the system must respond to temperature changes then the classes that depend on it can be observers of the class that emits these changes.
If a class depends on multiple configuration constants then it's perfectly fine to group them in a single class and pass that class as dependency where it's needed. Castle has an adapter that helps in this particular case.
I recommend reading Mark Seemann's book (Amazon link) on dependency injection, it is very well written and is, in my opinion, the best book on the subject.
Best Answer
Well, when I saw this design, first thing which came into my mind was "why the heck does the
TextBlock
have a Graphics attribute at all? Should it not be possible to draw the same block on two different graphics contexts? Should theTextBlock
object not have a lifetime which may be longer than the lifetime of theGraphics
object?"So my answer is yes - it does not make much sense to me not to have a Graphics parameter in the
Draw
function. This will imply to pass theGraphics
parameter into theDrawBlocks
method of thePage
, and some other method in yourSection
, of course, but that is probably the correct design.In fact, when your
TextBlock
has more than a half dozen private methods, all called directly or indirectly fromDraw
, and all of them use the sameGraphics
object, it may be more convenient to buffer theGraphics
object passed by theDraw
method in a member variable, so you do not need to have the sameGraphics
parameter in all of those methods. So keeping this attribute as a member might make sense (but this cannot be deciced from the small part of the text block class we see in your current model). And if you decide to keep this attribute, make sure you add a comment when this attribute is set and how long it is valid. The code then will look like thisAlternatively, you could consider to introduce a helper class
TextBlockDrawer
, which gets theGraphics
object and theTextBlock
passed as a constructor parameter, and encapsulates the whole drawing process. That way, you won't need aGraphics
member in yourTextBlock
class any more, only inTextBlockDrawer
, and this attribute can be initialized in the constructor of that class. This design also makes a better separation between the data (TextBlock
) and the drawing process. The resulting code will look either like this:or, by omitting the
Draw
method in theTextBlock
completely, and reuse theTextBlockDrawer for different text blocks
: