Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to design a class to prevent circular dependencies from calling derived members before construction?

(I tagged this as both C# and Java, since it's the same question in both languages.)

Say I have these classes

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. */ }
}

The problem with this design is that as soon as FileManager tries to do anything inside its constructor with kernel (which it might reasonably need to), it will be calling a virtual method on a potential subclass instance whose constructor is not yet called.

This problem doesn't occur in languages where you can define true constructors (rather than initializers, like C#/Java), since then the subclasses don't even exist before their constructors are called... but here, this problem happens.

So what is the best/proper design/practice, to ensure this doesn't happen?

Edit:

I'm not necessarily saying I need circular references, but the fact is that both Kernel and FileManager depend on each other. If you have a suggestion on how to alleviate this problem without using circular references, then that's great too!

like image 595
user541686 Avatar asked May 18 '12 20:05

user541686


People also ask

How do you prevent circular dependencies?

Avoiding circular dependencies by refactoring Circular dependencies create tight couplings between the classes or modules involved, which means both classes or modules have to be recompiled every time either of them is changed.

How do you break cyclic dependencies?

To remove the cycle using DI, we remove one direction of the two dependencies and harden the other one. To do that we should use an abstraction: in the order module, we add the DiscountCalculator interface. Thus, the Order class depends on this new type and the order module no longer depends on the customer module.

Can constructor injection prevents circular dependencies?

If you are predominantly using constructor injections then it is possible to create circular dependencies in Spring.

What are circular dependencies among servers and how can they be avoided?

A circular dependency occurs when two classes depend on each other. For example, class A needs class B, and class B also needs class A. Circular dependencies can arise in Nest between modules and between providers. While circular dependencies should be avoided where possible, you can't always do so.


2 Answers

To me, having circular dependencies between this kind of objects smells badly.

I think you should decide which object is the main one, and which one is the subject for aggregation, or even composition. Then construct the secondary object inside of the main one, or alternatively, inject it as a dependency of the main object. Then let the main object register its callback methods in the secondary object, which will call them whenever it needs to communicate with the "outer world".

If you decide that the relation type is aggregation, then once the main object is to be destroyed, it will unregister all the callbacks.

And if you go with composition, then just destroy the secondary object when the main one is being destroyed.

Here's an example of what I mean:

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 );
    }
}
like image 185
Dmytro Shevchenko Avatar answered Oct 06 '22 01:10

Dmytro Shevchenko


Personally, I don't like circular references. But if you decide to leave them, you may add some laziness:

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. */ }
}  

Laziness here lets ensure, that IKernel implementation will be initialized completely, when FileManager instance will be queried.

like image 31
Dennis Avatar answered Oct 06 '22 01:10

Dennis