views:

468

answers:

8

UPDATED I've updated the example to better illustrate my problem. I realised it was missing one specific point - namely the fact that the CreateLabel() method always takes a label type so the factory can decide what type of label to create. Thing is, it might need to obtain more or less information depending on what type of label it wants to return.

I have a factory class that returns objects representing labels to be sent to a printer.

The factory class looks like this:

public class LargeLabel : ILabel
{
    public string TrackingReference { get; private set; }

    public LargeLabel(string trackingReference)
    {
        TrackingReference = trackingReference;
    }
}

public class SmallLabel : ILabel
{
    public string TrackingReference { get; private set; }

    public SmallLabel(string trackingReference)
    {
        TrackingReference = trackingReference;
    }
}

public class LabelFactory
{
    public ILabel CreateLabel(LabelType labelType, string trackingReference)
    {
        switch (labelType)
        {
            case LabelType.Small:
                return new SmallLabel(trackingReference);
            case LabelType.Large:
                return new LargeLabel(trackingReference);
        }
    }
}

Say that I create a new label type, called CustomLabel. I want to return this from the factory, but it needs some additional data:

public class CustomLabel : ILabel
{
    public string TrackingReference { get; private set; }
    public string CustomText { get; private set; }

    public CustomLabel(string trackingReference, string customText)
    {
        TrackingReference = trackingReference;
        CustomText = customText;
    }
}

This means my factory method has to change:

public class LabelFactory
{
    public ILabel CreateLabel(LabelType labelType, string trackingReference, string customText)
    {
        switch (labelType)
        {
            case LabelType.Small:
                return new SmallLabel(trackingReference);
            case LabelType.Large:
                return new LargeLabel(trackingReference);
            case LabelType.Custom:
                return new CustomLabel(trackingReference, customText);
        }
    }
}

I don't like this because the factory now needs to cater for the lowest common denominator, but at the same time the CustomLabel class needs to get a custom text value. I could provide the additional factory method as an override, but I want to enforce the fact that the CustomLabel needs the value, otherwise it'll only ever be given empty strings.

What is the correct way to implement this scenario?

+1  A: 

This is probably an indication that a factory pattern isn't the best for you. If you do either need or wish to stick with it, though, I would suggest creating initialization classes/structs that can be passed into the factory, rather than the string. Whether you want to do it with various subclasses of a basic information class (basically creating an initialization class hierarchy that mimics that of your label classes) or one class that contains all of the information is up to you.

Adam Robinson
Can you recommend an alternative, more appropriate creational pattern?
Neil Barnwell
Well, not knowing your specific environment I would wonder what issues would lie in the client code instantiating the labels themselves? In other words, what led you down the path of the factory pattern?
Adam Robinson
+4  A: 

Well, how do you want to call the factory method?

Concentrate on how you want to be able to use your API, and the implementation will usually make itself fairly clear. This is made even easier if you write the desired results of your API as unit tests.

An overload may well be the right thing to do here, but it really depends on how you want to use the factory.

Jon Skeet
The problem is that I wanted the factory to decide which label to return, based on a parameter. The trouble is that depending on which label it wants to return, it might require additional information. Because calling code can't second-guess the factory, it won't know it needs to pass this additional information in, so we're at a stalemate where the factory can't get the info it needs, but the called doesn't know it needs to supply it.
Neil Barnwell
+1  A: 

You should try to use a configuration class and pass an instance of that to the factory. The configuration classes would build a hierarchy, where a special configuration class would exist for each result you expect from the factory. Each configuration class captures the specific properties of the factory result.

For the example you've given I'd write a BasicLabelConfiguration and a CustomLabelConfiguration derived from it. The BasicLabelConfiguration captures the tracking reference, while the CustomLabelConfiguration captures the custom text.

Finally the factory makes a decision based on the type of the passed configuration object.


Here's an example of the code:

public class BasicLabelConfiguration
{
  public BasicLabelConfiguration()
  {
  }

  public string TrackingReference { get; set; }
}

public class CustomLabelConfiguration : BasicLabelConfiguration
{
  public CustomLabelConfiguration()
  {
  }

  public string CustomText { get; set; }
}

public class LabelFactory
{
  public ILabel CreateLabel(BasicLabelConfiguration configuration)
  {
    // Possibly make decision from configuration
    CustomLabelConfiguration clc = configuration as CustomLabelConfiguration;
    if (clc != null)
    {
      return new CustomLabel(clc.TrackingReference, clc.CustomText);
    }
    else
    {
      return new BasicLabel(configuration.TrackingReference);
    }
  }
}

Finally you'd use the factory like this:

// Create basic label
ILabel label = factory.CreateLabel(new BasicLabelConfiguration 
{
  TrackingReference = "the reference"
});

or

// Create basic label
ILabel label = factory.CreateLabel(new CustomLabelConfiguration 
{
  TrackingReference = "the reference",
  CustomText = "The custom text"
});
__grover
Wow! this is way complex. Like Adam Robinson, I don't see the benefit of using factory at all if you expose all the complexity of the underlying implementation's API with at least as complex (if not more) "configuration" API.
Guss
The factory is still responsible for building the actual object. The configuration is basically a Memento to create the real object. But I agree this is complex.Another option would be to use the prototype pattern, but the auther hasn't described his environment so I don't know if its applicable.
__grover
+1  A: 

Without further information it's pretty hard to give any advice, but assuming that the factory pattern is what you actually need you could try the following approach:

Pack the needed arguments in some kind of property map (e.g. map of string to string) and pass that as an argument to the factory's create method. Use well-known tags as keys in the map, allowing the specialized factories to extract and interpret the mapped values to their own liking.

This will at least allow you to maintain a single factory interface for the time being, and postpone dealing with architectural issues if (or when) you notice that the factory pattern isn't the correct one here.

(Oh, and if you really want to use the factory pattern here I strongly suggest you make it pluggable to avoid having to modify the factory for each new label type).

Cwan
+2  A: 

How about just using the Factory method to decide what label you need?

public class LabelFactory {
    public ILabel CreateLabel(string trackingReference, string customText) {
        return new CustomLabel(trackingReference, customText);
    }

    public ILabel CreateLabel(String trackingReference) {
        return new BasicLabel(trackingReference);
    }
}

Your factory still needs to know about each type (although with an interface you can implement dynamic loading) but there is very little that the client needs to know - according to what data is provided, the factory generates the correct implementation.

This is a simplistic solution to the simple problem you described. I assume the question is an oversimplification of a more complex problem but without knowing what your real problem is, I'd rather not design an over complex solution.

Guss
+1  A: 

You are trying to force the pattern into a scenario in which it does not fit. I would suggest giving up on that particular pattern and focus instead of making the simplest solution possible.

I think in this case I would just have one class, Label, that has a text field for custom text that is normally null/empty but which one can set if the label needs to be custom. It is simple, self-explanatory and will not give your maintenance programmers any nightmares.

public class Label
{
    public Label(string trackingReference) : this(trackingReference, string.Empty)
    {
    }

    public Label(string trackingReference, string customText)
    {
        CustomText = customText;
    }

    public string CustomText ( get; private set; }

    public bool IsCustom
    {
        get
        {
            return !string.IsNullOrEmpty(CustomText);
        }
    }
}
Paul Ruane
What happens if you then want to add 10 new types of behaviors for labels? I bet that would be a nightmare using a single class ;). http://en.wikipedia.org/wiki/Open/closed_principle
Igor Brejc
Then, and only then, I would refactor the code to meet the requirements of the time. Do not try to predict the future: it is costly and you will often be wrong.
Paul Ruane
A: 

From reading your question it sounds like your UI collects the information and then uses the factory to create the appropriate label. We use a different approach in the CAD/CAM application I develop.

During startup my applications uses the factory method to create a master list of labels. Some of my labels have initialization parameters because they are variants of each other. For example we have three type of flat part labels. While others have parameters that are user defined or not known at setup.

In the first case the initialization is handled within the factory method. So I create three instances of FlatPartLabel passing in the needed parameters.

In the second case Label interface has a configure option. This is called by the label printer dialog to populate a setup panel. In your case this is where the tracking reference and CustomText would be passed in.

My label interface also returns a unique ID for each Label type. If I had a specific command to deal with that type of label then I would traverse the list of labels in my application find which one matches the ID, cast it to the specific type of label, and then configure it. We do this when user want to print one label only for a specific flat part.

Doing this means you can be arbitrary complex in the parameters your labels need and not burden your Factory with unessential parameters.

RS Conley
+1  A: 

ANSWER UPDATED FOLLOWING UPDATE OF THE QUESTION - SEE BELOW

I still think you're right to be using the Factory pattern, and correct in overloading the CreateLabel method; but I think in passing the LabelType to the CreateLabel method, you're missing the point of using the Factory pattern.

Key point: the entire purpose of the Factory pattern is to encapsulate the logic which chooses which concrete subclass to instantiate and return. The calling code should not be telling the Factory which type to instantiate. The benefit is that the code which calls the Factory is therefore shielded from changes to that logic in the future, and also from the addition of new concrete subclasses to the factory. All your calling code need depend on is the Factory, and the Interface type returned from CreateLabel.

The logic in your code at the point where you call the Factory must currently look something like this pseudocode...

// Need to create a label now
ILabel label;
if(we need to create a small label)
{
   label = factory.CreateLabel(LabelType.SmallLabel, "ref1");
}
else if(we need to create a large label)
{
   label = factory.CreateLabel(LabelType.LargeLabel, "ref1");
}
else if(we need to create a custom label)
{
   label = factory.CreateLabel(LabelType.CustomLabel, "ref1", "Custom text")
}

...so you're explicitly telling the Factory what to create. This is bad, because every time a new label type is added to the system, you'll need to...

  1. Change the factory code to deal with the new LabelType value
  2. Go and add a new else-if to everywhere that the factory's called

However, if you move the logic that chooses the LabelType value into your factory, you avoid this. The logic is encapsulated in the factory along with everything else. If a new type of label is added to your system, you only need to change the Factory. All existing code calling the Factory remains the same, no breaking changes.

What is the piece of data that your current calling code uses to decide whether a big label or small label is needed? That piece of data should be passed to the factory's CreateLabel() methods.

Your Factory and label classes could look like this...

// Unchanged
public class BasicLabel: ILabel
{
    public LabelSize Size {get; private set}
    public string TrackingReference { get; private set; }

    public SmallLabel(LabelSize size, string trackingReference)
    {
        Size = size;
        TrackingReference = trackingReference;
    }
}



// ADDED THE NULL OR EMPTY CHECK
public class CustomLabel : ILabel
{
    public string TrackingReference { get; private set; }
    public string CustomText { get; private set; }

    public CustomLabel(string trackingReference, string customText)
    {
        TrackingReference = trackingReference;
        if(customText.IsNullOrEmpty()){
           throw new SomeException();
        }
        CustomText = customText;
    }
}



public class LabelFactory
{
    public ILabel CreateLabel(string trackingReference, LabelSize labelSize)
    {
         return new BasicLabel(labelSize, trackingReference);
    }

    public ILabel CreateLabel(string trackingReference, string customText)
    {
         return new CustomLabel(trackingReference, customText);
    }
}

I hope this is helpful.

sgreeve