Programming against interfaces is an often-heard good practice in software development. Together with extension methods, this provides a great functionality. However, in C#, there are limitations to that. Let's declare a simple interface for a 3D-Point:
public interface IPoint3D
{
double X { get; }
double Y { get; }
double Z { get; }
}
I can add extension methods for this point in a static class, for example:
public static class Point3DExtensions
{
public static double SumOfCoordinates(this IPoint3D point)
{
return point.X + point.Y + point.Z;
}
}
However, what I can not code is functionality which returns an instance of the interface itself, like for example:
public static class Point3DExtensions
{
public static IPoint3D Add(this IPoint3D point1, IPoint3D point2)
{
return new IPoint3D(point1.X + point2.X, point1.Y + point2.Y, point1.Z + point2.Z);
}
}
The problem here is the part new IPoint3D
, because I can't create an instance of an interface (it's just a contract, not an implementation).
As a workaround, I'd see two possibilites:
In the same assembly as the interface, create an internal class implementing the interface, for example:
internal class SimplePoint : IPoint3D
{
public double X { get; }
public double Y { get; }
public double Z { get; }
public SimplePoint(double x, double y, double z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
}
This class won't be seen from outside the assembly, but now, my extension method Add
can still have IPoint3D
as return type, but actually return an instance of SimplePoint
. Noone from outside of the assembly will be able to cast the returned instance to SimplePoint
, and doesn't need to.
The second option would be to add something like a Create
-Method to the interface:
IPoint3D Create(double x, double y, double z);
However, this method is not static, so I would have to invoke it from any instance of IPoint3D
, which is kind of weird, and I don't always have such an instance. The problem here is that static methods are not allowed for interfaces in C#. If they were, one could simply add static IPoint3D Create(double x, double y, double z);
or even something like new(double x, double y, double z);
(for a constructor) to the interface, and use that for object creation.
Now, the actual questions:
- Am I trying to misuse interfaces here?
- Did I miss any possibility to get the desired behaviour?
- Which languages would offer this desired functionality?
Side Note: What I would like to have is e.g. an extension method for classes implementing an interface ICircle3D
, which can calculate a number of points IPoint3D
on those circles.
Best Answer
I doubt that an interface like
IPoint3D
brings you more benefits than trouble, so here my suggestion: create a concrete, immutable (so also sealed) classPoint3D
instead. When using interfaces, the geometric point/vector operations can only be implemented in a fashion where the underlying type information gets lost, but that is not a problem of the programming language, but a problem of the domain.The reason for this becomes clearer when you try to make two different implementations of that interface, lets say an
AutocadPoint3D
(representing a point in an autocad CAD model, with lots of additional data like tags, color, layer etc.) and aGoogleEarthPoint3D
(representing a coordinate in an KML model). Now try to add points of those two classes by a method likeSo what should be the underlying type of the result be? A new
AutocadPoint3D
, aGoogleEarthPoint3D
, or something different (like yourSimplePoint
)? The only thing which probably makes sense would probably be theSimplePoint
, because it is something like the "smallest common denominator" of all 3D points. Moreover,IPoint3D
cannot be used to force sealed implementations, so code which relies onIPoint3D
cannot be sure the underlying type is always immutable. This might suffer from unexpected side effects when the implementer of the interface is not very careful.Think about the reason for what purpose you typically use interfaces. The most frequent purpose is probably dependency injection, especially for unit testing. But it seldom makes sense to mock out a value object like
Point3D
. Imagine you need to test a class which needs anIPoint3D
, you would probably inject aSimplePoint
object with some predefined values as test data - but then you could also use aSimplePoint
directly and save the interface. So the interface only adds "noise" to your code, with no real benefit.