Home > Software engineering >  Factory method by type name
Factory method by type name

Time:01-13

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.

  • Related