1

I'm working on an app where we need to use different authentication flows depending on how the user is accessing the app. I want to make sure that there is only one instance of the authentication service at a given time (singleton).

There aren't that many auth services types and I don't expect there to be more than 3-4 types in the future.

Here are the two approaches that I can think of:

Approach A (Factory pattern):

export class AuthFactory {
  public instance

  public getInstance(): AuthService {
    if (this.instance) {
      return this.instance
    } else {
      this.instance = fooConditon ? new authServiceA() : new authServiceB() 
      return this.instance
    }
  }
}

export abstract class AuthService {
  ...
}

export class AuthServiceA implements AuthService {
  ...
}

export class AuthServiceB implements AuthService {
  ...
}

Approach B

Completely skip the factory and have a getInstance() method inside the abstract class

export abstract class AuthService {
  public instance

  public getInstance(): AuthService {
    if (this.instance) {
      return this.instance
    } else {
      this.instance = fooConditon ? new authServiceA() : new authServiceB() 
      return this.instance
    }
  }
  ...
}

export class AuthServiceA implements AuthService {
  ...
}

export class AuthServiceB implements AuthService {
  ...
}

Which approach is better and why? For me, I feel like having the factory is overkill because there are very few sub classes of AuthService but I wonder if I'm overlooking something.

I was also wondering, if the factory approach is the way to go, is creating a singleton factory (one that only creates one instance of the product sub classes and not one of each product subclass) common? Is there a better way to do it?

  • Did you mean for AuthService::getInstance to be non-static? At the moment you need an AuthService to get an AuthService – Caleth Jun 23 at 8:57
3

As often, none of these different design alternative is "better" in general. You need to figure out which of them is the most simple one which fullfills all of your system's requirements.

Approach B is fine in case

  • AuthService, AuthServiceA, AuthServiceB live all in the same library / component /assembly and,

  • it does not matter for you that there is a cyclic dependency between classes AuthService and its subclasses.

For small components and no requirements of distributing the development of these components between different people or teams, this approach may be fully sufficient. Note, if you need a "mock" AuthService for testing purposes, you will have to put it also into this one component, you cannot really separate this part of the testing code from the production code.

Approach A will allow you

  • to put AuthService in one component (maybe a reusable blackbox library), AuthServiceA in a second, AuthServiceB in a third, AuthServiceMock into a unit testing library and the factory in a fifth which references the former ones.

  • It will allow you to add further authentication mechanisms at a later point in time without changing existing code based on the abstraction of an AuthService (Open-Closed principle).

  • It will also allow to distribute the responsibility of development on all of these components to different people or teams.

As you already noted, this flexibility comes for the cost of being more complex.

If I were in your shoes, I would start with approach B and refactor to approach A as soon as it becomes apparent that B will not be sufficient any more.

| improve this answer | |
  • Very thorough answer, thank you so much! – YSA Jun 22 at 19:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.