Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
451 views
in Technique[技术] by (71.8m points)

dependency injection - Dagger @Reusable scope vs @Singleton

From the User's Guide:

Sometimes you want to limit the number of times an @Inject-constructed class is instantiated or a @Provides method is called, but you don’t need to guarantee that the exact same instance is used during the lifetime of any particular component or subcomponent.

Why would I use that instead of @Singleton?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Use @Singleton if you rely on singleton behavior and guarantees. Use @Reusable if an object would only be a @Singleton for performance reasons.


@Reusable bindings have much more in common with unscoped bindings than @Singleton bindings: You're telling Dagger that you'd be fine creating a brand-new object, but if there's a convenient object already created then Dagger may use that one. In contrast, @Singleton objects guarantee that you will always receive the same instance, which can be much more expensive to enforce.

In general, Dagger and DI prefer unscoped objects: Creating a new object is a great way to keep state tightly-contained, and allows for objects to be garbage-collected as soon as the dependent object can. Dagger shows some of this preference built-in: In Dagger unscoped objects can be mixed in to any component or module, regardless of whether the component is scope-annotated. This type of unscoped binding is also useful for stateless objects like injectable (mockable) utility classes and implementations of strategy, command, and other polymorphic behavioral design patterns: The objects should be bound globally and injected for testing/overrides, but instances don't keep any state and short-lived or disposable.

However, in Android and other performance- and memory-constrained environments, it goes against performance recommendations to create a lot of temporary objects, because instance creation and garbage collection are both more-expensive processes than on desktop VMs. This leads to the pragmatic solution of marking an object @Singleton, not because it's important to always get the same instance, but just to conserve instances. This works, but is semantically-weak, and also has memory and speed implications: Your short-lived util or strategy pattern object now has to exist as long as your application exists, and must be accessed through double-checked locking, or else you risk violating the "one instance only" @Singleton guarantee that is unnecessary here. This can be a source of increased memory usage and synchronization overhead.

The compromise is in @Reusable bindings, which have instance-conserving properties like @Singleton but are excepted from the scope-matching @Component rule just like unscoped bindings—which gives you more flexibility about where you install them. (See tests.) They have a lifespan only as long as the outermost component that uses them directly, and will opportunistically use an instance from an ancestor to conserve further, but without double-checked locking to save on creation costs. (Consequently, several instances of a @Reusable object may exist simultaneously in your object graph, particularly if they were requested on multiple threads at the same time.) Finally, and most importantly, they're a signal to you and future developers about the way the class is intended to be used.

Though the cost is lower, it's not zero: As Ron Shapiro notes on Medium, "Reusable has many of the same costs as Singleton. It saves synchronization, but it still forces extra classes to be loaded at app startup. The real suggestion here is to never scope unless you’ve profiled and you’ve seen a performance improvement by scoping." You'll have to evaluate the speed and memory effects yourself: @Reusable is another useful tool in the toolbox, but that doesn't mean it's always or obviously a good choice.

In short, @Singleton would work, but @Reusable has some distinct performance advantages if the whole point is performance instead of object lifecycle. Don't forget to measure performance before and after you mark an instance @Reusable, to make sure @Reusable is really a benefit for your use case.


Follow-up question from saiedmomen: "Just to be 100% clear things like okhttpclient, retrofit and gson should be declared @Reusable. right??"

Yes, in general I think it'd be good to declare stateless utilities and libraries as @Reusable. However, if they secretly keep some state—like handling connection limits or batching across all consumers—you might want to make them @Singleton, and if they are used very infrequently from a long-lived component it might still make sense to make them scopeless so they can be garbage-collected. It's really hard to make a general statement here that works for all cases and libraries: You'll have to decide based on library functionality, memory weight, instantiation cost, and expected lifespan of the objects involved.

OkHttpClient in particular does manage its own connection and thread pools per instance, as Wyko points out in the comments, and Albert Vila Calvo likewise notes Retrofit's intended-singleton behavior. That would make those good candidates for @Singleton over @Reusable. Thanks Wyko and Albert!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...