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?
-
Abstract Factory will make significantly harder and complex. A single factory should be ok. Have you considered adding one method per shape to the factory? And do that following the interface segregation principle?Laiv– Laiv03/31/2019 00:20:16Commented Mar 31, 2019 at 0:20
-
If I add a method per shape, then I’m unsure why I need the factory. Could you perhaps provide a code sample to illustrate?Craig– Craig03/31/2019 00:25:57Commented Mar 31, 2019 at 0:25
-
4Patterns help to solve a problem in your code. What problem you has? Why consumer can not create a shape it need directly with a constructor?Fabio– Fabio03/31/2019 01:19:46Commented Mar 31, 2019 at 1:19
4 Answers 4
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.
-
What about the
strategy pattern
? dofactory.com/net/strategy-design-patternTarabass– Tarabass04/04/2019 19:18:22Commented Apr 4, 2019 at 19:18 -
2@Tarabass: to solve which problem precisely?Doc Brown– Doc Brown04/04/2019 19:36:13Commented Apr 4, 2019 at 19:36
-
-
blogs.microsoft.co.il/gilf/2009/11/22/…Tarabass– Tarabass05/24/2019 06:47:27Commented May 24, 2019 at 6:47
-
@Tarabass: I can think of a scenario where utilizing strategy pattern would make sense in my example above (see my edit). But like all the other things I said above, this is a trade-off: one has to check if the additional complexity is worth it, and if a simpler solution may not be sufficient.Doc Brown– Doc Brown05/24/2019 11:38:49Commented May 24, 2019 at 11:38
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 });
-
2Why the factory method better then
new Circle(radius: 3);
? What problem we solved with a factory method or class?Fabio– Fabio03/31/2019 07:51:14Commented Mar 31, 2019 at 7:51 -
2Depends on what Circle does. If it's just a plain data container with no behavior, there isn't much benefit. If it has behavior, side effects, or dependencies, the factory gives you something to inject (if you use dependency injection) and something to stub (if you use automated unit testing).John Wu– John Wu03/31/2019 07:55:03Commented Mar 31, 2019 at 7:55
-
Thanks, that I was expecting to hear, constructor and factory has same API contract(same arguments need to be provided), and because shape's constructors do not receive any other dependencies - you don't need to mock it for automated tests, so there no need in a factory for this particular case.Fabio– Fabio03/31/2019 08:41:28Commented Mar 31, 2019 at 8:41
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.
-
What shape would you create with
CreateShape(length: 10, base: 21, height: 10, radius: 1)
. In other words, would you validate all the permutations to decide what shape to create? What if it gets all the arguments? what shape will you create?Laiv– Laiv03/31/2019 00:34:12Commented Mar 31, 2019 at 0:34 -
@Laiv I would validate the inputs, of course. I would throw an exception if the shape is defined ambiguously. For the arguments which you have provided, I would have to throw an exception, because it's not possible to determine if you want a Square or a Circle.Nick Alexeev– Nick Alexeev03/31/2019 00:35:21Commented Mar 31, 2019 at 0:35
-
Hm. Would not you find the API of such Factory to be a little bit messy?Laiv– Laiv03/31/2019 00:36:26Commented Mar 31, 2019 at 0:36
-
@Laiv I do find it messy and contrived. I'd add an explicit parameter for the desired shape type, if it were my question.Nick Alexeev– Nick Alexeev03/31/2019 00:38:26Commented Mar 31, 2019 at 0:38
-
How this factory differ with constructors?Fabio– Fabio03/31/2019 01:21:34Commented Mar 31, 2019 at 1:21
One option could be to use an abstract builder with abstract factory pattern.
E.g. ShapeBuilder
and its subclasses CircleBuilder
, TriangleBuilder
and RectangleBuilder
.
The class that calls factory can construct a builder object and cast it back to ShapeBuilder
.
The createShape
method would accept a ShapeBuilder
object and make a decision on ShapeBuilder.type
and further cast ShapeBuilder
to one of the subclasses of it.
The createShape
method then can safely access the attributes required for building the object and send the object back.
Explore related questions
See similar questions with these tags.