昨天我们团队中的2个人带着一个不寻常的问题来找我。我们在其中一个winforms应用程序中使用了第三方组件。所有代码都已针对它编写。然后,他们希望将同一供应商的另一个第三方组件合并到我们的应用程序中。令他们高兴的是,他们发现第二部分与第一部分具有完全相同的公共成员。但令他们沮丧的是,这两个组件具有完全独立的继承层次结构,并且没有实现通用接口。让你好奇......好吧,让我好奇。
问题的一个例子:
Incompatible Types http://www.freeimagehosting.net/uploads/f9f6b862f1.png
public class ThirdPartyClass1
{
public string Name
{
get
{
return "ThirdPartyClass1";
}
}
public void DoThirdPartyStuff ()
{
Console.WriteLine ("ThirdPartyClass1 is doing its thing.");
}
}
public class ThirdPartyClass2
{
public string Name
{
get
{
return "ThirdPartyClass2";
}
}
public void DoThirdPartyStuff ()
{
Console.WriteLine ("ThirdPartyClass2 is doing its thing.");
}
}
很高兴他们觉得复制和粘贴他们为第一个组件编写的代码并不是正确的答案。因此,他们考虑将组件即时分配到对象引用中,然后在检查它是什么类型之后修改代码以执行条件转换。但这可能比复制和粘贴方法更加丑陋。
然后他们问我是否可以编写一些反射代码来访问属性并从两种不同的对象类型中调用方法,因为我们知道它们是什么,并且它们完全相同。但我的第一个想法是,优雅。我认为必须有一个更好,更优雅的解决方案来解决这个问题。
答案 0 :(得分:12)
我的第一个问题是,2个第三方组件类是否已密封?它们不是。至少我们有这个。
因此,由于它们没有密封,因此可以通过以下方式解决问题:
从2个第三方类的重合成员中提取公共接口。我称之为Icommon。
public interface ICommon
{
string Name
{
get;
}
void DoThirdPartyStuff ();
}
然后创建2个新类; DerivedClass1和DerivedClass2分别从ThirdPartyClass1和ThirdPartyClass2继承。这两个新类都实现了ICommon接口,但完全是空的。
public class DerivedClass1
: ThirdPartyClass1, ICommon
{
}
public class DerivedClass2
: ThirdPartyClass2, ICommon
{
}
现在,即使派生类是空的,基类也会满足接口,这是我们首先从中提取接口的地方。 生成的类图如下所示。
alt text http://www.freeimagehosting.net/uploads/988cadf318.png
现在,而不是我们以前的:
ThirdPartyClass1 c1 = new ThirdPartyClass1 ();
c1. DoThirdPartyStuff ();
我们现在可以做到:
ICommon common = new DerivedClass1 ();
common. DoThirdPartyStuff ();
DerivedClass2也可以这样做。
结果是,所有引用ThirdPartyClass1实例的现有代码都可以保留原样,只需更换ICommon引用的ThirdPartyClass1引用即可。然后可以给ICommon引用提供DerivedClass1或DerivedClass2的实例,当然它们又分别从ThirdPartyClass1和ThirdPartyClass2继承。一切正常。
我不知道是否有特定的名称,但对我来说它看起来像是适配器模式的变体。
也许我们可以用C#4.0中的动态类型来解决问题,但这样做没有编译时检查的好处。
我很想知道是否有其他人有另一种优雅的方法来解决这个问题。
答案 1 :(得分:4)
如果你正在使用.Net 4,你可以避免这么做,因为动态类型可以帮助你想要的东西。但是,如果使用.Net 2+,还有另一种(不同的方式)实现这一目标:
您可以使用类似Deft Flux中的鸭子类型库来处理您的第三方类,就像它们实现了一个界面一样。
例如:
public interface ICommonInterface
{
string Name { get; }
void DoThirdPartyStuff();
}
//...in your code:
ThirdPartyClass1 classWeWishHadInterface = new ThirdPartyClass1()
ICommonInterface classWrappedAsInterface = DuckTyping.Cast<ICommonInterface>(classWeWishHadInterface);
classWrappedAsInterface.DoThirdPartyStuff();
这避免了必须为所有这些类手动构建派生包装类 - 并且只要该类具有与接口相同的成员就可以工作
答案 2 :(得分:3)
一些包装器怎么样?
public class ThirdPartyClass1 {
public string Name {
get {
return "ThirdPartyClass1";
}
}
public void DoThirdPartyStuff() {
Console.WriteLine("ThirdPartyClass1 is doing its thing.");
}
}
public interface IThirdPartyClassWrapper {
public string Name { get; }
public void DoThirdPartyStuff();
}
public class ThirdPartyClassWrapper1 : IThirdPartyClassWrapper {
ThirdPartyClass1 _thirdParty;
public string Name {
get { return _thirdParty.Name; }
}
public void DoThirdPartyStuff() {
_thirdParty.DoThirdPartyStuff();
}
}
...和ThirdPartyClass2相同,然后在所有方法中使用包装器接口。
答案 3 :(得分:1)
添加界面。您可以为每个第三方添加一个包装器(实现接口)。
无论如何,如果你有第三方的代码,你可以跳过包装器并直接实现接口。不过,我很确定你没有这个来源。