I have a scenario where I need a lower layer to be controlled by an upper layer much like a puppet master pulling on strings.
The lower layer also will call back to the upper layer as some internal events are generated from time to time.
I am using SimpleInjector, I inject the ILower in to the Upper constructor. I cannot inject the Upper in to the Lower as it would cause a circular reference.
Instead I have a register callback function to link the two layers. However, I have to scatter my code with null checks.
Are there any nicer ways or different architectures to achieve this linking of objects?
// an interface that transport can callback from transport to client
public interface ILowerToUpperCallback
{
void ReplyA();
void ReplyB();
}
// transport interface that client calls
public interface ILower
{
void Test1();
void Test2();
void RegisterCallback(ILowerToUpperCallback callback);
}
public class Upper : ILowerToUpperCallback
{
private readonly ILower lower;
public Upper(ILower lower)
{
this.lower = lower;
this.lower.RegisterCallback(this);
}
void ReplyA()
{
}
void ReplyB()
{
}
}
public class Lower : ILower
{
private ILowerToUpperCallback callback;
/* this is not possible, would cause a circular reference
public Lower(ILowerToUpperCallback callback)
{
this.callback = callback;
}
*/
// set by different method instead, what happens if this is never set?!
void RegisterCallback(ILowerToUpperCallback callback)
{
this.callback = callback;
}
void OnTimer()
{
// some timer function
if(this.callback != null) // these null checks are everywhere :(
this.callback.ReplyA();
}
}
I don't think there is anything wrong with your design, although you already noticed that it isn't the easiest thing to configure. The problem isn't in the limitations of your DI framework, but more in the mental gymnastics you'll have to perform.
Here is an idea. Change your classes to the following:
public class Upper : IUpper, ILowerToUpperCallback
{
public Upper(/* all depedencies except ILower */) { }
// Promote ILower to property dependency
public ILower Lower { get; set; }
}
public class Lower : ILower
{
// Use the Null Object Pattern for default implementation to prevent
// null checks.
private ILowerToUpperCallback callback = new NullCallback();
public Upper(/* all dependencies except ILowerToUpperCallback */)
{
this.callback = callback;
}
// Allow overriding the default implementation using a method, just
// as you are already did.
public SetCallback(ILowerToUpperCallback callback)
{
if (callback == null) throw new ArgumentNullException("callback");
this.callback = callback;
}
}
With this design you can wire everything up as follows:
container.Register<ILower, Lower>();
container.Register<IUpper, Upper>();
container.RegisterInitializer<Upper>(upper =>
{
var lower = (Lower)container.GetInstance<ILower>();
lower.SetCallback(upper);
upper.Lower = lower;
});
Since Lower
and Upper
are normal services you can resolve them as usual. By registering an initializer delegate for Upper
, you can do some extra initialization after the container created Upper
. This initializer wires Lower
and Upper
together. Since the Composition Root knows about ILower
and Lower
, we can safely cast from ILower
to Lower
, without breaking any rule. Best about this design is that the ILower
interface is kept clean, and oblivious of the ILowerToUpperCallback
, which is in fact an implementation detail.