问题
I have a simple program which draws geometrical figures based on mouse data provided by user. I've got one class which handles the mouse tracking (it gets the List with mouse movement history) and one abstract class called Shape. From this class I derieve some extra Shapes, like Circle, Rectangle, etc. - and every one of them overrides the abstract Draw() function.
It all works nicely, but the problem comes when I want the user to be able to switch desired Shape manually. I get the mouse data and i know what shape should I draw. The problem is how to make the code to "know" which object should it create and pass appropiate parameters to the constructor. It is also impossible at this point to add new Shape derivatives, which is obiously wrong.
I obiously don't want to come out with code like:
List<Shape> Shapes = new List<Shape>();
// somwhere later
if(CurrentShape == "polyline"){
Shapes.Add(new Polyline(Points));
}
else if (CurrentShape == "rectangle"){
Shapes.Add(new Rectangle(BeginPoint, EndPoint));
}
// and so on.
The code above clearly vilates the Open-Closed Principle. The problem is that I don't have any good idea how to get over it. The main problem is that different Shapes have constructors with different parameters, which makes it much more troublesome.
I am pretty sure that this is a common problem, but I don't know how to get past it. Do you have ay idea?
回答1:
It begs for a factory but not just the factory but factory with injectable workers.
public class Context {
public Point BeginPoint;
public Point EndPoint;
public List Points;
whatever else
}
public class ShapeFactory {
List<FactoryWorker> workers;
public Shape CreateShape( string ShapeName, Context context )
{
foreach ( FactoryWorker worker in workers )
if ( worker.Accepts( ShapeName ) )
return worker.CreateShape( context );
}
public void AddWorker( FactoryWorker worker ) {
workers.Add( worker );
}
}
public abstract class FactortWorker {
public abstract bool Accepts( string ShapeName );
puboic Shape CreateShape( Context context );
}
public class PolyLineFactoryWorker : FactoryWorker {
public override bool Accepts( string ShapeName ) {
return ShapeName == "polyline";
}
public Shape CreateShape( Context context ) { ... }
}
This way the code is open for extensions - new factory workers can be created freely and added to the factory.
回答2:
When you need to create objects that all derive from a single class or implement the same interface, one common approach is to use a factory. In your case, however, a simple factory may not be sufficient, because the factory itself needs to be extensible.
One way to implement it is as follows:
interface IShapeMaker {
IShape Make(IList<Point> points);
}
class RectMaker : IShapeMaker {
public Make(IList<Point> points) {
// Check if the points are good to make a rectangle
...
if (pointsAreGoodForRectangle) {
return new Rectangle(...);
}
return null; // Cannot make a rectangle
}
}
class PolylineMaker : IShapeMaker {
public Make(IList<Point> points) {
// Check if the points are good to make a polyline
...
if (pointsAreGoodForPolyline) {
return new Polyline(...);
}
return null; // Cannot make a polyline
}
}
With these Maker
classes in hand, you can make a registry of makers (a simple List<IShapeMaker>
) go through the makers passing them the points, and stopping when you get back a non-null shape.
This system remains extensible, because you can add a pair of NewShape
and NewShapeMaker
, and "plug them in" into the existing framework: once NewShapeMaker
gets in the registry, the rest of the system instantly becomes ready to recognize and use your NewShape
.
来源:https://stackoverflow.com/questions/15998071/getting-past-open-closed-principle