以 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 );
*
之类的话似乎很愚蠢
这种东西有更好的模式吗?
我考虑过的其他选项:
- Factory.CreateCoordinate(double lat, double lon, string coordinateType) - 但是为了创建更多坐标类型,我还必须更新我的工厂,所以我的工厂没有关闭。
- require classes to implement new(lat, lon),但这在 C# 7 中是不可能的
编辑
*所以我必须创建一个新实例,然后访问它的一个方法,该方法也将创建一个新实例。每次需要一个实例时,我都必须创建两个实例...
一种 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
知道它们。
首先,让我陈述一下我到目前为止学到的知识: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 );
*
这种东西有更好的模式吗?
我考虑过的其他选项:
- Factory.CreateCoordinate(double lat, double lon, string coordinateType) - 但是为了创建更多坐标类型,我还必须更新我的工厂,所以我的工厂没有关闭。
- require classes to implement new(lat, lon),但这在 C# 7 中是不可能的
编辑
*所以我必须创建一个新实例,然后访问它的一个方法,该方法也将创建一个新实例。每次需要一个实例时,我都必须创建两个实例...
一种 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
知道它们。