Question

I have a short question for you: let's imagine that we have a class similar to this one.

public class StreamTradeDataProvider : ITradeDataProvider
{
    public StreamTradeDataProvider(Stream stream)
    {
        this.stream = stream;
    }

    public IEnumerable<string> GetTradeData()
    {
        var tradeData = new List<string>();
        using (var reader = new StreamReader(stream))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                tradeData.Add(line);
            }
        }
        return tradeData;
    }

    private Stream stream;
}

When would you pass through the stream in the constructor? Better yet, would it be a viable option to make the GetTradeData a static method and pass through the stream to that? What is the difference as long as maintainability and functionality go? This has always been unclear to me. It seems like I never know when to use a private backing field + constructor or simply a static method, like this:

public class StreamTradeDataProvider : ITradeDataProvider
{
    public StreamTradeDataProvider() { }

    public static IEnumerable<string> GetTradeData(Stream stream)
    {
        var tradeData = new List<string>();
        using (var reader = new StreamReader(stream))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                tradeData.Add(line);
            }
        }
        return tradeData;
    }    
}

Thanks in advance!

Était-ce utile?

La solution

I believe the question is more about whether your algorithm should be statefull or stateless rather than about wether the method should be static or not. I believe it is best to stay away from static methods in this case.

In your example your interface ITradeDataProvider implies the definition of an instance method, not a static one. Static methods are not inheritable and therefore this would affect the polymorphic design of your GetTradeData method if you did so.

The more relevant question is, therefore, the merits of passing the contextual data in the constructor (statefull) or in an instance method parameter (stateless).

Reusability

One aspect you may consider is wether you should be able to reuse your ITradeDataProvider. If you think of your ITradeDataProvider as an strategy pattern simply encapsulating an algorithm to deal with a stream, and particularly if you actually need to process several streams at once or with certain frequency, then it makes more sense to pass this contextual details in a method parameter. That way you can reuse the same strategy instance over and over.

ITradeDataProvider strategy = new DefaultTradeDataProvider();
for(Stream stream : getMyTradeDataStreams()) {
   IEnumerable<string> tradeData = strategy.getTradeData(stream);   
}

What could be the point of creating multiple instances of the DefaultTradeDataProvider if they all are pretty much just going to run an algorithm on the provided stream? One instance is more than enough here.

In this case the implementation of ITradeDataProvider is stateless and we make sure to pass whatever state it needs to do its work.

Space and Time Co-location

An interesting aspect of the pattern here is that the contextual data and strategy algorithm are probably colocated in time and space. That is, when you receive the stream of data, it is in the same place where you probably are going to use it as well.

Advantages of Statelessness and Multi-threading

A stateless object is in general more reusable, and you don't have to worry of multi-threading issues if you get to share it with multiple other components possibly running in other threads.

With a stateless object you can probably just create a singleton instance of your algorithm and safely reuse it every time.

Statefull Closure

However, the things change a bit when you want to create a statefull closure, i.e. when your intention is to pack data and functionality and maybe use it later at some other time and/or some other place.

In that case it could be convenient to pack the state within the closure.

ITradeDataProvider provider = new DefaultTradeDataProvider(stream);
someOtherService.withProvider(provider);

Encapsulation

Now someOtherService does not need to worry about passing contextual details to the provider. The provider already packs those details inside. It will be used by the someService at some other place and time different than the one from where it was originally created. That is convenient because the someOtherService may not even have access to the stream, which is encapsulated in some other place to which it cannot reach (i.e. the provider implementation). The someOtherService does not even care if the source of the data is a stream or something different. That is the sole responsibility of the closure.

So this could be a convenient way to encapsulate state and behavior and share it with other objects.

Complexity of Keeping State

For this case, you have to deal with responsibly treating the state. It means the provider may not be sharable between multiple threads and you would probably need to create new instances of it per thread, or you'd force to make it thread-safe if you want to share it safely with other threads.

In the case of the stream example of your original question, what should happen once you have consumed the stream once? Can you still invoke the getTradeData() successfully and consume the stream again?

And so, as you can see, packing the state has more complex implications.

Autres conseils

There is not really a difference.

The first example is called constructor injection.

The second kind is somehing which I would call parametric dependency injection, or simply the modeling of usage: A uses B.

The advantage of constructor injection is, that you put the receiving object from the beginning in a valid state.

As a rule of thumb, you should distinguish between necessary dependencies needed for the object to function properly and optional dependencies.

Say you have a writer encapsulating writing to different destinations. It would be necessary to initialize it with the writing component. This would be done via constructor injection. Without the object would be useless.

But perhaps you need the possibility to do a processing step, so you could do setter injection for this optional component: SetPreProcessor.

This covers your first example.

For the second expample: Stream is neither a necessary nor an optional component of StreamTradeDataProvider.

To make the context complete, you would need a third object, a so called mediator. The mediator has access to Stream and to StreamTradeDataProvider and it's job is to bring both together.

When would you pass through the stream in the constructor?

Everytime when I want to build an autonomous component out of other components which could then be (theoretically) passed around (say a logger with a FileWriter component).

Better yet, would it be a viable option to make the GetTradeData a static method and pass through the stream to that?

In terms of testability non static methods are easier to mock. You could instantiate any kind of object with the desired method on it. I see no advantage of a static method here.

What is the difference as long as maintainability and functionality go?

As always: You are in charge! When the code does, what it should and you have no problems testing and working on it (including your co-programmers), you could do whatever you want. There is no law, that forbids anything. Even for copy & paste you aren't going to hell. I see (here) no direct advantage. Under different (or more concrete) circumstances there might be one. But as far as this example go: do what your heart tells you.

This has always been unclear to me. It seems like I never know when to use a private backing field + constructor or simply a static method

And I fear even after my post a certain unclarity remains.

If Stream is like a battery you should inject it, when it is more like a knife it's okay to do it both ways.

The advantage of constructor injection is only, that you do not forget to put batteries in the toy you pass around.

Licencié sous: CC-BY-SA avec attribution
scroll top