Meh*_*dad 7 c# java oop inheritance circular-dependency
(我将它标记为C#和Java,因为它在两种语言中都是相同的问题.)
说我有这些课程
interface IKernel
{
// Useful members, e.g. AvailableMemory, TotalMemory, etc.
}
class Kernel : IKernel
{
private /*readonly*/ FileManager fileManager; // Every kernel has 1 file manager
public Kernel() { this.fileManager = new FileManager(this); /* etc. */ }
// implements the interface; members are overridable
}
class FileManager
{
private /*readonly*/ IKernel kernel; // Every file manager belongs to 1 kernel
public FileManager(IKernel kernel) { this.kernel = kernel; /* etc. */ }
}
Run Code Online (Sandbox Code Playgroud)
这种设计的问题是,只要FileManager尝试在其构造函数内部执行任何操作kernel(它可能合理地需要),它就会在潜在的子类实例上调用一个虚方法,该实例的构造函数尚未被调用.
在你可以定义真正的构造函数(而不是初始化程序,如C#/ Java)的语言中不会出现这个问题,因为在它们的构造函数被调用之前,子类甚至都不存在 ......但是在这里,会出现这个问题.
那么什么是最佳/适当的设计/实践,以确保不会发生这种情况?
我不一定说我需要循环引用,但事实是,无论Kernel和FileManager互相依赖.如果您有关于如何在不使用循环引用的情况下缓解此问题的建议,那么这也很棒!
对我来说,在这类对象之间建立循环依赖关系的做法很难闻。
我认为您应该决定哪个对象是主要对象,哪个对象是聚合甚至组合的对象。然后在主要对象内部构造次要对象,或者将其作为主要对象的依赖项注入。然后,让主对象在辅助对象中注册其回调方法,该方法将在需要与“外部世界”通信时调用它们。
如果您确定关系类型为聚合,那么一旦要销毁主对象,它将注销所有回调。
如果您使用合成,则只需在破坏主要对象时破坏次要对象即可。
这是我的意思的示例:
class Program
{
static void Main( )
{
FileManager fm = new FileManager( );
Kernel k = new Kernel( fm );
fm.DoSomething( 10 );
}
}
class Kernel
{
private readonly FileManager fileManager;
public Kernel( FileManager fileManager )
{
this.fileManager = fileManager;
this.fileManager.OnDoSomething += OnFileManagerDidSomething;
}
~Kernel()
{
this.fileManager.OnDoSomething -= OnFileManagerDidSomething;
}
protected virtual void OnFileManagerDidSomething( int i )
{
Console.WriteLine( i );
}
}
class FileManager
{
public event Action<int> OnDoSomething;
public void DoSomething( int i )
{
// ...
OnDoSomething.Invoke( i );
}
}
Run Code Online (Sandbox Code Playgroud)
就我个人而言,我不喜欢循环引用。但如果你决定离开它们,你可能会增加一些懒惰:
interface IKernel
{
// Useful members, e.g. AvailableMemory, TotalMemory, etc.
}
class Kernel : IKernel
{
private readonly Lazy<FileManager> fileManager; // Every kernel has 1 file manager
public Kernel() { this.fileManager = new Lazy<FileManager>(() => new FileManager(this)); /* etc. */ }
// implements the interface; members are overridable
}
class FileManager
{
private /*readonly*/ IKernel kernel; // Every file manager belongs to 1 kernel
public FileManager(IKernel kernel) { this.kernel = kernel; /* etc. */ }
}
Run Code Online (Sandbox Code Playgroud)
这里的惰性可以确保当查询 FileManager 实例时,IKernel 实现将被完全初始化。