我有这样的事情:
// This gets implemented by plugin authors to get callbacks about various things.
public interface ExternalPlugin
{
// This gets called by the main application to tell the plugin some data
// is available or similar.
void DoStuff(SomeDataBlob blob);
}
// Data blob for v1 of API
public class SomeDataBlob
{
internal SomeDataBlob(string prop) { Prop = prop; }
// Some piece of data that V1 plugins need
public string Prop { get; private set; }
}
// FUTURE!
// Data blob API v2 of API
public class SomeDataBlobV2 : SomeDataBlob
{
// Can be passed to clients expecting SomeDataBlob no problem.
internal SomeDataBlobV2(string prop, string prop2) :base(prop) { Prop2 = prop2; }
// Some piece of data that V2 plugins need. V2 plugins can cast to this from
// SomeDataBlob, but still can load successfully into older versions that support
// only V1 of the API
public string Prop2 { get; private set; }
}
我必须公开SomeDataBlob
,以便它可以用作公共接口方法ExternalPlugin.DoStuff
的成员。但是,我不希望允许客户端继承该类,因此容易受到脆弱的基类问题的影响。 (该类的所有衍生物应保存在同一组件中)
标记班级sealed
太过分了,因为我认为删除sealed
是一个重大的API变化;即使不是这样,一旦我发货SomeDataBlobV2
客户仍然可以做错事并直接从SomeDataBlob
继承。
有没有办法强制执行这种模式?
答案 0 :(得分:9)
使类成为内部类,并显示接口。然后使用工厂模式创建正确的实现。
public interface ISomeDataBlob
{
}
internal class SomeDataBlob : ISomeDataBlob
{
}
public class BlobApiFactory
{
ISomeDataBlob Create();
}
您隐藏了实现,但仍然允许用户访问所有内容。您甚至可以让用户更轻松地进行单元测试;)
编辑(回答OP的评论)
我真正想要的是采用一些参数的方法。我希望能够添加主应用程序可以在未来版本中提供的参数,如果API不破坏客户端。但我不希望客户端能够创建“参数”类/接口的实例,或者除了接收它的实例作为参数之外与其进行交互
您可以确保传递到库的所有对象都来自您的程序集,而不是隐藏API:
public class YourCoolService
{
public void DoSomething(ISomeDataBlob blob)
{
if (blob.GetType().Assembly != Assembly.GetExecutingAssembly())
throw new InvalidOperationException("We only support our own types");
}
}
<强> EDIT2 强>
注意到@RQDQ已经提供了解决方案(在回答你的评论时没有注意到)。如果这是您想要的解决方案,请接受他的回答。
答案 1 :(得分:3)
/// <summary>
/// This is a dummy constructor - it is just here to prevent classes in other assemblies
/// from being derived from this class.
/// See http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2971840&SiteID=1
/// </summary>
internal MhlAdminLayer() { }
这个想法是让一个没有内部参数的构造函数。然后该类不能从中派生出来。
编辑:抱歉,评论中的链接不再有效。
编辑2: http://msdn.microsoft.com/en-us/library/vstudio/ms173115.aspx
“您可以通过将构造函数设为私有来阻止实例化类...”
答案 2 :(得分:2)
如果您不想使用密封的AND仍在使用类,那么您可以在运行时强制执行此操作。换句话说,在您的API边界,检查所涉及的类并确保它们来自您的程序集。
一个简单的例子:
public void Bar(Foo foo)
{
if (foo.GetType().Assembly != this.GetType().Assembly)
throw new InvalidOperationException("It's not one of mine!");
}
public class Foo
{
}
答案 3 :(得分:1)
据我所知,接口是实现此目的的方法。这将是一个API破坏性的变化,但这意味着你可以做你想做的事。
public interface ExternalPlugin
{
void DoStuff(ISomeDataBlob blob);
}
// Interfaces:
public interface IDataBlob
{
string Prop { get; }
}
public interface IDataBlobV2 : IDataBlob
{
string Prop2 { get; }
}
// Data blob for v1 of API
internal class SomeDataBlob : IDataBlob
{
internal SomeDataBlob(string prop) { Prop = prop; }
public string Prop { get; private set; }
}
// FUTURE!
// Data blob API v2 of API
public class SomeDataBlobV2 : SomeDataBlob, IDataBlobV2
{
// Can be passed to clients expecting SomeDataBlob no problem.
internal SomeDataBlobV2(string prop, string prop2) : base(prop) { Prop2 = prop2; }
public string Prop2 { get; private set; }
}
然后使对象使用工厂模式,例如
public static class DataBlobFactory
{
public IDataBlob GetDataBlob(string prop)
{
return new SomeDataBlob(prop);
}
// so on.
}
答案 4 :(得分:0)
我要做的是制作某种工厂类,该工厂类公开一个接口,该接口将传递客户端使用的特定API的任何版本的实例,并使用内部类隐藏实现
你也可以使用约束来使它更容易使用,然后客户端可以只放置他们正在寻找的对象类型
public interface IBlobV1 { /*public stuff for V1 here*/ }
internal class BlobV1: IBlobV1 {/*v1 implementation*/ }
public interface IBlobV2 : IBlobV1 {/*public interface for V2 here*/ }
internal class BlobV2:BlobV1,IBlobV2 {/*v2 implementation*/}
public sealed BlobFactory
{
public IBlobV1 CreateVersion1Blob(){/* implementation */}
public IBlobV2 CreateVersion2Blob(){/* implementation */}
public T CreateBlob<T>()
where T: IBlobV1
{ /* implementation */}
}
答案 5 :(得分:0)
SomeDataBlob无法继承,因为它的唯一构造函数是内部。如果您尝试在客户端应用程序中实现派生类:
class SomeDataBlobClient : SomeDataBlob
{
SomeDataBlobClient():base("TEST")
{
}
}
您将收到以下错误:
类型'ClassLibrary1.SomeDataBlob'没有定义构造函数
看来你解决了自己的问题。