Recktus
Recktus

Reputation: 19

C#: Provider vs Factory class example

I am using class LoggerProvider to provide ILogger. For each concrete implementation of ILogger I am using specific named method. I am thinking about the structure, can I say this provider class is a factory class?

public class LoggerProvider
{
    public static ILogger GetDbLogger()
    {
        return new DBLogger();
    }

    public static ILogger GetEmailLogger(string email)
    {
        return new EmailLogger(email);
    }

    public static ILogger GetTxtLogger()
    {
        return new TxtLogger(new BaseTxtLoggerConfiguration(), ProjectManager.GetInstance().GetCurrentProjectSettings());
    }
}

public class CustomerOneService
{
    private readonly ILogger _logger;

    public CustomerOneService()
        : this(LoggerProvider.GetDbLogger())
    { }

    public CustomerOneService(ILogger logger)
    {
        _logger = logger;
    }

    public void Import()
    {
        // some operation with _logger
    }
}
public class CustomerTwoService
{
    private readonly ILogger _logger;

    public CustomerTwoService()
        : this(LoggerProvider.GetTxtLogger())
    { }

    public CustomerTwoService(ILogger logger)
    {
        _logger = logger;
    }

    public void CopyData()
    {
        // some operation with _logger
    }
}

Because if I understand purpose of Factory correctly, its used to provide instances of specific interface, but in runtime is decided which concrete implementation will be returned from Factory, for example some input from user interface which is passed to factory as parameter. But on the other hand another purpose is to provide instance and deal with the problem of creating objects - when client does not care how the instance was created. My provider applies this second principle, but not the first one.

I checked article Difference between a Factory, Provider and a Service? but according by definitions of factory and provider, I am still not sure which one is correct one for my case.

Upvotes: 0

Views: 3216

Answers (2)

quetzalcoatl
quetzalcoatl

Reputation: 33586

In short, it depends on usage. If multiple instances of CustomerOneService and CustomerTwoService MAY share the same instances of Loggers, then Provider. "Provider" implies that you don't care if you get a new instance, or shared instance, as long as you get something you can work with.

If One/TwoService require that every instance gets a new instance of a logger, then a factory. "Factory" implies that every time you call one of its 'CreateXYZ' method, you get a NEW instance.

If One/TwoService require that every instance gets an EXCLUSIVE instance of a logger, then a Pool. "Pool" implies that every time you call one of its 'GetXYZ' method, you get an instance that only you will use. Then it's a good idea to 'Release' that instance when you are done with it. "Pool" doesn't imply that this will be a new instance everytime. It may be reused. But you will get it for exclusive use until you release it.

Soo.. You can say a Factory is a kind of a Provider, and a Pool is a kind of a Provider. But certainly a Pool is not a Factory.

Soo#2, your LoggerProvider is certainly a provider, but judging by its implementation, it is also a Factory. Whether you call it one or the other depends on whether you want its users to know that it is a Factory with the guarantee of always getting a new object.

There may be other patterns, but you probably get the idea. It doesn't differ that much in use (hey, you have an X that gives you Ys). It all depends on the resource that you manage/distribute, and naming is there mostly as a hint for what behavior you can expect from your source of objects.

I have no idea which one will suit your case the best. It depends on how your loggers can be used and what limitations they have and how much time you want to spend implementing and managing i.e. a pool. Often, factory/provider are much simpler than a pool.

Also, since One/TwoService seem to get their dependencies in constructors, consider using DI/IoCC. Instead of static lookup via hardcoded factory/provider/etc

public CustomerTwoService()
    : this(LoggerProvider.GetTxtLogger())

consider

public CustomerTwoService(ILogger logger)
    : this(logger)

and then configuring the IoC Container so that CustomerTwoService gets a TxtLogger.

That won't "just do" without a working IoC Container, and introducing it for the first time to your project is another separate topic (but similar in concepts to what you are analysing right now, so it's somewhat in-context of this problem), but worth taking a look at, especially if you expect many components and/or many dependencies.

Upvotes: 3

Jason Holloway
Jason Holloway

Reputation: 709

Your LoggerProvider is almost exactly the same as this:

public enum Scenario
{
  IWantADbLogger,
  IWantAnEmailLogger,
  IWantATxtLogger
}

public class LoggerProvider
{
    public static ILogger GetLogger(Scenario scenario)
    {
        switch(scenario) 
        {
           case Scenario.IWantADbLogger: return new DbLogger();
           //etc etc etc
        }
    }
}

Which looks more like a factory, as it suggests that it decides for you what it returns back (even though in this case it doesn't use it, and is effectively the same as the original code).

Even a poor factory with little runtime-leeway is still a factory if it produces something for you without you determining exactly what that thing is, and I think your code does that. Your LoggerProvider could wire up and return a slightly differently-constructed TxtLogger if it wanted to - and the caller would not know about this. As long as it is making decisions for the caller it is a factory.

Of course providers also do this! As do services, and basically every other encapsulated class in existence does this. So what's the difference?

Factories take the place of constructors. They create you objects, not data, and these objects belong to you, just like they would with a constructor. You have to store the object yourself if you want to keep it around, you have to dispose of it properly, things like that. It doesn't live anywhere else unless you send it somewhere.

And then they distinguish themselves (slightly) from constructors by keeping some element of choice within them at runtime. The more choice the better.

Upvotes: 0

Related Questions