snippetcsharpMinor
Create shapes in a console
Viewed 0 times
consoleshapescreate
Problem
I've done a job interview assignment on shapes (surprising ah?!) which is a C# console application which creates a list of shapes basically. It's available here, but for my question the only relevant part is the following:
This is where shapes are created -
The problem that was discussed about this section is that it's not "Open for Extension" (Personally I prefer the 'Predicted Variations' version of this principle). So later on I refactored this part:
And now you can clone my project and add whatever shape you want, implement
So, does this follow the principle? Did I get it right using reflection or should I have used a different approach?
This is where shapes are created -
AddNewShape method (I presented this in the interview):switch (pressedKey)
{
case ConsoleKey.D1:
shape = new Square(GetNumFromConsoleAndVerifyIt());
break;
case ConsoleKey.D2:
double height = GetNumFromConsoleAndVerifyIt();
double width = GetNumFromConsoleAndVerifyIt();
shape = new Rectangle(height, width);
break;
case ConsoleKey.D3:
shape = new Circle(GetNumFromConsoleAndVerifyIt());
break;
case ConsoleKey.D4:
double triangleHeight = GetNumFromConsoleAndVerifyIt();
double triangleWidth = GetNumFromConsoleAndVerifyIt();
shape = new RightTriangle(triangleHeight, triangleWidth);
break;
default:
throw new ArgumentException();
}The problem that was discussed about this section is that it's not "Open for Extension" (Personally I prefer the 'Predicted Variations' version of this principle). So later on I refactored this part:
// Use reflection to get the type and it's required params:
Type shapeType = shapeTypes.Where(t => t.FullName.ToLower().Contains(shapeNameEntered.ToLower())).First();
var ctor = shapeType.GetConstructors()[0];
List sides = new List();
foreach (ParameterInfo pi in ctor.GetParameters())
{
sides.Add(GetNumFromConsole(pi.Name));
}
shape = (I2DShape)ctor.Invoke(sides.ToArray());And now you can clone my project and add whatever shape you want, implement
I2DShape (also - have only one ctor) and change nothing in the Program.cs etc... So, does this follow the principle? Did I get it right using reflection or should I have used a different approach?
Solution
I find neither approach is good.
What's wrong with the
What's wrong with reflection?
Instead I suggest the following. Let's say you have an abstract
and some concrete types
For each type you can write a factory that implements a common interface:
Each factory of course can depend on other services like reading from the console etc.
Finally you create a dictionary with all the shape factories
You can now add as many shapes and factories as you want without touching any other shapes or common code.
What's wrong with the
switch?- It needs to be modified with each change and even only if you want to do something with only one shape.
- It's bound to key-codes.
- You cannot test it.
What's wrong with reflection?
- Reflection is usually the last resort solution. I think you are not at a dead end yet.
- The way you implemented it is very unsafe because you use the
FullNameandContains. What if aRectanglewould be in a namespace likeCirclesAndSquresand you are looking for a circle? You'll find something that is not a circle but a rectangle only because its namespace has the name circle in it.
- You cannot test it.
Instead I suggest the following. Let's say you have an abstract
Shape abstract class Shape { }and some concrete types
class Circle : Shape { }For each type you can write a factory that implements a common interface:
interface IShapeFactory
{
Shape CreateShape();
}Each factory of course can depend on other services like reading from the console etc.
class CircleFactory : IShapeFactory
{
public Shape CreateShape()
{
// read params etc
return new Circle();
}
}Finally you create a dictionary with all the shape factories
var shapeFactories = new Dictionary(StringComparer.OrdinalIgnoreCase)
{
[nameof(Circle)] = new CircleFactory()
};
var shapeName = // read shape name...
if (shapeFactories.TryGetValue(shapeName, out IShapeFactory shapeFactory))
{
var shape = shapeFactory.CreateShape();
}You can now add as many shapes and factories as you want without touching any other shapes or common code.
Code Snippets
abstract class Shape { }class Circle : Shape { }interface IShapeFactory
{
Shape CreateShape();
}class CircleFactory : IShapeFactory
{
public Shape CreateShape()
{
// read params etc
return new Circle();
}
}var shapeFactories = new Dictionary<string, IShapeFactory>(StringComparer.OrdinalIgnoreCase)
{
[nameof(Circle)] = new CircleFactory()
};
var shapeName = // read shape name...
if (shapeFactories.TryGetValue(shapeName, out IShapeFactory shapeFactory))
{
var shape = shapeFactory.CreateShape();
}Context
StackExchange Code Review Q#157807, answer score: 9
Revisions (0)
No revisions yet.