I've been reading a lot about the singleton pattern and how it's "bad" because it makes the classes using it hard to test so it should be avoided. I've read some articles explaining how the singleton could be replaced with dependency injection, but it seems unnecessarily complex to me.
Here's my problem in a bit more detail. I'm building a mobile app using React Native and I want to make a REST client that will communicate with the server, get data, post data and handle the login (store the login token and send it with each request after login).
My initial plan was to create a singleton object (RESTClient) that my app will use initially to login and then make request sending the credentials where needed. The DI approach seems really complicated to me (maybe because I never used DI before) but I'm using this project to learn as much as can so I want to do what's best here. Any suggestions and comments are much appreciated.
Edit:
I've now realized I've worded my question poorly. I wanted some guidance on how to avoid the singleton pattern in RN and should I even do it. Luckily Samuel gave me just the kind of answer I wanted. My problem was I wanted to avoid the singleton pattern and use DI, but it seemed really complicated to implement it in React Native. I did some further research and implemented it using Reacts context system.
For anyone interested here's how I did it. Like I said, I used the context in RN which is something like props but it's propagated down to every component.
In the root component I provide the needed dependencies like this:
export default class Root extends Component {
getChildContext() {
restClient: new MyRestClient();
}
render() {...}
}
Root.childContextTypes = {restClient: PropTypes.object};
Now restClient is available in all components below Root. I can access it like this.
export default class Child extends Component {
useRestClient() {
this.context.restClient.getData(...);
}
render() {...}
}
Child.contextTypes = {restClient: PropTypes.object}
This effectively moves object creation away from logic and decouples the REST client implementation from my components.
Best Answer
Dependency injection does not need to be complex at all, and it's absolutely worth learning and using. Usually it's complicated by the usage of dependency injection frameworks, but they aren't necessary.
In its simplest form, dependency injection is passing dependencies instead of importing or constructing them. This can be implimented by simply using a parameter for what would be imported. Lets say you have a component named
MyList
that needs to useRESTClient
to fetch some data and display it to the user. The "singleton" approach would look something like this:This tightly-couples
MyList
torestClient
, and there is no way you can unit testMyList
without testingrestClient
. The DI approach would look something like this:That's all it takes to use DI. It adds at most two lines of code and you get to eliminate an import. The reason why I introduced a new function "factory" is because AFAIK you can't pass additional constructor parameters in React, and I prefer not to pass these things down through React properties because it's not portable and all parent components must know to pass all props to children.
So now you have a function to construct
MyList
components, but how do you use it? The DI pattern bubbles up the dependency chain. Say you have a componentMyApp
that usesMyList
. The "singleton" approach would be:The DI approach is:
Now we can test
MyApp
without testingMyList
directly. We could even reuseMyApp
with a completely different kind of list. This pattern bubbles up to the composition root. This is where you call your factories and wire all the components.Now our system uses a single instance of
RESTClient
, but we've designed it in a way that components are loosely-coupled and easy to test.