I need to create factory method that takes type name and params. Example:
ShapeFactory.CreateShape("Circle", new Object[] { 4 }) ShapeFactory.CreateShape("Rectangle", new Object[] { 3, 5 })
All shapes derived from BaseShape with method .GetName(); I cannot put together, how to use GetName method to get shape name and create it by string parameter.
Factory method interface:
public static object CreateShape(string shape, object[] parameters)
{
// realization for example
switch (shape)
{
case "Rectangle":
// if (parameters.Length != 2) throw new WrongParamCountException();
return new Rectangle(TryCastToFloat(parameters[0]), TryCastToFloat(parameters[1]));
case "Circle":
// if (parameters.Length != 1) throw new WrongParamCountException();
return new Circle(TryCastToFloat(parameters[0]));
case "Square":
// if (parameters.Length != 1) throw new WrongParamCountException();
return new Square(TryCastToFloat(parameters[0]));
default:
throw new UnsupportedShapeException();
}
}
CodePudding user response:
You will not be able to construct the object the way you want. If all you have is a string value at run time, a switch
statement like this is probably (unfortunately) your best option. But we can improve it somewhat:
public static BaseShape CreateShape(string shape, params float[] parameters)
{
switch (shape)
{
case "Rectangle":
// if (parameters.Length != 2) throw new WrongParamCountException();
return new Rectangle(parameters[0], parameters[1]);
break;
case "Circle":
// if (parameters.Length != 1) throw new WrongParamCountException();
return new Circle(parameters[0]);
break;
case "Square":
// if (parameters.Length != 1) throw new WrongParamCountException();
return new Square(parameters[0]);
break;
default:
throw new UnsupportedShapeException();
}
}
Now, the result of method will be typed to use the BaseShape
, which is an improvement over Object
. Additionally, we're forcing the caller to ensure their arguments will cast to float. Finally, we no longer need to construct an array manually. You can instead call the method like this:
var circle = ShapeFactory.CreateShape("Circle", 2.0);
var rect = ShapeFactory.CreateShape("Rectangle", 3.0, 5.0);
We could also try generics to get even better typing:
public static T CreateShape<T>(string shape, params float[] parameters) where T : BaseShape
{
switch (shape)
{
case "Rectangle":
// if (parameters.Length != 2) throw new WrongParamCountException();
return new Rectangle(parameters[0], parameters[1]);
break;
case "Circle":
// if (parameters.Length != 1) throw new WrongParamCountException();
return new Circle(parameters[0]);
break;
case "Square":
// if (parameters.Length != 1) throw new WrongParamCountException();
return new Square(parameters[0]);
break;
default:
throw new UnsupportedShapeException();
}
}
The implementation is the same, but now you must also specify the shape as a type argument when calling:
var circle = ShapeFactory.CreateShape<Circle>("Circle", 2.0);
The advantage here is the resulting circle
variable really is typed as a Circle
. The downside is you have to know how call the method at compile time, rather than, say, just loading the shape string from a database column at run time.
CodePudding user response:
Assuming GetName
is an instance property, you can collect the sub-types of BaseShape
and map them to their GetName
but this requires a public constructor that takes no parameters.
public static class ShapeFactory {
static Dictionary<string,Type> shapeTypeMap = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => t.IsSubclassOf(typeof(BaseShape)))
.ToDictionary(t => ((BaseShape)Activator.CreateInstance(t)).GetName, t => t);
public static object CreateShape(string shape, params object[] parameters) {
if (shapeTypeMap.TryGetValue(shape, out var shapeType))
return Activator.CreateInstance(shapeType, parameters);
else
throw new UnsupportedShapeException();
}
}
This is not recommended.