Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Registering different implementations of the same interface. #108

Open
shtlrs opened this issue Sep 10, 2024 · 7 comments
Open

Registering different implementations of the same interface. #108

shtlrs opened this issue Sep 10, 2024 · 7 comments

Comments

@shtlrs
Copy link

shtlrs commented Sep 10, 2024

I think the issue title says it all, but one issue i faced a lot is registering 2 or more different implementations of the same interface.

This can turn out to be useful for example when introducing abstractions in the form of chains of responsibility.

Just like middlewares work for example in web frameworks, you have multiple middlewares and then we iterate on them and each one handle the request per the same interface.

Also, it should be possible to make named registrations of instances of the same interface, so that when you need a particular instance that you define based on some key, you can get it from the container.

This is possible in frameworks like Autofac or SimpleInjector.

@ivankorobkov
Copy link
Owner

Hi.

but one issue i faced a lot is registering 2 or more different implementations of the same interface.

Inject is specifically designed to represent an application is a typed object graph. It simplifies reasoning about the whole application.

This can turn out to be useful for example when introducing abstractions in the form of chains of responsibility.

Please, take a look at the example in this comment #104 (comment)

Also, it should be possible to make named registrations of instances of the same interface

In my opinion, it imitates different types in a custom way. It is usually better to just create different types for different use cases.

@shtlrs
Copy link
Author

shtlrs commented Sep 12, 2024

Hi 👋

Inject is specifically designed to represent an application is a typed object graph. It simplifies reasoning about the whole application.

I get the idea, but when we talk about typed object graph, does it imply that a node can have one child only ?

Please, take a look at the example in this comment #104 (comment)

I did check that, but it's confusing to do that for different reasons:

  • You'd expect an iterator, not a decorator pattern
  • The fact that you don't configure the injector is confusing, at least IMO.

In my opinion, it imitates different types in a custom way. It is usually better to just create different types for different use cases.

Sure, but i still believe it could use a type binding too. Like, I want this implementation of that interface, but now that I think of it, I am guessing this could be done with the params decorator.

class Interface(ABC):
    def method(self):
         pass

class ImplementationX(Interface):
    def method(self):
        pass


@injector.params(param=ImplementationX)
def somefunc(param: Interface)
    param.method()
    ...

@ivankorobkov
Copy link
Owner

does it imply that a node can have one child only

It does imply that a single type has a single implementation.

The fact that you don't configure the injector is confusing, at least IMO.

There is no need to configure a binding of MyType to MyType, inject can just instantiate it for you without any custom bindings.

Take a look at this case:

@injector.autoparams()
def somefunc(service: MyService)

You already specified that you need MyService. Inject can instantiate a singleton for you.

Like, I want this implementation of that interface, but now that I think of it, I am guessing this could be done with the params decorator.

@injector.params(param=ImplementationX)
def somefunc(param: Interface)

That's exactly not an implementation but another type:

@injector.autoparam()
def somefunc(param: InterfaceX)

And that's the idea. Just use the types to specify the whole application as a typed object graph.

@shtlrs
Copy link
Author

shtlrs commented Sep 12, 2024

There is no need to configure a binding of MyType to MyType, inject can just instantiate it for you without any custom bindings.

Yes, but it needs to be able to know how to instantiate it, not all constructors won't take params.

That's exactly not an implementation but another type:
It does imply that a single type has a single implementation.

Aren't both of these contradictory ?

It's theoretically a type yes, but it's used as in implementation (I forgot the @AbstractMethod)

@ivankorobkov
Copy link
Owner

Yes, but it needs to be able to know how to instantiate it, not all constructors won't take params.

Good point. In a typical application the only thing that needs to be provided is a configuration instance. All other params are part of an object graph. And the configuration can be bound manually.

Aren't both of these contradictory ?

It just means that a type is what others use and depend on, an an implementation is an actual hidden, private implementation of the type.

Just a random image from google:

Type is an external circle around a dot, which is an implementation. And the whole graph is an application.

@shtlrs
Copy link
Author

shtlrs commented Sep 12, 2024

Type is an external circle around a dot, which is an implementation. And the whole graph is an application.

Ok, I understand that.

However, what happens when the type needs to be polymorphic, and i need to use different implementation of it based on specific context ?

Meaning, if i have this application like this

image

How can implementations C and D use different implementations of the X type ? The idea is mainly to register two implementations A and B of type X, it doesn't make much sense to me to create a different type for it.

@ivankorobkov
Copy link
Owner

Simple example:

class Cache:
  pass
  
class MemCache(Cache):
  pass
  
class RedisCache(Cache):
  pass

# Later

class UserService:
  cache = inject.attr(Cache)

class MailService:
  cache = inject.attr(RedisCache) # not named cache "redis"

# Bindings

def configure(binder):
  binder.bind(Cache, MemCache())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants