以 open/closed 原则的方式在相似类型之间进行转换

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

首先,让我陈述一下我到目前为止学到的知识:C# 接口不能有静态成员 - 请参阅 here or here

现在说说我想做的事情:

我想在几种不同的坐标类型之间进行转换。我在想,如果我创建一个 LatLon class 并让它们都实现这个:

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

那么在类型之间进行转换就很简单了,我会遵循 Open Closed Principle - 我可以根据需要添加任意数量的坐标类型(对扩展开放),只要它们实施 ICoordinate,我可以立即在它们和任何其他坐标类型之间进行转换,而无需 更新 任何旧代码(因修改而关闭)。

然后我意识到只有当我已经有一个坐标实例时才能调用 FromLatLon 并且说 new MyCoordinate().FromLatLon( aLatLon );*

之类的话似乎很愚蠢

这种东西有更好的模式吗?

我考虑过的其他选项:

编辑

*所以我必须创建一个新实例,然后访问它的一个方法,该方法也将创建一个新实例。每次需要一个实例时,我都必须创建两个实例...

一种 janky 解决方案是创建一个抽象 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);
    }
}

然后派生一个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)
    {
       ...
    }
}

此解决方案的缺点:

  • 你必须继承BaseCoordinate,所以你不能继承其他东西
  • 虽然输入像 MailingAddress.FromLatLon(lat, lon) 这样的东西真的很容易,但你不能说像 BaseCoordinate<T> genericCoordinate = new MailingAddress(); 这样的通用东西,因为那个讨厌的 T

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

这是您对 interface-based 方法的唯一抱怨吗?如果是这样,为什么不通过包含 implicit (or explicit) conversion operators 来详细说明 interface-based 方法?

interface-based 方法在您还不知道要处理的对象的类型时很有用。实际上,您的 LatLon 类型也可以实现该接口,而只是 return 本身。

但是如果您知道要处理的类型或类型(就像您在给出的示例中所做的那样),为什么还要费心使用接口呢? public static implicit operator MyCoordinate(LatLon d) => /* your conversion here */; 之类的东西将允许使用 MyCoordinate myCoordinate = someLatLonValue; 这样的代码。作为一个额外的好处,如果这些类型是值类型,您将不会 运行 仅仅为了接口而将它们装箱的风险。

而且接口实现几乎肯定是微不足道的,服从于操作员。例如。类似于:

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
}

选项 1. 可变 class 和初始化语法

如果 class 是可变的,您可以这样定义 属性:

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

然后要从 latlon 创建一个新实例,您只需使用初始化语法进行分配:

var someCoordinate = new MyCoordinate { LatLon = aLatLon };

要将它转换回来,您会读到完全相同的内容 属性:

var aLatLon = someCoordinate.LatLon;

选项 2. 使用工厂

将方法移动到工厂接口。

interface ICoordinate
{
    LatLon ToLatLon();
}

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

后一种模式更适合使用 IoC 的架构,因为它允许将其他参数(例如注入的依赖项)传递到对象创建过程中,而不需要 ICoordinate 知道它们。