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?
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.
Comments