This article compares four approaches to handling dependencies in Object-Oriented Programming: constructor injection, parameter passing, ThreadLocal, and Kotlin’s new context parameters. Each technique is analyzed for testability, coupling, and readability. While constructor injection remains the standard, Kotlin’s evolving language features, particularly context parameters, introduce an elegant, implicit alternative for dependency management in modern OOP design.This article compares four approaches to handling dependencies in Object-Oriented Programming: constructor injection, parameter passing, ThreadLocal, and Kotlin’s new context parameters. Each technique is analyzed for testability, coupling, and readability. While constructor injection remains the standard, Kotlin’s evolving language features, particularly context parameters, introduce an elegant, implicit alternative for dependency management in modern OOP design.

Understanding Dependency Injection in Object-Oriented Programming

In Object-Oriented Programming, objects collaborate. The initial idea of collaboration, first found in Smalltalk, was for object A to send a message to object B. Languages designed later use method calling. In both cases, the same question stands: how does an object reference other objects to reach the desired results?

In this post, I tackle the problem of passing dependencies to an object. I will go through several options and analyze their respective pros and cons.

Constructor injection

For constructor injection, you pass dependencies as parameters to the constructor.

class Delivery(private val addressService: AddressService,                private val geoService: GeoService,                private val zoneId: ZoneId) {      fun computeDeliveryTime(user: User, warehouseLocation: Location): ZonedDateTime {         val address = addressService.getAddressOf(user)         val coordinates = geoService.getCoordinates(location)         // return date time     } } 

Constructor injection is by far the most widespread way to pass to an object its dependencies: for about ten years, every codebase I've seen has constructor injection.

I've a slight issue with constructor injection: it stores dependencies as fields, just like state. Looking at the constructor's signature, it's impossible to distinguish between the state and dependencies without proper typing.

It bugs me. Let's see other ways.

Parameter passing

Instead of storing the dependencies along with the state, we can pass the dependency when calling the method.

class Delivery(private val zoneId: ZoneId) {      fun computeDeliveryTime(addressService: AddressService,                             geoService: GeoService,                             user: User, warehouseLocation: Location): ZonedDateTime {         val address = addressService.getAddressOf(user)         val coordinates = geoService.getCoordinates(location)         // return date time     } } 

The separation of state and dependencies is now clear: the former is stored in fields, while the latter is passed as function parameters. However, the responsibility of handling the dependency is moved one level up the call chain. The longer the call chain, the more unwieldy it gets.

class Order() {      fun deliver(delivery: Delivery, user: User, warehouseLocation: Location): OrderDetails {         // Somehow get the address and the geo services         val deliveryTime = delivery.computeDeliveryTime(addressService, geoService, user, warehouseLocation)         // return order details     } } 

Note that the call chain length is also a problem with constructor injection. You need to design the code for the call site to be as close as possible to the dependency creation one.

ThreadLocal

Legacy design makes use of the ThreadLocal:

\

\ We can rewrite the above code using ThreadLocal:

class Delivery(private val zoneId: ZoneId) {      fun computeDeliveryTime(user: User, warehouseLocation: Location): ZonedDateTime {         val addressService = AddressService.get()         val geoService = GeoService.get()         // return date time     } } 

\ The ThreadLocal can be either set up in the call chain or lazily, on first access. Regardless, the biggest disadvantage of this approach is that it completely hides the dependency. There's no way to understand the coupling by only looking at the class constructor or the function signature; one needs to read the function's source code.

Additionally, the implementation could be a regular singleton pattern, with the same downsides.

Kotlin context

The last approach is Kotlin-specific and has just been promoted from experimental to beta in Kotlin 2.2.

Here's how we can migrate the above code to context parameters:

class Delivery(private val zoneId: ZoneId) {      context(addressService: AddressService, geoService: GeoService)     fun computeDeliveryTime(user: User, warehouseLocation: Location): ZonedDateTime {         // return date time     } } 

And here's how to call it:

context(addressService,geoService) {     delivery.computeDeliveryTime(user, location) } 

Note that the call can be nested at any level inside the context.

Summary

| Approach | Pros | Cons | |----|----|----| | Constructor injection | Testable | Mix state and dependencies | | Parameter passing | Testable | Noisy | | ThreadLocal | | Hides coupling | | Context parameter | Get dependencies on deeply-nested | Limited to Kotlin |

I guess I'll continue to use constructor injection, unless I'm coding in Kotlin. In this case, I'll be happy to use context parameters, even though they are in beta.


Originally published at A Java Geek on October 12th, 2025

Disclaimer: The articles reposted on this site are sourced from public platforms and are provided for informational purposes only. They do not necessarily reflect the views of MEXC. All rights remain with the original authors. If you believe any content infringes on third-party rights, please contact [email protected] for removal. MEXC makes no guarantees regarding the accuracy, completeness, or timeliness of the content and is not responsible for any actions taken based on the information provided. The content does not constitute financial, legal, or other professional advice, nor should it be considered a recommendation or endorsement by MEXC.

You May Also Like

When is the flash US S&P Global PMI data and how could it affect EUR/USD?

When is the flash US S&P Global PMI data and how could it affect EUR/USD?

The post When is the flash US S&P Global PMI data and how could it affect EUR/USD? appeared on BitcoinEthereumNews.com. US flash PMI Overview The preliminary United
Share
BitcoinEthereumNews2026/01/23 20:54
BetFury is at SBC Summit Lisbon 2025: Affiliate Growth in Focus

BetFury is at SBC Summit Lisbon 2025: Affiliate Growth in Focus

The post BetFury is at SBC Summit Lisbon 2025: Affiliate Growth in Focus appeared on BitcoinEthereumNews.com. Press Releases are sponsored content and not a part of Finbold’s editorial content. For a full disclaimer, please . Crypto assets/products can be highly risky. Never invest unless you’re prepared to lose all the money you invest. Curacao, Curacao, September 17th, 2025, Chainwire BetFury steps onto the stage of SBC Summit Lisbon 2025 — one of the key gatherings in the iGaming calendar. From 16 to 18 September, the platform showcases its brand strength, deepens affiliate connections, and outlines its plans for global expansion. BetFury continues to play a role in the evolving crypto and iGaming partnership landscape. BetFury’s Participation at SBC Summit The SBC Summit gathers over 25,000 delegates, including 6,000+ affiliates — the largest concentration of affiliate professionals in iGaming. For BetFury, this isn’t just visibility, it’s a strategic chance to present its Affiliate Program to the right audience. Face-to-face meetings, dedicated networking zones, and affiliate-focused sessions make Lisbon the ideal ground to build new partnerships and strengthen existing ones. BetFury Meets Affiliate Leaders at its Massive Stand BetFury arrives at the summit with a massive stand placed right in the center of the Affiliate zone. Designed as a true meeting hub, the stand combines large LED screens, a sleek interior, and the best coffee at the event — but its core mission goes far beyond style. Here, BetFury’s team welcomes partners and affiliates to discuss tailored collaborations, explore growth opportunities across multiple GEOs, and expand its global Affiliate Program. To make the experience even more engaging, the stand also hosts: Affiliate Lottery — a branded drum filled with exclusive offers and personalized deals for affiliates. Merch Kits — premium giveaways to boost brand recognition and leave visitors with a lasting conference memory. Besides, at SBC Summit Lisbon, attendees have a chance to meet the BetFury team along…
Share
BitcoinEthereumNews2025/09/18 01:20
Wizkid & Asake’s ‘Jogodo’ becomes fastest African song to surpass 10 million streams on Spotify

Wizkid & Asake’s ‘Jogodo’ becomes fastest African song to surpass 10 million streams on Spotify

Wizkid and Asake have set a new record with their latest collaboration, “Jogodo,” which crossed 10 million Spotify… The post Wizkid & Asake’s ‘Jogodo’ becomes fastest
Share
Technext2026/01/23 21:27