Get startedGet started for free

Lazy initialization and singleton patterns

1. Lazy initialization and singleton patterns

Welcome back, everyone! Now we'll discuss Lazy Initialization and Singleton Patterns, exploring how these patterns can significantly improve our application's performance when managing expensive resources.

2. Building a Redis cache client

In the previous video, we described how caching could improve the performance of our application, and we briefly mentioned Redis as a cache example. Imagine we're building a Java application that connects to a Redis cache to store expensive computation results. As this cache is hosted using a third-party provider, creating this connection involves network overhead - for example, it could take 500ms to get an initial connection to Redis.

3. A simple (eager) implementation

Here's a typical approach. Our `RedisCache` class has a property, `client`, that holds the connection to our Redis instance. When the class is initialized, we set up our Redis connection by calling the `RedisClient.connect()` method in the class constructor. This is a network call, and depending on our network connection and the availability of our Redis host, it could take significant time. We call this approach an eager initialization of our Redis connection - meaning we set it up as soon as possible.

4. A potential issue with this approach

We might ask, what's the problem with our previous approach? We will need this Redis client, so what's the problem with setting it up as soon as possible? In many cases, that might not be an issue. But imagine a use case where our Redis client might or might not be used, and we don't know beforehand which one it will be. If the Redis client was never used, we would have wasted precious time and set up unnecessary connections for something that was not needed.

5. Lazy initialization

An approach that would solve this problem would be lazy initialization. Here, when our class is initialized, we don't make any network calls - yet. Then, only when our Redis cache is actually needed, we make network calls as part of the `getClient` method.

6. Another issue with our approach

Nice! We improved our startup times and reduced unnecessary connections. However, our current approach still has some issues. Imagine we have multiple places in our code where we need to access our cache: `UserService`, `PaymentService`, and `OrderService`. As we can see, every time one of the above services uses our cache, it has to create a new Redis client, which, in turn, creates a separate network connection, which also uses redundant memory!

7. The singleton pattern

The solution to our problem is the singleton pattern - a design pattern that restricts the instantiation of a class to a single instance. The singleton pattern involves a private instance variable of the class itself, a private constructor, and a public method that returns the single instance of the class. Like so, parts outside our class cannot create a new instance of the singleton class, as the constructor is private; they are forced to call `getInstance()` instead, which will create the single instance. In our `RedisCache`, we implement this pattern with a static instance that stores our singleton, a private constructor, and a public `getInstance()` method. External services can access Redis by calling `getInstance()`, which either creates a new `RedisCache` instance if none exists or returns the existing one.

8. Let's practice!

That's all for now! In this lesson, we covered the eager and lazy initialization patterns and the singleton pattern. Now, let's practice!