Pergunta

Let's say (just for the sake of example) I have three classes that implement IShape. One is a Square with a constructor of Square(int length). Second is a Triangle with a constructor of Triangle(int base, int height). Third is a Circle with a constructor of Circle(double radius).

Considering all the classes share the same interface, my mind goes to the factory pattern as a creational pattern to use. But, the factory method would be awkward as it must provide parameters for these various constructors - for instance:

IShape CreateShape(int length, int base, int height, double radius)
{
    ...
    return new Circle(radius);

    ...
    return new Triage(base, height);

    ...
    return new Square(length);
}

This factory method seems quite awkward. Is this where an abstract factory or some other design pattern comes into play as a superior approach?

Foi útil?

Solução

You have a solution looking for a problem, that is why you run into trouble.

A factory method is not an end in itself, it is a means to an end. So you need to start identifying the problem you want to solve first, which means you need a use case for constructing those objects, providing you with the necessary context. Like:

  • you have an external data source like a file stream or database with object descriptions

  • you want a factory to create IShape objects from this data source (so having one and only one place in code to modify in case the list of shapes gets extended)

In the "file stream" context, for example, a CreateShape factory method could probably get a string as a parameter, containing one object description (maybe some CSV string, a JSON string or an XML snippet), and the requirement would be to parse that string to create the right object:

IShape CreateShape(string shapeDescription)
{
    switch(getShapeType(shapeDescription))
    {
      case "Circle":
          radius=parseRadius(shapeDescription);
          return new Circle(radius);

      case "Triangle":
          base=parseBase(shapeDescription);
          height=parseHeight(shapeDescription);
          return new Triangle(base, height);
    ...

}

Now the parameter list of this method does not look quite so awkward any more, I guess?

Other potential use cases:

  • shapes are created based on user inputs: the factory gets part of the user input data as a parameter

  • creating shapes based on some dynamic business logic

You also need to take other, non-functional requirements into account:

  • do you want your factory to assist in decoupling from that external data source? For example, for unit testing? Then make it not just a method, make it a class with an interface, which can be mocked out.

  • do you want the factory itself to be a reusable component, following the Open/Closed principle, where the code does not have to be touched even when new shapes should be added? Then you need to build it in a more generic way, either using reflection, generics, the Prototype pattern, or the Strategy pattern.

And yes, for certain use cases you will probably need no factory method at all.

So in short: clarify your requirements first. If you don't know the context for using the factory method, you don't need it yet.

Outras dicas

Factory class

Use a factory class, which can have several methods. The factory should have its own interface.

interface IShapeFactory
{
    IShape CreateRectangle(float width, float height);
    IShape CreateCircle(float radius);
}

class ShapeFactory : IShapeFactory
{
    ///etc....

Generic factory method

If you'd rather stick with a factory method, and wish to parameterize the type using a generic type parameter, you have a little work to do to make the inputs generic as well.

The trick is to define an interface for the input parameters (e.g. IShapeArgsFor<T>). Because the interface is tied to T, the compiler can infer the rest:

T CreateShape<T>(IShapeArgsFor<T> input) where T : IShape

Supported by

interface IShapeArgsFor<T> where T : IShape
{
}

And

class CircleArgs : IShapeArgsFor<Circle>
{
    public float Radius { get; }
}

class RectangleArgs : IShapeArgsFor<Rectangle>
{
    public float Height { get; }
    public float Width { get; }
}

etc....

You'd then call it like this:

var circle = CreateShape(new CircleArgs { Radius = 3 });

This factory method seems quite awkward.

The client code which calls the factory has to pass parameters for all possible shapes. Furthermore, the parameters which don't apply for the desired shape need to be stubbed out. To get a triangle, the call would be

CreateShape(length: 0, base: 21, height: 42, radius: 0)  // returns a triangle
// 0 or a negative number is a special value

How would the calling code know what parameters to stub out?
There are two options:

  • The calling code doesn't know. It gets the shape data somewhere and passes it through to the factory. The incoming shape data already has all the necessary stubs.
    This is a valid scenario.

  • The calling code adds the stubs. Effectively, the calling code would have to "know" what shape it wants (otherwise it doesn't know which parameters are unneeded).
    That would defeat the purpose of the factory.

Licenciado em: CC-BY-SA com atribuição
scroll top