在接口中使用泛型

5

更新:

Heinzi是正确的,AutoCAD多段线是引用类型而不是结构体。这是很好的一点。但是我简化了场景,因为我在实际应用中处理的是一个AutoCAD对象,它是结构体。所以请将两者都视为结构体而不是引用类型。


我正在寻找在这种情况下采取的正确方法,并且希望有人可以给予指导或帮助我更好地理解。

数据访问层中有一个接口,它有两个实现来处理两个不同的提供程序:AutoCad和Sketchup API。

interface IEntity
{
    void object GetPoly();
    void void   InsertPoly(object poly);
}

class AutocadEntity
{
    void object GetPoly()
    {
         //calling Autocad APIs
         return Autocad Polyline object
    }
    void InsertPoly(object poly){...}
}

Autocad 的 GetPoly 实现将返回 Polyline 对象,因为 Autocad API 中将其定义为折线,而 Sketchup 则会返回面(Face)对象。
我将返回类型(和参数)定义为 object,以处理这些不同的类型。代价是性能问题,涉及装箱/拆箱。当返回/参数是 object[] 时,它表现得更加明显。
我曾想过使方法返回/参数类型成为泛型是解决方案,但我认为这并不是解决方案,因为这些实现是特定于类型的。

为什么不使用两个不同的接口呢?毕竟,尽管是类似的调用,但不同实现的函数定义是不同的。 - Thomas Stringer
AutocadEntity是处理Autocad的类,那么SketchupEntity是另一个类吗?还是AutocadEntity同时处理两者? - Jarrett Robertson
@Jarrett 是的,还有一个 SketchupEntity 类也实现了相同的接口来处理 Skethcup API。很抱歉错过了这一部分。 - ali
1
通用接口是一个好的解决方案,但你为什么认为性能问题与装箱/拆箱有关?PolylineFace是结构体吗? - Hamlet Hakobyan
@TyreeJackson 是的,这就是意图。为了使这个数据访问层抽象化,以便其他层不必处理特定的API方法或类型。 - ali
显示剩余4条评论
3个回答

4
成本是性能问题,其中会出现装箱/拆箱。
我不这么认为。 Polyline 是一个类,而不是结构体。因此,没有涉及到装箱。
如果这确实是您的应用程序的性能瓶颈,那么您的性能在其他地方丢失了。像往常一样:在优化之前进行测量,否则您可能会最终优化错误的内容。
我认为您的解决方案完全没问题。您可以使用泛型并从 IEntity<Polyline> 派生 AutocadEntity,但这有什么意义呢?由于 Polyline/Face 在接口中既用作输入参数又用作输出参数,因此您不能使 IEntity 成为协变或逆变。因此,IEntity<Polyline>IEntity<Face> 的最常见基类型将是 object,这意味着如果您不知道具体类型,就不能只传递一个通用的 IEntity

我认为好处在于类型安全。 - Hamlet Hakobyan
@HamletHakobyan:好的,没错...不过代价是代码重复,因为你需要将所有 DoSomething(IEntity) 方法拆分成两个:DoSomething(IEntity<Polyline>)DoSomething(IEntity<Face>)。当然,你可以使用 DoSomething<T>(IEntity<T>),但那样你又要进行类型检查,类型安全的好处就没了。 - Heinzi
@Heinzi “因此,IEntity<Polyline>和IEntity<Face>的最常见基本类型将是object,这意味着如果您不知道具体类型,就不能再传递通用的IEntity了。” 请问你能举个例子吗?谢谢。 - ali
@Heinzi 我想你的意思是我不能再在其他地方使用通用的IEntity,比如说在另一个层(例如业务层)。 - ali
@ali:没错。除非你让 IEntity<T> 实现一个通用的 IEntity,但那样你又回到了对象... - Heinzi
@Heinzi,那么您是否仍然认为通用接口是最佳方法,因为不再有唯一的IEntity接口可供传递。正如TyreeJackson所提到的那样,这是最初的意图。 - ali

1

由于您有两个实现接口的不同类,我认为最好的方法是使接口通用。

interface IEntity<T>
{
   T GetPoly();
   void InsertPoly(T poly);
}
class AutocadEntity : IEntity<Polyline>
{
    Polyline GetPoly(){...}
    void InsertPoly(Polyline poly) {...}
}

1
尝试使用适配器模式将PolyLine和Face类型适应为您更喜欢使用的单一类型。例如:
public abstract class BasePoly
{
    public abstract double X { get; set; }
    public abstract double Y { get; set; }
    public abstract double Width { get; set; }
    public abstract double Height { get; set; }
}
public abstract class BasePoly<T> : BasePoly
{
    public T poly { get; private set; }
    protected BasePoly(T poly) { this.poly = poly; }
}

public class PolyLineAdapter : BasePoly<PolyLine>
{
    public PolyLineAdapter(PolyLine poly) : base(poly) {}
    // override abstracts and forward to inner PolyLine instance at 'this.poly'

    public override double X { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }

    public override double Y { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }

    public override double Width { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }

    public override double Height { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }

}

public class FaceAdapter : BasePoly<Face>
{
    public FaceAdapter(Face poly) : base(poly) {}
    // override abstracts and forward to inner Face instance at 'this.poly'

    public override double X { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }

    public override double Y { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }

    public override double Width { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }

    public override double Height { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }


}

interface IEntity
{
    BasePoly GetPoly();
    void   InsertPoly(BasePoly poly);
}

public abstract class Entity<TEntity> : IEntity
    where TEntity : BasePoly
{
    public BasePoly GetPoly()
    {
        return this.GetExternalPoly();
    }
    public abstract TEntity GetExternalPoly();
    public void InsertPoly(BasePoly poly)
    {
        this.InsertExternalPoly((TEntity) poly);
    }
    public abstract void InsertExternalPoly(TEntity poly);
}

public class AutocadEntity : Entity<PolyLineAdapter>
{
    public override PolyLineAdapter GetExternalPoly()
    {
        throw new NotImplementedException();
    }
    public override void InsertExternalPoly(PolyLineAdapter poly)
    {
        throw new NotImplementedException();
    }
}

public class SketchupEntity : Entity<FaceAdapter>
{
    public override FaceAdapter GetExternalPoly()
    {
        throw new NotImplementedException();
    }
    public override void InsertExternalPoly(FaceAdapter poly)
    {
        throw new NotImplementedException();
    }
}

// fills for third party classes
public class PolyLine {}
public class Face {}

使用适配器模式,您提供了一个代理层,将两个第三方类型转换为您想要使用的类型。
请注意,此设计假定您一次只使用一种类型的第三方引擎。如果您同时使用两个引擎,则进行以下更改:
public class BasePoly
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Width { get; set; }
    public double Height { get; set; }
}

interface IEntity
{
    BasePoly GetPoly();
    void InsertPoly(BasePoly poly);
}

public abstract class Entity : IEntity
{
    public abstract BasePoly GetPoly();
    public abstract void InsertPoly(BasePoly poly);
}

public class AutocadEntity : Entity
{
    public override BasePoly GetPoly()
    {
        // retrieve external type, convert it to BasePoly and return that
        throw new NotImplementedException();
    }
    public override void InsertPoly(BasePoly poly)
    {
        // convert BasePoly to external type and insert that
        throw new NotImplementedException();
    }
}

public class SketchupEntity : Entity
{
    public override BasePoly GetPoly()
    {
        // retrieve external type, convert it to BasePoly and return that
        throw new NotImplementedException();
    }
    public override void InsertPoly(BasePoly poly)
    {
        // convert BasePoly to external type and insert that
        throw new NotImplementedException();
    }

}

// fills for third party classes
public class PolyLine {}
public class Face {}

此外,如果您担心适配器封装或转换操作的成本(在您实际测量并确定是否需要优化之前我不会这样认为),那么您可以将适配器模式应用于消耗 IEntity 而不是 PolyLineAdapter/FaceAdapterAutocadEntity/SketchupEntity 类型本身的调用方。基本上,构建一个插件引擎。您可能能够使用泛型来抽象两个实现之间的公共习语。

这里有一个 dotnetfiddle 示例:https://dotnetfiddle.net/UsFPM7


谢谢你的回答。PolylineAdapter是从BasePoly<T>而不是BasePoly派生出来的,我说得对吗?如果是这样的话,那么我想上面的代码会在Entity<PolyLineAdapter>上产生编译错误。 - ali
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Tyree Jackson
@ali 还要注意第二个选项中缺乏泛型。在AutocadEntitySketchupEntity类之间重复的任何代码都可以使用泛型来DRY(Don't Repeat Yourself)。 - Tyree Jackson
@ali DRY指的是“不要重复自己”,你可以在https://en.wikipedia.org/wiki/Don%27t_repeat_yourself上了解更多信息。如果您编写实现“AutocadEntity”和“SketchupEntity”类的代码时发现可以复制粘贴的代码,则可能会出现将泛型应用于第二个解决方案的机会。还有其他选项,但这些选项取决于这些类的消费者(换句话说,您的其他层)。 - Tyree Jackson
让我们在聊天中继续这个讨论 - Tyree Jackson
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接