better interface support

This commit is contained in:
admin 2025-08-05 13:06:09 +02:00
parent 24e307bd32
commit 26e6d8261b
3 changed files with 67 additions and 72 deletions

View File

@ -15,7 +15,7 @@ import "git.apihub24.de/admin/generic-di"
func init() { func init() {
// register the Struct Constructor Function for DI // register the Struct Constructor Function for DI
di.Injectable(NewConfiguration) di.Injectable[*Configuration, *Configuration](NewConfiguration)
} }
type Configuration struct { type Configuration struct {
@ -40,7 +40,7 @@ import (
) )
func init() { func init() {
di.Injectable(NewGreeter) di.Injectable[*Greeter, *Greeter](NewGreeter)
} }
type Greeter struct { type Greeter struct {
@ -67,7 +67,7 @@ package main
import "git.apihub24.de/admin/generic-di" import "git.apihub24.de/admin/generic-di"
func init() { func init() {
di.Injectable(NewMessageService) di.Injectable[*MessageService, *MessageService](NewMessageService)
} }
type MessageService struct { type MessageService struct {
@ -94,7 +94,7 @@ package main
import di "git.apihub24.de/admin/generic-di" import di "git.apihub24.de/admin/generic-di"
func main() { func main() {
msgService := di.Inject[*MessageService]() msgService := di.Inject[*MessageService, *MessageService]()
// prints the message "Hello, Markus" // prints the message "Hello, Markus"
println(msgService.Welcome()) println(msgService.Welcome())
} }
@ -110,7 +110,7 @@ package main
import "git.apihub24.de/admin/generic-di" import "git.apihub24.de/admin/generic-di"
func init() { func init() {
di.Injectable(newMessageService) di.Injectable[IMessageService, *messageService](newMessageService)
} }
type IMessageService interface { type IMessageService interface {
@ -141,13 +141,13 @@ package main
import di "git.apihub24.de/admin/generic-di" import di "git.apihub24.de/admin/generic-di"
func main() { func main() {
msgService := di.Inject[IMessageService]() msgService := di.Inject[IMessageService, *messageService]()
// prints the message "Hello, Markus" // prints the message "Hello, Markus"
println(msgService.Welcome()) println(msgService.Welcome())
} }
``` ```
## Replace Instance ## Multiple Instance
services/message_service.go services/message_service.go
@ -157,27 +157,38 @@ package services
import "git.apihub24.de/admin/generic-di" import "git.apihub24.de/admin/generic-di"
func init() { func init() {
di.Injectable(newMessageService) di.Injectable[IMessageService, *MessageService](newMessageService)
di.Injectable[IMessageService, *MessageServiceMock](newMessageServiceMock)
} }
type IMessageService interface { type IMessageService interface {
Welcome() string Welcome() string
} }
type messageService struct { type MessageService struct {
greeter *Greeter greeter *Greeter
} }
func NewMessageService() IMessageService { func newMessageService() IMessageService {
return &messageService{ return &messageService{
// here was the Greeter from greeter.go injected // here was the Greeter from greeter.go injected
greeter: di.Inject[*Greeter](), greeter: di.Inject[*Greeter](),
} }
} }
func (ctx *messageService) Welcome() string { func (ctx *MessageService) Welcome() string {
return ctx.greeter.Greet() return ctx.greeter.Greet()
} }
type MessageServiceMock struct {}
func newMessageServiceMock() IMessageService {
return &MessageServiceMock{}
}
func (svc *MessageServiceMock) Welcome() string {
return "Hello, Mock"
}
``` ```
main.go main.go
@ -188,36 +199,14 @@ package main
import di "git.apihub24.de/admin/generic-di" import di "git.apihub24.de/admin/generic-di"
func main() { func main() {
msgService := di.Inject[services.IMessageService]() // get the basic implementation
msgService := di.Inject[services.IMessageService, *services.MessageService]()
// prints the message "Hello, Markus" // prints the message "Hello, Markus"
println(msgService.Welcome()) println(msgService.Welcome())
// get the mock implementation
msgService = di.Inject[services.IMessageService, *services.MessageServiceMock]()
// prints the message "Hello, Mock"
println(msgService.Welcome())
} }
``` ```
message_service_test.go
```go
package services_test
func init() {
// replace the instance of IMessageService with the Mock
di.Replace(newMessageServiceMock)
}
type messageServiceMock struct {}
func newMessageServiceMock() services.IMessageService {
return &messageServiceMock{}
}
func (svc *messageServiceMock) Welcome() string {
return "Hello, Mock"
}
func TestMessageServiceMocking(t *testing.T) {
service := di.Inject[services.IMessageService]()
if service.Welcome() != "Hello, Mock" {
t.Errorf("expect Hello, Mock but get %s", service.Welcome())
}
}
```

View File

@ -13,18 +13,18 @@ var creators = make(map[string]func() any)
var instances = make(map[string]any) var instances = make(map[string]any)
// Injectable marks a constructor Function of a Struct for DI // Injectable marks a constructor Function of a Struct for DI
func Injectable[T any](creator func() T) { func Injectable[T any, K any](creator func() T) {
creatorMutex.Lock() creatorMutex.Lock()
defer creatorMutex.Unlock() defer creatorMutex.Unlock()
creators[getSelector[T]()] = func() any { creators[getSelector[T, K]()] = func() any {
return creator() return creator()
} }
} }
func Replace[T any](creator func() T, identifier ...string) { func Replace[T any, K any](creator func() T, identifier ...string) {
Injectable(creator) Injectable[T, K](creator)
selector := getSelector[T]() selector := getSelector[T, K]()
instanceSelector := getSelector[T](identifier...) instanceSelector := getSelector[T, K](identifier...)
cre, creatorExists := creators[selector] cre, creatorExists := creators[selector]
if !creatorExists { if !creatorExists {
return return
@ -38,10 +38,10 @@ func Replace[T any](creator func() T, identifier ...string) {
} }
// Inject gets or create a Instance of the Struct used the Injectable constructor Function // Inject gets or create a Instance of the Struct used the Injectable constructor Function
func Inject[T any](identifier ...string) T { func Inject[T any, K any](identifier ...string) T {
var nilResult T var nilResult T
selector := getSelector[T]() selector := getSelector[T, K]()
instanceSelector := getSelector[T](identifier...) instanceSelector := getSelector[T, K](identifier...)
_, instanceExists := instances[instanceSelector].(T) _, instanceExists := instances[instanceSelector].(T)
if !instanceExists { if !instanceExists {
creator, creatorExists := creators[selector] creator, creatorExists := creators[selector]
@ -62,14 +62,21 @@ func Inject[T any](identifier ...string) T {
return instances[instanceSelector].(T) return instances[instanceSelector].(T)
} }
func Destroy[T any](identifier ...string) { func Destroy[T any, K any](identifier ...string) {
instanceMutex.Lock() instanceMutex.Lock()
defer instanceMutex.Unlock() defer instanceMutex.Unlock()
instanceSelector := getSelector[T](identifier...) instanceSelector := getSelector[T, K](identifier...)
delete(instances, instanceSelector) delete(instances, instanceSelector)
} }
func getSelector[T any](identifier ...string) string { func getSelector[T any, K any](identifier ...string) string {
tName := getTypeName[T]()
kName := getTypeName[K]()
additionalKey := strings.Join(identifier, "_")
return fmt.Sprintf("%s_%s_%s", tName, kName, additionalKey)
}
func getTypeName[T any]() string {
var def T var def T
typeName := "" typeName := ""
typeOf := reflect.TypeOf(def) typeOf := reflect.TypeOf(def)
@ -78,6 +85,5 @@ func getSelector[T any](identifier ...string) string {
} else { } else {
typeName = reflect.TypeOf((*T)(nil)).Elem().String() typeName = reflect.TypeOf((*T)(nil)).Elem().String()
} }
additionalKey := strings.Join(identifier, "_") return typeName
return fmt.Sprintf("%s_%s", typeName, additionalKey)
} }

View File

@ -9,11 +9,12 @@ import (
) )
func init() { func init() {
di.Injectable(newTextService) di.Injectable[*textService, *textService](newTextService)
di.Injectable(newMessageService) di.Injectable[*messageService, *messageService](newMessageService)
di.Injectable(newConfiguration) di.Injectable[*configuration, *configuration](newConfiguration)
di.Injectable(newGreetingService) di.Injectable[greetingService, *textService](newGreetingService)
di.Injectable(newBasicOverridableService) di.Injectable[overridableService, *basicOverridableService](newBasicOverridableService)
di.Injectable[overridableService, *basicOverridableServiceMock](newBasicOverridableServiceMock)
} }
type ( type (
@ -64,7 +65,7 @@ func newConfiguration() *configuration {
func newTextService() *textService { func newTextService() *textService {
return &textService{ return &textService{
config: di.Inject[*configuration](), config: di.Inject[*configuration, *configuration](),
id: uuid.NewString(), id: uuid.NewString(),
} }
} }
@ -75,7 +76,7 @@ func newGreetingService() greetingService {
func newMessageService() *messageService { func newMessageService() *messageService {
return &messageService{ return &messageService{
texts: di.Inject[*textService](), texts: di.Inject[*textService, *textService](),
} }
} }
@ -142,29 +143,29 @@ func TestInject_Duplicate(t *testing.T) {
func TestInject_Parallel(t *testing.T) { func TestInject_Parallel(t *testing.T) {
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
go func() { go func() {
println(di.Inject[*textService]().GetID()) println(di.Inject[*textService, *textService]().GetID())
}() }()
} }
} }
func TestInject_MultipleInstances(t *testing.T) { func TestInject_MultipleInstances(t *testing.T) {
textServiceA := di.Inject[*textService]("a") textServiceA := di.Inject[*textService, *textService]("a")
textServiceB := di.Inject[*textService]("b") textServiceB := di.Inject[*textService, *textService]("b")
if textServiceA.GetID() == textServiceB.GetID() { if textServiceA.GetID() == textServiceB.GetID() {
t.Errorf("expect a seperate instance textServiceA and textServiceB but there was identical") t.Errorf("expect a seperate instance textServiceA and textServiceB but there was identical")
} }
} }
func TestTryInterface(t *testing.T) { func TestTryInterface(t *testing.T) {
greeter := di.Inject[greetingService]() greeter := di.Inject[greetingService, *textService]()
if greeter.Greeting() != "Hello Markus" { if greeter.Greeting() != "Hello Markus" {
t.Errorf("expect greeting Hello Markus") t.Errorf("expect greeting Hello Markus")
} }
} }
func TestTryInterface_MultipleInstances(t *testing.T) { func TestTryInterface_MultipleInstances(t *testing.T) {
greeterA := di.Inject[greetingService]("a") greeterA := di.Inject[greetingService, *textService]("a")
greeterB := di.Inject[greetingService]("b") greeterB := di.Inject[greetingService, *textService]("b")
if greeterA.Greeting() != "Hello Markus" { if greeterA.Greeting() != "Hello Markus" {
t.Errorf("expect greeting Hello Markus") t.Errorf("expect greeting Hello Markus")
} }
@ -177,13 +178,12 @@ func TestTryInterface_MultipleInstances(t *testing.T) {
} }
func TestOverwriteInjectable(t *testing.T) { func TestOverwriteInjectable(t *testing.T) {
basic := di.Inject[overridableService]() basic := di.Inject[overridableService, *basicOverridableService]()
basicID := basic.GetInstanceID() basicID := basic.GetInstanceID()
if basic.GetValue() != "i am original" { if basic.GetValue() != "i am original" {
t.Errorf("wrong service instance get") t.Errorf("wrong service instance get")
} }
di.Replace(newBasicOverridableServiceMock) basic = di.Inject[overridableService, *basicOverridableServiceMock]()
basic = di.Inject[overridableService]()
if basic.GetInstanceID() == basicID { if basic.GetInstanceID() == basicID {
t.Errorf("basic and newOne are the same instance") t.Errorf("basic and newOne are the same instance")
} }
@ -193,6 +193,6 @@ func TestOverwriteInjectable(t *testing.T) {
} }
func TestDestroy(t *testing.T) { func TestDestroy(t *testing.T) {
_ = di.Inject[textService]("a") _ = di.Inject[*textService, *textService]("a")
di.Destroy[textService]("a") di.Destroy[*textService, *textService]("a")
} }