How to resolve circular dependency in class constructors?

Dima Kambalin

I have CategoryService class which depends on FilterService:

export class FilterService implements IFilterService
{
  protected readonly categoryService: ICategoryService;
  
  constructor(
    categoryService: ICategoryService;
  ) {    
    this.categoryService = categoryService;
  }
}

And CategoryService which depends on FilterService:

    export class CategoryService implements ICategoryService
    {
      protected readonly filterService: IFilterService;
      
      constructor(
        filterService: IFilterService;
      ) {    
        this.filterService = filterService;
      }
    }

How to resolve such circular dependency?

Thomas Frank

Often circular references when composing/aggregating in OOP makes for nice human readable logic. And as long as you don't want to serialize the data they pose no real problem.

You can look for a library that can handle circular references during serialization/deserialization, but then you are 'locked in' into that particular library's way of expressing circular references.

So the most generic way do get rid of circular references is to store an id rather than an object reference.

Using getters and setters to 'rehydrate' fields (including lists) can make this a rather pleasent experience with almost no changes to your application logic.

Here's a code example with circular references, pets know about their owner, the owner in turn knows about they pets etc.

Code with circular references

class Pet {

  constructor(name, species) {
    this.name = name;
    this.species = species;
  }

  chooseOwner(owner) {
    this.owner = owner;
    owner.buyPet(this);
  }
}

class PetOwner {
  pets = [];

  constructor(name) {
    this.name = name;
  }

  buyPet(pet) {
    if (this.pets.includes(pet)) { return; }
    this.pets = [...this.pets, pet];
    pet.chooseOwner(this);
  }
}

// Tests
let jon = new PetOwner('Jon');
let garfield = new Pet('Garfield', 'cat');
let odie = new Pet('Odie', 'dog');
garfield.chooseOwner(jon);
jon.buyPet(odie);
console.log('Jons pets', jon.pets);
console.log('Garfields owner', garfield.owner);
console.log('Odies owner', odie.owner);

Code without circular references

Here's the same example reworked so that it does not have any circular references.

We are using an "ObjectMemory" for all instances created in our program that makes it easy to set an id to each instance and look it up from the memory later (in our getters and setters). Just remember to not mutate lists, create new ones instead, thus triggering the setter for the list in question and everything works fine...

class ObjectMemory {
  static list = [];

  static add(object) {
    this.list = [...this.list, object];
    return this.list.length - 1;
  }
}

class Pet {
  #ownerId;
  get owner() { return ObjectMemory.list[this.#ownerId] }
  set owner(owner) { this.#ownerId = owner.id; }

  constructor(name, species) {
    this.name = name;
    this.species = species;
    this.id = ObjectMemory.add(this);
  }

  chooseOwner(owner) {
    this.owner = owner;
    owner.buyPet(this);
  }
}

class PetOwner {
  #petIds = [];
  get pets() { return this.#petIds.map(id => ObjectMemory.list[id]); }
  set pets(pets) { this.#petIds = pets.map(pet => pet.id); }

  constructor(name) {
    this.name = name;
    this.id = ObjectMemory.add(this);
  }

  buyPet(pet) {
    if (this.pets.includes(pet)) { return; }
    this.pets = [...this.pets, pet];
    pet.chooseOwner(this);
  }
}

// Tests
let jon = new PetOwner('Jon');
let garfield = new Pet('Garfield', 'cat');
let odie = new Pet('Odie', 'dog');
garfield.chooseOwner(jon);
jon.buyPet(odie);
console.log('Jons pets', jon.pets);
console.log('Garfields owner', garfield.owner);
console.log('Odies owner', odie.owner);

Additional bonus of this approach: If you want to serialize things now you really only need to serialize the ObjectMemory.list since it contains all instances with correct id:s/lookups.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

How to resolve circular dependency in Gradle

How to resolve circular dependency in DbContext

Circular dependency - how to resolve and avoid

How to resolve this circular dependency in EF

Circular Dependency Injection in constructors

How to resolve a circular dependency in nest js?

How to resolve circular dependency for Spring context?

Resolve circular dependency in gradle

Resolve circular dependency in Swift

How to resolve the issue of circular nested class definition

How to resolve composer package circular dependency of external package?

How to resolve a circular dependency while still using Dagger2?

How do I resolve this Angular 7 "Circular dependency detected" warning

How to resolve circular dependency when working with IAM roles

Is there a way to resolve this template circular dependency

Class Circular Dependency

Circular dependency in the class constructor

Design: Class circular dependency?

How to resolve dependency in static class with Unity?

How to use or resolve a service(or dependency) in the Startup class

How to solve a circular class dependency with shared base class without headers?

How to resolve maven problems when using dependency injection to solve circular dependency?

How to break circular dependency of the headers that defines nested template class?

How not to get a circular dependency?

How to solve the circular dependency

Fix circular dependency in arithmetic class

How to Resolve a .so Dependency

How to resolve the dependency of `highline`

How to solve Autofac circular dependency?