【问题标题】:How to not write dumb overloads but handle lots of casting in overloaded function如何不编写愚蠢的重载,而是在重载函数中处理大量强制转换
【发布时间】:2017-09-26 00:49:26
【问题描述】:

我想设计一些东西,让两件事之间的距离。然而,这些东西可能以许多令人讨厌的形式表现出来。

假设我们有一组类。 (不一定是基础和派生的) 猫,CoolCat,ReallyCoolCat

他们都有获得职位的方法。我想写一个函数调用'DistanceBetween'来获取猫之间的距离。

我可以重载:

public static float DistanceBetween(Cat cat1, Cat cat2)
{
    return Mathf.Abs(cat1.position - cat2.position);
}

public static float DistanceBetween(CoolCat cat1, CoolCat cat2)
{
    return Mathf.Abs(cat1.transform.position, cat2.transform.position);
}

// ... etc...

但是,在某些情况下,我需要知道 Cat 和 CoolCat 之间的距离或 CoolCat 和 RealCoolCat 之间的距离。这意味着……

public static float DistanceBetween(Cat cat1, CoolCat cat2)
{
    return Mathf.Abs(cat1.position, cat2.transform.position);
}

public static float DistanceBetween(CoolCat cat1, ReallyCoolCat cat2)
{
    return Math.Abs(cat1.tranform.position, cat2.kittyVariables.position);
}

// ... etc ...

但这似乎是任意的,因为我可以重新排列参数的顺序,而我的函数将不起作用。所以我必须...

public static float DistanceBetween(CoolCat cat1, Cat cat2)
{
    return Mathf.Abs(cat1.tranform.position, cat1.position);
}

public static float DistanceBetween(ReallyCoolCat cat1, CoolCat cat2)
{
    return Math.Abs(cat1.kittyVariables.position, cat2.transform.position);
}

// ... etc ...

所以这意味着我制作的每只可爱小猫的代码量增加了 n^2。由于我想制作多少可爱的小猫,这种代码增长量是不可接受的。我无法实现继承,因为我可爱的小猫(虽然名字相似)具有非常不同的功能并且是独一无二的。 (我也可以添加小狗等。)所以我想的是创建一个接口“IDistanceable”,表示实现类有一个“位置”属性并在每个小猫中实现它。但这似乎有点过头了,我想要的只是可以重新排列我的论点并使 Func(a,b) 等于 Func(b,a)...

我真的不知道该怎么办...这两种解决方案(编写 500 个函数或制作界面和大量垃圾)似乎都错了。

由于无法修改一些可爱的小猫类,界面将无法正常工作...

请帮助我和我可爱的小猫!谢谢!

【问题讨论】:

  • 如果他们都有办法访问某个职位,请创建一个名为IPositioned 的接口并让他们实现它。那么你只需要一个 GetDistance 函数,它需要 2 个 IPositioned 对象。这正是接口的用途。
  • 我正在考虑使用“IDistanceable”界面类似的东西......但现在我意识到了一些事情。一些可爱的小猫是我无法修改的类。
  • 绝对最坏的情况,让GetDistance 函数接受2 个对象,并使用反射来查找Position 属性。如果“位置”函数在某些类中的命名不同,您可能需要使用特殊逻辑来找到正确的函数。也可以使用dynamic 对象。
  • 然后你可以为那些你不能修改的类做一个重载,其余的使用一个接口。所有这些类都共享一些基类吗?
  • 听起来很难。但是有没有办法让 Func(a,b) 等于 Func(b,a) 以便代码的增长可以从 n^2 减少到第 n 个三角形数

标签: c# casting overloading


【解决方案1】:

如果你不能修改类,你最好把它们包装在你可以修改的东西中。这样,您可以将特定于类的逻辑集中在一个地方(不同的构造函数)。

class CatWrapper
{
    private int position { get; set; }

    public CatWrapper(Cat cat) { ... }
    public CatWrapper(CoolCat cat) { ... }
    public CatWrapper(ReallyCoolCat cat) { ... }

    public DistanceFrom(CatWrapper other) { ... }
}

【讨论】:

  • 我喜欢这个。您应该考虑让包装器实现一个IPositioned 接口,这样只需要一个GetDistance 函数,并且只需要包装特殊情况的对象。 BTW,这个模式有个名字,叫做“Adapter”模式。
  • 哇,这太酷了!我也喜欢这个。但只是出于好奇,你会在多少只猫的情况下蛮力超载,又有多少只猫会使用这种很酷的设计模式?我是个新手,不擅长编码,所以我有点想要你们的一些常识。
  • 如果你控制类,它应该直接实现接口。如果您不控制,请使用包装器。这是涉及代码量最少的方式。
  • “你会在多少只猫的情况下蛮力超载,在多少只猫会使用这种很酷的设计模式?”这里有两个变量:有多少类和多少属性。如果您需要使用position 和velocity,那么您将不得不进行两倍的重载。我的经验法则是在三个或更多重载时,切换到包装器。
【解决方案2】:

这是一个纯粹的学术答案,因为@Andrews Pilser's 对于几乎任何现实世界的项目来说都远远优于它,但这对于具有 any 的 any 类将解决它可以想象的方式来表示一个位置。它大量使用 lambda 表达式和泛型,并且不需要控制底层类。

代码是用LINQPad 编写的,所以看起来可能有点奇怪,但它是标准的 C#(版本 7),可以直接嵌入到 Visual Studio 中。文件可用here。

这使用Dictionary 为任何可以转换为Point 的Type 存储ToPointConverter。 ToPointConverter 是从静态方法 Create 创建的,该方法接受从特定泛型 T 返回 Point 的 lambda。

如您所见,我提供了 3 个示例“kitty”类,每个类都以完全不同的方式存储它们的位置。主函数为每个创建一个转换器,将其存储在转换器字典中,然后计算“kitties”不同组合之间的距离。 (我可能弄错了距离函数,已经晚了,但这是一个小细节。)

它产生这个输出:

2.23606797749979
9.05538513813742
2.23606797749979
8.06225774829855
9.05538513813742
8.06225774829855

void Main()
{
    //Define conversion functions for anything that can be converted.
    converters.Add(typeof(KittyA), ToPointConverter<KittyA>.Create(kitty => kitty.Location));
    converters.Add(typeof(KittyB), ToPointConverter<KittyB>.Create(kitty => new Point { X = kitty.X, Y = kitty.Y }));
    converters.Add(typeof(KittyC), ToPointConverter<KittyC>.Create(kitty => kitty.MyLocation));

    //Declare some kitties
    var kitty1 = new KittyA { Location = new Point { X = 1, Y = 1 } };
    var kitty2 = new KittyB { X = 3, Y = 2 };
    var kitty3 = new KittyC { MyLocation = new Point { X = 2, Y = 10 } };

    //Calculate the distances
    GetDistance(kitty1, kitty2).Dump();
    GetDistance(kitty1, kitty3).Dump();

    GetDistance(kitty2, kitty1).Dump();
    GetDistance(kitty2, kitty3).Dump();

    GetDistance(kitty3, kitty1).Dump();
    GetDistance(kitty3, kitty2).Dump();
}
private Dictionary<Type, IToPointConverter> converters = new Dictionary<Type, IToPointConverter>();

//A helper function that does the converts the passed objects in to Points, and calculates the distance between them.
private double GetDistance(object obj1, object obj2)
{
    var point1 = GetConvrterFor(obj1).Convert(obj1);
    var point2 = GetConvrterFor(obj2).Convert(obj2);

    return Math.Sqrt(Math.Pow(point2.X - point1.X, 2) + Math.Pow(point2.Y - point1.Y, 2));
}

//Another helper that gets the IToPointConverter for the object instance passed in.
private IToPointConverter GetConvrterFor(object obj) => converters[obj.GetType()];

//This generic class stores a lambda expression that converters from T to a Point
public class ToPointConverter<T> : IToPointConverter
{
    public static ToPointConverter<T> Create(Func<T, Point> conversion)
    {
        return new ToPointConverter<T>(conversion);
    }

    private ToPointConverter(Func<T, Point> conversion)
    {
        _conversion = conversion;
    }

    private Func<T, Point> _conversion;

    public Point Convert(T obj) => _conversion(obj);

    Point IToPointConverter.Convert(object obj) => Convert((T)obj);
}

//The non-generic interface for the converter (so different closed generic types can be stored in the same dictionary, and have their Convert method called.)
public interface IToPointConverter
{
    Point Convert(object obj);
}

//Just a standard structure to hold a location.  You would use whatever native location class your framework has.
public struct Point
{
    public int X;
    public int Y;
}


//Some example kitty classes
public class KittyA
{
    public Point Location { get; set; }
}

public class KittyB
{
    public int X { get; set; }
    public int Y { get; set; }
}

public class KittyC
{
    public Point MyLocation { get; set; }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-28
    • 2016-10-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多