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?
1 Answer 1
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.
Explore related questions
See similar questions with these tags.
AuthService::getInstance
to be non-static? At the moment you need anAuthService
to get anAuthService