8

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.

  • 3
    Consider changing the title of the question. Right now REST and client are irrelevant elements . The only question here is: Is singleton the best alternative to DI in my specific situation?. – Laiv Jul 13 '17 at 20:00
  • I don't know much about react, but in general DI is a simple concept. If you're finding it complex I suspect you haven't read/heard a good explanation. – Ben Aaronson Jul 14 '17 at 15:45
15

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 use RESTClient to fetch some data and display it to the user. The "singleton" approach would look something like this:

import restClient from '...' // import singleton

class MyList extends React.Component {
  // use restClient and render stuff
}

This tightly-couples MyList to restClient, and there is no way you can unit test MyList without testing restClient. The DI approach would look something like this:

function MyListFactory(restClient) {
  class MyList extends React.Component {
    // use restClient and render stuff
  }

  return MyList
}

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 component MyApp that uses MyList. The "singleton" approach would be:

import MyList from '...'
class MyApp extends React.Component {
  render() {
    return <MyList />
  }
}

The DI approach is:

function MyAppFactory(ListComponent) {
  class MyApp extends React.Component {
    render() {
      return <ListComponent />
    }
  }

  return MyApp
}

Now we can test MyApp without testing MyList directly. We could even reuse MyApp 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.

import RESTClient from '...'
import MyListFactory from '...'
import MyAppFactory from '...'

const restClient = new RESTClient(...)
const MyList = MyListFactory(restClient)
const MyApp = MyAppFactory(MyList)

ReactDOM.render(<MyApp />, document.getElementById('app'))

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.

  • If you don't mind I would like to make reference to your answer from mine, because I think It illustrate very well the points I have only mentioned superficially. – Laiv Jul 13 '17 at 20:54
  • 2
    @Laiv, I think the term "poor man's DI" is falling out of fashion, in favour of "pure DI". Though not intended, the former sounds too negative as it implies IoC containers are "richer". – David Arno Jul 13 '17 at 21:36
  • This. I thought Dependency Injection was extremely complicated when i first saw it, but after a little time it is extremely easy to grasp. And @Samuel explained it well! – Rhys Johns Jul 14 '17 at 0:21
  • 1
    @Samuel So to properly apply DI in React I would need to create factories for every component? Also in your example, shouldn't MyAppFactory return the MyApp class? – Mateo Hrastnik Jul 14 '17 at 13:23
  • @MattHammond Simple components that don't have any dependencies don't need factories. And this is just the way I've done it, there may be other approaches to DI in React that are better, but I was aiming for simplicity here. Thanks for the correction; I've fixed it now. – Samuel Jul 15 '17 at 0:58
5

According to your premises (learning), the simplest answer is no, singletons are not the best alternative to Dependency Injection.

If learning is the goal, you will find DI to be a more valuable resource in your toolbox than Singleton. It might seem complex, but the learning curve is almost on everything you have to learn from scratch. Don't lay on your comfort zone or there won't be learning at all.

Technically, there's little difference between singleton and single instance (what I think you are trying to do). Learning DI you will realise that you can inject single instances all over the code. You will find your code to be easier to test and loosely coupled. (For more details check out Samuel's answer)

But don't you stop here. Do implement the same code with Singleton. Then compare both approaches.

Understanding and to be familiar with both implementations will allow you to know when they are appropriated and probably you will be in a position to answer yourself the question.

It's now, during the training, when you forge your Golden Hammers, so if you decide to avoid learning DI it's likely probable that you will end up implementing singletons everytime you need DI.

0

I agree with the other answers that learning what DI is and how to use it is a good idea.

That said, the warning that singletons make testing too hard is usually made by people using statically typed languages (C++, C#, Java, etc.).
In contrast in a dynamic language (Javascript, PHP, Python, Ruby, etc.), it is usually hardly harder to replace a singleton with a test-specific implementation than it would be in the case when you are using DI.

In that case, I recommend using the design that feels more natural to you and your co-developers, because that will tend to avoid mistakes. If that results in singletons, so be it.

(But then again: Learn DI before you make that decision.)

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.