kotlin-context-di
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSTARTER_CHARACTER = 🔌
STARTER_CHARACTER = 🔌
Manual Dependency Injection with SystemContext and TestContext
基于SystemContext和TestContext的手动依赖注入
Structure Kotlin applications using manual DI with SystemContext (production) and TestContext (test) patterns. This approach provides type-safe dependency management, full control over initialization, and excellent testability without framework overhead.
使用SystemContext(生产环境)和TestContext(测试环境)模式的手动DI来构建Kotlin应用。这种方式无需框架开销,可提供类型安全的依赖管理、对初始化过程的完全控制,以及出色的可测试性。
Core Pattern: SystemContext
核心模式:SystemContext
Create an open class that holds all application dependencies. Use interfaces to group related components, and implement them as anonymous objects inside the context.
kotlin
open class SystemContext {
interface Repositories {
val customerRepo: CustomerRepository
val orderRepo: OrderRepository
}
open val repositories: Repositories by lazy {
object : Repositories {
override val customerRepo by lazy { CustomerRepositoryImpl(dataSource) }
override val orderRepo by lazy { OrderRepositoryImpl(dataSource) }
}
}
open val customerService by lazy {
CustomerService(repositories.customerRepo)
}
open val orderService by lazy {
OrderService(repositories.orderRepo, customerService)
}
}Key characteristics:
- Group related dependencies using interfaces (not open classes — see rationale below)
- Implement production wiring as anonymous objects inside the context
- Services reference repositories and other services directly
- Use for initialization that depends on other context properties or is expensive
lazy - Use direct instantiation when initialization is trivial and has no dependencies
Why interfaces instead of open classes:
- Open classes with constructor parameters force test subclasses to satisfy those parameters, even when test fakes never use them (e.g., creating a dummy just to satisfy a constructor)
DataSource - Open classes carry implicit coupling: test subclasses inherit production defaults, which can mask test setup errors
- Interfaces have no constructors and no inherited behavior — test implementations must explicitly provide every dependency
Why anonymous objects for production wiring:
- Production wiring stays in one place ()
SystemContext - Implementation details are private to the context
- The anonymous object naturally captures and other context properties from the enclosing scope
dataSource - No need for constructor parameters on the grouping interface
创建一个开放类来存储所有应用依赖。使用接口对相关组件进行分组,并在上下文内部以匿名对象的形式实现这些接口。
kotlin
open class SystemContext {
interface Repositories {
val customerRepo: CustomerRepository
val orderRepo: OrderRepository
}
open val repositories: Repositories by lazy {
object : Repositories {
override val customerRepo by lazy { CustomerRepositoryImpl(dataSource) }
override val orderRepo by lazy { OrderRepositoryImpl(dataSource) }
}
}
open val customerService by lazy {
CustomerService(repositories.customerRepo)
}
open val orderService by lazy {
OrderService(repositories.orderRepo, customerService)
}
}核心特性:
- 使用接口对相关依赖进行分组(而非开放类——详见下文原理)
- 在上下文内部以匿名对象的形式实现生产环境的依赖连接
- 服务直接引用仓库及其他服务
- 对于依赖其他上下文属性或初始化成本较高的组件,使用初始化
lazy - 当初始化过程简单且无依赖时,使用直接实例化
为何使用接口而非开放类:
- 带构造参数的开放类会强制测试子类满足这些参数,即便测试替身永远不会用到它们(例如,仅为了满足构造函数而创建一个虚拟的)
DataSource - 开放类会带来隐式耦合:测试子类会继承生产环境的默认实现,这可能掩盖测试设置中的错误
- 接口没有构造函数,也没有继承行为——测试实现必须显式提供所有依赖
为何使用匿名对象实现生产环境依赖连接:
- 生产环境的依赖连接逻辑集中在一处()
SystemContext - 实现细节对上下文外部不可见
- 匿名对象可自然捕获封闭作用域中的及其他上下文属性
dataSource - 分组接口无需构造参数
Test Pattern: TestContext with Typed Test Implementations
测试模式:带类型化测试实现的TestContext
Extend SystemContext and override dependency groups with typed test implementations. Use concrete types in test implementations to avoid casting.
kotlin
class SystemTestContext : SystemContext() {
class TestRepositories : Repositories {
override val customerRepo = CustomerRepositoryFake() // concrete type!
override val orderRepo = OrderRepositoryFake() // concrete type!
}
val testRepositories = TestRepositories()
override val repositories: Repositories get() = testRepositories
}This exploits Kotlin's covariant return types: has type (concrete), while still satisfying the interface contract (abstract).
TestRepositories.customerRepoCustomerRepositoryFakeCustomerRepositoryTwo access paths exist in tests:
- — typed as
repositories.customerRepo(used by production code)CustomerRepository - — typed as
testRepositories.customerRepo(for test assertions and setup)CustomerRepositoryFake
In tests — create a fresh context per test:
kotlin
@Test
fun testOrderCreation() {
with(SystemTestContext()) {
// Arrange - use service methods to set up state
customerService.registerCustomer(Customer.valid())
// Act
val order = orderService.createOrder(customerId, items)
// Assert — direct access to fake methods, no casting needed
assertThat(testRepositories.orderRepo.getSavedOrders())
.contains(order)
}
}The pattern creates a fresh context per test, provides clean access to all services and repositories, and prevents state leakage between tests.
with(SystemTestContext()) { ... }继承SystemContext并使用类型化测试实现覆盖依赖分组。在测试实现中使用具体类型以避免类型转换。
kotlin
class SystemTestContext : SystemContext() {
class TestRepositories : Repositories {
override val customerRepo = CustomerRepositoryFake() // concrete type!
override val orderRepo = OrderRepositoryFake() // concrete type!
}
val testRepositories = TestRepositories()
override val repositories: Repositories get() = testRepositories
}这利用了Kotlin的协变返回类型特性:的类型是具体的,但仍能满足抽象的接口契约。
TestRepositories.customerRepoCustomerRepositoryFakeCustomerRepository测试中存在两种访问路径:
- — 类型为
repositories.customerRepo(供生产代码使用)CustomerRepository - — 类型为
testRepositories.customerRepo(用于测试断言和设置)CustomerRepositoryFake
测试中——为每个测试创建全新上下文:
kotlin
@Test
fun testOrderCreation() {
with(SystemTestContext()) {
// 准备阶段 - 使用服务方法设置状态
customerService.registerCustomer(Customer.valid())
// 执行操作
val order = orderService.createOrder(customerId, items)
// 断言 — 直接访问替身方法,无需类型转换
assertThat(testRepositories.orderRepo.getSavedOrders())
.contains(order)
}
}with(SystemTestContext()) { ... }Type Safety Benefits
类型安全优势
Compile-time checking:
- Typos caught immediately
- Refactoring tools work perfectly (rename, move, find usages)
- Missing dependencies fail at compile time, not runtime
IDE support:
- Full autocomplete for all dependencies
- Jump to definition works seamlessly
- No string-based lookups or reflection
Clear dependency graph:
- Constructor parameters show exact dependencies
- Easy to trace where any component is used
- No hidden framework magic
编译时检查:
- 拼写错误可立即被捕获
- 重构工具可完美工作(重命名、移动、查找引用)
- 缺失的依赖会在编译阶段报错,而非运行时
IDE支持:
- 所有依赖均支持自动补全
- 跳转至定义功能无缝工作
- 无基于字符串的查找或反射操作
清晰的依赖关系图:
- 构造函数参数展示了精确的依赖项
- 可轻松追踪任意组件的使用位置
- 无隐藏的框架魔法
Initialization Control
初始化控制
Direct instantiation (default):
kotlin
open val customerService = CustomerService(repositories.customerRepo)Use when initialization is cheap and there are no circular dependencies.
Lazy initialization:
kotlin
open val customerService by lazy {
CustomerService(repositories.customerRepo)
}Use when:
- Circular dependencies exist (A needs B, B needs A)
- Initialization is expensive (database connections, HTTP clients)
- Component may not be used in all test scenarios
- The value depends on other context properties that may be overridden
When in doubt, start with direct instantiation. Add only when needed.
lazy直接实例化(默认方式):
kotlin
open val customerService = CustomerService(repositories.customerRepo)适用于初始化成本低且无循环依赖的场景。
延迟初始化:
kotlin
open val customerService by lazy {
CustomerService(repositories.customerRepo)
}适用于以下场景:
- 存在循环依赖(A依赖B,B依赖A)
- 初始化成本高(数据库连接、HTTP客户端)
- 组件可能不会在所有测试场景中被使用
- 组件值依赖可能被覆盖的其他上下文属性
若不确定,优先使用直接实例化。仅在必要时添加。
lazyGrouping Dependencies
依赖分组
Organize dependencies by layer or concern using interfaces:
kotlin
open class SystemContext(private val config: Config) {
// Data layer
interface Repositories {
val userRepo: UserRepository
val productRepo: ProductRepository
}
// External services
interface Clients {
val paymentClient: PaymentClient
val emailClient: EmailClient
}
// Infrastructure
interface Infrastructure {
val database: Database
val cache: Cache
}
open val infrastructure: Infrastructure by lazy {
object : Infrastructure {
override val database by lazy { DatabaseImpl(config.dbUrl) }
override val cache by lazy { RedisCache(config.redisUrl) }
}
}
open val repositories: Repositories by lazy {
object : Repositories {
override val userRepo by lazy { UserRepositoryImpl(infrastructure.database) }
override val productRepo by lazy { ProductRepositoryImpl(infrastructure.database) }
}
}
open val clients: Clients by lazy {
object : Clients {
override val paymentClient by lazy { PaymentClientImpl(config.paymentApiKey) }
override val emailClient by lazy { EmailClientImpl(config.smtpConfig) }
}
}
// Business logic
open val userService by lazy { UserService(repositories.userRepo) }
open val orderService by lazy {
OrderService(
repositories.productRepo,
clients.paymentClient,
clients.emailClient
)
}
}This structure:
- Makes test overriding straightforward (override entire groups)
- Clarifies architectural layers
- Keeps production wiring in one place
- Anonymous objects capture config and other context properties from the enclosing scope
使用接口按层级或关注点组织依赖:
kotlin
open class SystemContext(private val config: Config) {
// 数据层
interface Repositories {
val userRepo: UserRepository
val productRepo: ProductRepository
}
// 外部服务
interface Clients {
val paymentClient: PaymentClient
val emailClient: EmailClient
}
// 基础设施
interface Infrastructure {
val database: Database
val cache: Cache
}
open val infrastructure: Infrastructure by lazy {
object : Infrastructure {
override val database by lazy { DatabaseImpl(config.dbUrl) }
override val cache by lazy { RedisCache(config.redisUrl) }
}
}
open val repositories: Repositories by lazy {
object : Repositories {
override val userRepo by lazy { UserRepositoryImpl(infrastructure.database) }
override val productRepo by lazy { ProductRepositoryImpl(infrastructure.database) }
}
}
open val clients: Clients by lazy {
object : Clients {
override val paymentClient by lazy { PaymentClientImpl(config.paymentApiKey) }
override val emailClient by lazy { EmailClientImpl(config.smtpConfig) }
}
}
// 业务逻辑
open val userService by lazy { UserService(repositories.userRepo) }
open val orderService by lazy {
OrderService(
repositories.productRepo,
clients.paymentClient,
clients.emailClient
)
}
}这种结构:
- 使测试覆盖变得简单(可覆盖整个分组)
- 明确了架构层级
- 生产环境的依赖连接逻辑集中在一处
- 匿名对象可捕获封闭作用域中的配置及其他上下文属性
Test Context: Full Example
TestContext完整示例
kotlin
class SystemTestContext : SystemContext(Config.test()) {
class TestRepositories : Repositories {
override val userRepo = UserRepositoryFake() // concrete type
override val productRepo = ProductRepositoryFake() // concrete type
}
class TestClients : Clients {
override val paymentClient = PaymentClientFake() // concrete type
override val emailClient = EmailClientFake() // concrete type
}
val testRepositories = TestRepositories()
override val repositories: Repositories get() = testRepositories
val testClients = TestClients()
override val clients: Clients get() = testClients
}In tests — no casting needed:
kotlin
@Test
fun testEmailSent() {
with(SystemTestContext()) {
orderService.completeOrder(orderId)
// Direct access to fake methods via testClients — no casting
assertThat(testClients.emailClient.sentEmails).hasSize(1)
assertThat(testClients.emailClient.sentEmails[0].subject)
.contains("Order Confirmed")
}
}
@Test
fun testPaymentFailure() {
with(SystemTestContext()) {
// Configure fake behavior — no casting
testClients.paymentClient.failOnNextCharge()
val result = orderService.createOrder(request)
assertThat(result.status).isEqualTo(OrderStatus.PAYMENT_FAILED)
}
}kotlin
class SystemTestContext : SystemContext(Config.test()) {
class TestRepositories : Repositories {
override val userRepo = UserRepositoryFake() // concrete type
override val productRepo = ProductRepositoryFake() // concrete type
}
class TestClients : Clients {
override val paymentClient = PaymentClientFake() // concrete type
override val emailClient = EmailClientFake() // concrete type
}
val testRepositories = TestRepositories()
override val repositories: Repositories get() = testRepositories
val testClients = TestClients()
override val clients: Clients get() = testClients
}测试中——无需类型转换:
kotlin
@Test
fun testEmailSent() {
with(SystemTestContext()) {
orderService.completeOrder(orderId)
// 通过testClients直接访问替身方法 — 无需类型转换
assertThat(testClients.emailClient.sentEmails).hasSize(1)
assertThat(testClients.emailClient.sentEmails[0].subject)
.contains("Order Confirmed")
}
}
@Test
fun testPaymentFailure() {
with(SystemTestContext()) {
// 配置替身行为 — 无需类型转换
testClients.paymentClient.failOnNextCharge()
val result = orderService.createOrder(request)
assertThat(result.status).isEqualTo(OrderStatus.PAYMENT_FAILED)
}
}Fresh Context Per Test
每个测试使用全新上下文
Create a fresh context per test when fakes are stateful (the common case):
kotlin
@Test
fun `should save order`() {
with(SystemTestContext()) {
// Fresh fakes with no accumulated state
orderService.createOrder(request)
assertThat(testRepositories.orderRepo.getSavedOrders()).hasSize(1)
}
}
@Test
fun `should not save order when payment fails`() {
with(SystemTestContext()) {
// Independent from the test above
testClients.paymentClient.failOnNextCharge()
orderService.createOrder(request)
assertThat(testRepositories.orderRepo.getSavedOrders()).isEmpty()
}
}Why: Fakes are stateful — accumulates saved orders, accumulates sent emails. Sharing a context across tests causes state from one test to leak into the next, leading to order-dependent failures and flaky tests.
OrderRepositoryFakeEmailClientFakeThe pattern is idiomatic, cheap (no real I/O), and prevents test pollution.
with(SystemTestContext()) { ... }Share a context only when fakes are truly stateless or when you have explicit reset logic — this is uncommon.
当替身有状态时(常见场景),为每个测试创建全新上下文:
kotlin
@Test
fun `should save order`() {
with(SystemTestContext()) {
// 无累积状态的全新替身
orderService.createOrder(request)
assertThat(testRepositories.orderRepo.getSavedOrders()).hasSize(1)
}
}
@Test
fun `should not save order when payment fails`() {
with(SystemTestContext()) {
// 与上一个测试完全独立
testClients.paymentClient.failOnNextCharge()
orderService.createOrder(request)
assertThat(testRepositories.orderRepo.getSavedOrders()).isEmpty()
}
}原因: 替身是有状态的——会累积已保存的订单,会累积已发送的邮件。在测试间共享上下文会导致一个测试的状态泄漏到下一个测试中,从而引发依赖测试顺序的失败和不稳定测试。
OrderRepositoryFakeEmailClientFakewith(SystemTestContext()) { ... }仅当替身真正无状态或有明确的重置逻辑时,才共享上下文——这种情况并不常见。
Nullable-to-Non-nullable Narrowing in Tests
测试中的可空类型转非空类型
When production interfaces have nullable dependencies (because configuration may be absent), test implementations can narrow them to non-nullable:
kotlin
// Production interface — nullable because config may not exist
interface Clients {
val authClient: AuthClient?
val notificationClient: NotificationClient?
}
// Test implementation — non-nullable
class TestClients : Clients {
override val authClient = AuthClientStub() // non-nullable!
override val notificationClient = NotificationClientStub() // non-nullable!
}This is valid Kotlin because non-nullable types are subtypes of nullable types. Tests never need null checks when accessing test clients, even though production code handles the nullable case. This is a significant ergonomic win — test code stays clean and focused on behavior.
当生产环境接口包含可空依赖(因为配置可能不存在)时,测试实现可将其缩小为非空类型:
kotlin
// 生产环境接口 — 可空,因为配置可能不存在
interface Clients {
val authClient: AuthClient?
val notificationClient: NotificationClient?
}
// 测试实现 — 非空
class TestClients : Clients {
override val authClient = AuthClientStub() // non-nullable!
override val notificationClient = NotificationClientStub() // non-nullable!
}这在Kotlin中是合法的,因为非空类型是可空类型的子类型。测试代码访问测试客户端时无需空检查,即便生产代码需要处理可空情况。这是一个显著的易用性提升——测试代码可保持简洁,专注于业务行为。
Integration with Test Doubles
与测试替身的集成
TestContext typically contains Fakes (in-memory implementations of interfaces):
kotlin
class CustomerRepositoryFake : CustomerRepository {
private val db = mutableMapOf<String, Customer>()
override fun save(customer: Customer) {
db[customer.id] = customer
}
override fun findById(id: String): Customer? {
return db[id]
}
// Test-specific methods (not in interface)
fun getSavedCustomers(): List<Customer> = db.values.toList()
fun failOnNextSave() { /* ... */ }
}The TestContext wires these Fakes and exposes them with concrete types:
kotlin
class SystemTestContext : SystemContext() {
class TestRepositories : Repositories {
override val customerRepo = CustomerRepositoryFake() // concrete type
}
val testRepositories = TestRepositories()
override val repositories: Repositories get() = testRepositories
}Now uses automatically because it references , and tests access fake-specific methods via without casting.
customerServiceCustomerRepositoryFakerepositories.customerRepotestRepositories.customerRepoTestContext通常包含Fakes(接口的内存实现):
kotlin
class CustomerRepositoryFake : CustomerRepository {
private val db = mutableMapOf<String, Customer>()
override fun save(customer: Customer) {
db[customer.id] = customer
}
override fun findById(id: String): Customer? {
return db[id]
}
// 测试专属方法(不在接口中)
fun getSavedCustomers(): List<Customer> = db.values.toList()
fun failOnNextSave() { /* ... */ }
}TestContext会连接这些Fakes并以具体类型暴露它们:
kotlin
class SystemTestContext : SystemContext() {
class TestRepositories : Repositories {
override val customerRepo = CustomerRepositoryFake() // concrete type
}
val testRepositories = TestRepositories()
override val repositories: Repositories get() = testRepositories
}现在会自动使用,因为它引用的是,而测试可通过直接访问替身的专属方法,无需类型转换。
customerServiceCustomerRepositoryFakerepositories.customerRepotestRepositories.customerRepoApplication Wiring
应用连接
Main entry point:
kotlin
fun main() {
val context = SystemContext(Config.fromEnvironment())
// Start application with context
val app = Application(
context.orderService,
context.userService
)
app.start()
}Web framework integration (Ktor example):
kotlin
fun Application.module() {
val context = SystemContext(Config.fromEnvironment())
routing {
get("/orders/{id}") {
val orderId = call.parameters["id"]!!
val order = context.orderService.getOrder(orderId)
call.respond(order)
}
post("/orders") {
val request = call.receive<CreateOrderRequest>()
val order = context.orderService.createOrder(request)
call.respond(order)
}
}
}Routes access services directly from the context. No framework-specific annotations or registrations needed.
主入口:
kotlin
fun main() {
val context = SystemContext(Config.fromEnvironment())
// 使用上下文启动应用
val app = Application(
context.orderService,
context.userService
)
app.start()
}Web框架集成(Ktor示例):
kotlin
fun Application.module() {
val context = SystemContext(Config.fromEnvironment())
routing {
get("/orders/{id}") {
val orderId = call.parameters["id"]!!
val order = context.orderService.getOrder(orderId)
call.respond(order)
}
post("/orders") {
val request = call.receive<CreateOrderRequest>()
val order = context.orderService.createOrder(request)
call.respond(order)
}
}
}路由可直接从上下文中访问服务。无需框架专属注解或注册操作。
Why This Pattern Works
该模式为何有效
Simplicity:
- No annotations to learn
- No configuration files
- No classpath scanning or reflection
- Plain Kotlin code
Debuggability:
- Step through initialization in debugger
- Set breakpoints in context creation
- No framework magic hiding behavior
Readability:
- Dependencies visible in one place
- Constructor calls show exactly what's needed
- No surprising behavior from framework lifecycle
Test control:
- Full control over what gets loaded
- Fast test startup (only load what you need)
- Easy to inject test doubles
- No special test runners or annotations
- No casting needed to access test-specific methods
Flexibility:
- Change initialization order easily
- Add conditional logic (feature flags, environment checks)
- Compose contexts (production + feature flags)
Scalability:
- Pattern stays simple as project grows
- More dependencies just mean more properties in context classes
- No framework limitations or architectural constraints
简洁性:
- 无需学习注解
- 无需配置文件
- 无需类路径扫描或反射
- 纯Kotlin代码实现
可调试性:
- 可在调试器中单步执行初始化过程
- 可在上下文创建时设置断点
- 无隐藏的框架魔法
可读性:
- 所有依赖集中可见
- 构造函数调用清晰展示所需依赖
- 无框架生命周期带来的意外行为
测试可控性:
- 完全控制加载内容
- 测试启动速度快(仅加载所需内容)
- 轻松注入测试替身
- 无需特殊测试运行器或注解
- 无需类型转换即可访问测试专属方法
灵活性:
- 可轻松修改初始化顺序
- 可添加条件逻辑(功能开关、环境检查)
- 可组合上下文(生产环境+功能开关)
可扩展性:
- 随着项目增长,模式仍保持简洁
- 新增依赖仅需在上下文类中添加更多属性
- 无框架限制或架构约束
Anti-patterns
反模式
Avoid using open classes for dependency grouping:
kotlin
// Don't do this — forces test subclasses to satisfy constructor parameters
open class Repositories(private val dataSource: DataSource) {
open val customerRepo: CustomerRepository = CustomerRepositoryImpl(dataSource)
}Use interfaces instead — they have no constructors and force explicit implementation.
Avoid casting to access test-specific methods:
kotlin
// Don't do this
val emailClient = clients.emailClient as EmailClientFake
assertThat(emailClient.sentEmails).hasSize(1)Use typed test implementations with dual access () instead.
testClients.emailClientAvoid making everything lazy:
kotlin
// Don't do this unless needed
open val customerService by lazy { CustomerService(...) }
open val orderService by lazy { OrderService(...) }
open val productService by lazy { ProductService(...) }Lazy adds complexity. Use direct instantiation unless circular dependencies or expensive initialization require it.
Avoid deep context hierarchies:
kotlin
// Too complex
open class DatabaseContext : InfrastructureContext()
open class RepositoryContext : DatabaseContext()
open class ServiceContext : RepositoryContext()
open class SystemContext : ServiceContext()Keep it flat: one SystemContext with nested interface groups for organization.
Don't mix with annotation-based DI:
kotlin
// Don't mix patterns
@Inject lateinit var customerService: CustomerService // Framework DI
val orderService = OrderService(repositories.orderRepo) // Manual DIChoose one approach and stick with it.
避免使用开放类进行依赖分组:
kotlin
// 不要这样做 — 会强制测试子类满足构造参数
open class Repositories(private val dataSource: DataSource) {
open val customerRepo: CustomerRepository = CustomerRepositoryImpl(dataSource)
}应使用接口替代——接口没有构造函数,且强制显式实现。
避免通过类型转换访问测试专属方法:
kotlin
// 不要这样做
val emailClient = clients.emailClient as EmailClientFake
assertThat(emailClient.sentEmails).hasSize(1)应使用带双重访问路径的类型化测试实现()替代。
testClients.emailClient避免给所有组件添加lazy初始化:
kotlin
// 除非必要,否则不要这样做
open val customerService by lazy { CustomerService(...) }
open val orderService by lazy { OrderService(...) }
open val productService by lazy { ProductService(...) }lazylazy避免过深的上下文层级:
kotlin
// 过于复杂
open class DatabaseContext : InfrastructureContext()
open class RepositoryContext : DatabaseContext()
open class ServiceContext : RepositoryContext()
open class SystemContext : ServiceContext()保持扁平化结构:一个SystemContext,内部使用嵌套接口分组进行组织。
不要与基于注解的DI混合使用:
kotlin
// 不要混合模式
@Inject lateinit var customerService: CustomerService // 框架DI
val orderService = OrderService(repositories.orderRepo) // 手动DI选择一种方案并坚持使用。
Migration Path
迁移路径
Adding to existing project:
- Create SystemContext with existing components
- Wire main entry point to use context
- Gradually move initialization logic into context
- Create TestContext and migrate tests incrementally
From framework DI:
- Create parallel SystemContext alongside framework
- New code uses SystemContext
- Gradually migrate existing code
- Remove framework once migration complete
No big-bang rewrite required. Adopt incrementally.
向现有项目中添加该模式:
- 创建包含现有组件的SystemContext
- 修改主入口以使用该上下文
- 逐步将初始化逻辑迁移至上下文
- 创建TestContext并逐步迁移测试
从框架DI迁移:
- 在现有框架旁创建并行的SystemContext
- 新代码使用SystemContext
- 逐步迁移现有代码
- 迁移完成后移除框架
无需大规模重写。可逐步采用该模式。