Converting between similar types in an open/closed principle sort of way

107 Views Asked by At

First, let me state what I have learned so far: C# interfaces cannot have static members - see here or here

Now for what I would like to do:

I have several different coordinate types that I want to convert between. I was thinking that if I created a LatLon class and made them all implement this:

ICoordinate
{
    LatLon ToLatLon();
    object FromLatLon(LatLon latLon); // must have an instance to call this method :(
}

then it would be trivial to convert between types, and I'd have followed the Open Closed Principle - I can add as many types of coordinates as I want (open to extension), and as long as they implement ICoordinate, I can immediately convert between them and ANY OTHER COORDINATE TYPE without updating any old code (closed for modification).

Then I realized that FromLatLon could only be called if I already had an instance of a coordinate and it seems silly to say something like new MyCoordinate().FromLatLon( aLatLon );*

Is there a better pattern for this kind of thing?

Other options I've thought about:

  • Factory.CreateCoordinate(double lat, double lon, string coordinateType) - but in order to create more coordinate types, I'll also have to update my Factory, so my factory is not closed.
  • require classes to implement new(lat, lon), but this isn't possible in C# 7

Edit

*so I'd have to create a new instance, then access a method on it that will also create a new instance. I'd have to create two instances every time I needed one...

3

There are 3 best solutions below

0
Peter Duniho On BEST ANSWER

it seems silly to say something like new MyCoordinate().FromLatLon( aLatLon );

Is that your only complaint about the interface-based approach? If so, why not elaborate on the interface-based approach by including implicit (or explicit) conversion operators?

The interface-based approach is useful where you don't already know the type of the object you're dealing with. Indeed, your LatLon type could also implement the interface, and just return itself.

But where you know the type or types you're dealing with (as you do in the example you give), why bother with an interface? Something like public static implicit operator MyCoordinate(LatLon d) => /* your conversion here */; will allow for code like MyCoordinate myCoordinate = someLatLonValue;. As an added bonus, if these types are value types, you won't run the risk of having them boxed just for the sake of the interface.

And the interface implementation would almost certainly be trivial, deferring to the operator. E.g. something like:

struct MyCoordinate : ICoordinate
{
    // Implicit conversion "operator LatLon(MyCoordinate mc) { }" takes care of this
    LatLon ToLatLon() => this;

    // No need for `FromLatLon()`...this would be provided for by the other implicit conversion
}
0
bwall On

One kind of janky solution is to create an abstract class:

// "where T : BaseCoordinate<T>" basically allows us to say `Instance` is of type T
public abstract class BaseCoordinate<T> where T : BaseCoordinate<T>
{
    
    protected static T Instance { get; }

    public abstract Point ToLatLong();

    public static T FromLatLong(double latitude, double longitude)
    {
        // so we still need an instance to call FromLatLongInternal from,
        // but at least we only have to create it once per coordinate type
        // because of the Instance property
        return Instance.FromLatLongInternal(latitude, longitude);
    }

    // we don't really want outsiders to use this directly, but I need something
    // I can override that lets me define how to convert this in inheriting classes
    protected abstract T FromLatLongInternal(double latitude, double longitude);


    // Now every time I create a new coordinate type, I can immediately convert between it and all other coordinate types.
    public static TcoordinateType ConvertTo<TcoordinateType>() where coordinateType : BaseCoordinate<coordinateType>
    {
         Point latLong = this.ToLatLong();
         return BaseCoordinate<coordinateType>.FromLatLong(latLong.X, latLong.Y);
    }
}

And then a derived class:

public class MailingAddress : BaseCoordinate<MailingAddress>
{
    public string AddressLn1 {get; set;}
    public string AddressLn2 {get; set;}
    public string AddressLn3 {get; set;}

    public override Point ToLatLon()
    {
       ...
    }

    protected override T FromLatLonInternal(double latitude, double longitude)
    {
       ...
    }
}

Downsides to this solution:

  • You have to inherit from BaseCoordinate, so you can't inherit from something else
  • While it's really easy to type something like MailingAddress.FromLatLon(lat, lon) you can't say something generic like BaseCoordinate<T> genericCoordinate = new MailingAddress(); because of that pesky T
0
John Wu On

Option 1. Mutable class & initializer syntax

If the class is meant to be mutable you could define a property like this:

interface ICoordinate
{
    LatLon LatLon { get; set; }
}

Then to create a new instance from a latlon, you'd just assign using initializer syntax:

var someCoordinate = new MyCoordinate { LatLon = aLatLon };

And to convert it back you'd read the very same property:

var aLatLon = someCoordinate.LatLon;

Option 2. Use a factory

Move the method to a factory interface.

interface ICoordinate
{
    LatLon ToLatLon();
}

interface ICoorindateFactory<T> where T : ICoorindate
{
    T FromLatLon( LatLon input);
}

The latter pattern is more suitable for architectures that use IoC, since it allows for other arguments (e.g. injected dependencies) to be passed into the object creation process without requiring that ICoordinate know about them.