-2

I am using the factory method pattern in some of my code. The problem is, some of those instances also use the same factory method pattern. This creates circular dependencies and I can't think of a way of removing them. Let me give an example:

// factoryMethod.ts
import Instance1 from './Instance1';
import Instance2 from './Instance2';
import Instance3 from './Instance3';
import { Instance, InstanceName } from './Instance';

export const getInstanceByName = (
  instanceName: InstanceName
): Instance => {
  switch (instanceName) {
    case 'Instance1':
      return Instance1;
    case 'Instance2':
      return Instance2;
    case 'Instance3':
      return Instance3;
    default:
      throw new Error();
  }
};

// extremelyHelpfulUtilityFunction.ts
import { getInstanceByName } from './factoryMethod';

export const extremelyHelpfulUtilityFunction = (instanceName: InstanceName): number => {
  // Imagine this was an extremely helpful utility function
  return getInstanceByName(instanceName).num
}

// Instance.ts
export interface Instance {
  doSomething: () => number;
  num: number;
}

export type InstanceName = 'Instance1' | 'Instance2' | 'Instance3';

// Instance1.ts
import { extremelyHelpfulUtilityFunction } from './extremelyHelpfulUtilityFunction';

const i: Instance = {
  doSomething: (): number => {
    return extremelyHelpfulUtilityFunction('Instance2') + extremelyHelpfulUtilityFunction('Instance3'); // circular dependency
  },
}
export default i;

// Other instances defined below, you get the idea.

I'm using rollup to turn this into a single JavaScript file, and when I do, it warns me that I have a circular dependency. I want to get rid of this warning. I realize the code will still function, but I don't want the warning there. How can I modify this code so that InstanceX can get InstanceY without it being a circular dependency?

Daniel Kaplan
  • 62,768
  • 50
  • 234
  • 356
  • What is the reason for using `extremelyHelpfulUtilityFunction('Instance2')` instead of `new Instance2()` here? `doSomething` does not take any parameters, so the string value is constant. – JulienD Jan 01 '21 at 22:39
  • if `Instance2's doSomething => extremelyHelpfulUtilityFunction("Instance1") + extremelyHelpfulUtilityFunction("Instance3")` then it will go into infinite recursion, right? – ABOS Jan 01 '21 at 22:42
  • @JulienD because the real `extremelyHelpfulUtilityFunction` is extremely complex in the real code. It does a lot more than that single line. – Daniel Kaplan Jan 01 '21 at 22:53

1 Answers1

1

IMO the problem is that extremelyHelpfulUtilityFunction has to know getInstanceByName, while the result of this factory could always be known in advance by the caller and the desired value passed as argument to the helper.

I would propose

// Instance1.ts
const instance1: Instance = {
  doSomething: (): number => {
    return (new Instance2()).toNum() + (new Instance3()).toNum()
  },
}

with toNum defined in Instance.ts and overridden in its subclasses, using the helper but with proper parameters, for example

// Instance2.ts
const instance2: Instance = {
  doSomething: ...,
  toNum: (): number => {
    return extremelyHelpfulUtilityFunction(1234)
  }
}

where you would use this.num instead of 1234 if you declared a proper class for Instance2 instead of this object, like

// Instance2.ts
class Instance2 extends Instance {
  num = 1234;
  doSomething: ...
  toNum(): number {
    return extremelyHelpfulUtilityFunction(this.num)
  }
}
export default new Instance2();
JulienD
  • 7,102
  • 9
  • 50
  • 84
  • Thank you, I was thinking the same thing, but I was hoping there was a way I wouldn't have to call `extremelyHelpfulUtilityFunction` in each implementation. I guess this is the expressivity problem. Anyway, I think this solves my problem. I'm going to attempt it before I mark this answer as accepted though – Daniel Kaplan Jan 02 '21 at 00:03