I have the following Shape hierarchy:
public abstract class Shape
{ ... }
public class Rectangle : Shape
{ ... }
public class Circle : Shape
{ ... }
public class Triangle : Shape
{ ... }
I have implemented the following functionality to determine if two shapes are intersecting. I use the following IsOverlapping
extension method, which uses dynamic
to call the appropriate overloaded IsOverlappingSpecialisation
method at runtime. I believe this is called double dispatching.
static class ShapeActions
{
public static bool IsOverlapping(this Shape shape1, Shape shape2)
{
return IsOverlappingSpecialisation(shape1 as dynamic, shape2 as dynamic);
}
private static bool IsOverlappingSpecialisation(Rectangle rect, Circle circle)
{
// Do specialised geometry
return true;
}
private static bool IsOverlappingSpecialisation(Rectangle rect, Triangle triangle)
{
// Do specialised geometry
return true;
}
This means I can do the following:
Shape rect = new Rectangle();
Shape circle = new Circle();
bool isOverlap = rect.IsOverlapping(circle);
The problem I face now, is that I will have to also implement the following in ShapeActions
for circle.IsOverlapping(rect)
to work:
private static bool IsOverlappingSpecialisation(Circle circle, Rectangle rect)
{
// The same geometry maths is used here
return IsOverlappingSpecialisation(rect, circle);
}
This is redundant (as I will need to do this for every new shape created). Is there a way I could possibly get around this? I thought of passing in a Tuple
parameter into IsOverlapping
, but I still have problems. Essentially I want overloading to occur based on unique unordered parameter sets (I know this is not possible, so looking for a workaround).
I may be overcomplicating things here, but it works...
public static class OverlapCalculator
{
private static readonly Dictionary<Tuple<Type, Type>, Delegate> Calculations = new Dictionary<Tuple<Type, Type>, Delegate>();
public static bool IsOverlapping<TShape, TOtherShape>(this TShape shape, TOtherShape otherShape)
where TShape : Shape
where TOtherShape : Shape
{
var calculation = GetCalculationDelegate<TShape, TOtherShape>();
if (calculation != null)
{
return calculation(shape, otherShape);
}
throw new InvalidOperationException(string.Format("Could not find calculation for {0} and {1}", typeof(TShape).Name, typeof(TOtherShape).Name));
}
public static void AddCalculation<TShape, TOtherShape>(Func<TShape, TOtherShape, bool> calculation)
where TShape : Shape
where TOtherShape : Shape
{
var key = new Tuple<Type, Type>(typeof(TShape), typeof(TOtherShape));
Calculations[key] = calculation;
var reverseKey = new Tuple<Type, Type>(typeof(TOtherShape), typeof(TShape));
var reverseCalculation = new Func<TOtherShape, TShape, bool>((otherShape, shape) => calculation(shape, otherShape));
Calculations[reverseKey] = reverseCalculation;
}
private static Func<TShape, TOtherShape, bool> GetCalculationDelegate<TShape, TOtherShape>()
{
var key = new Tuple<Type, Type>(typeof(TShape), typeof(TOtherShape));
Delegate calculationDelegate;
if (Calculations.TryGetValue(key, out calculationDelegate))
{
return (Func<TShape, TOtherShape, bool>) calculationDelegate;
}
return null;
}
}
This just stores delegates in a Dictionary
and tries to get a matching one when you call IsOverlapping
on a Shape
.
You use it like this:
public class Program
{
public static void Main()
{
// Add the calculation algorithm defined below.
OverlapCalculator.AddCalculation<Rectangle, Triangle>(IsOverlapping);
var rect = new Rectangle();
var triangle = new Triangle();
var circle = new Circle();
// These will work since we have a two way calculation for Rectangle and Triangle
rect.IsOverlapping(triangle);
triangle.IsOverlapping(rect);
// This will throw since we have no calculation between Circle and Triangle.
circle.IsOverlapping(triangle);
}
private static bool IsOverlapping(Rectangle rectangle, Triangle triangle)
{
// Do specialised geometry
return true;
}
}
This should be a neat and fast (no reflection) solution to your problem.
One drawback with this solution is that you have to "declare" the calculation methods using the AddCalculation
method.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.