8

我有这样的事情:

// 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直接继承。

有没有办法强制执行这种模式?

4

6 回答 6

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");
    }
}

编辑2

刚刚注意到@RQDQ 已经提供了该解决方案(在回答您的评论时没有注意到)。如果那是您想要的解决方案,请接受他的回答。

于 2013-07-02T20:46:14.180 回答
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

“您可以通过将构造函数设为私有来防止类被实例化......”

于 2013-07-02T20:46:47.857 回答
2

如果您一心不使用密封并且仍然使用类,您可以在运行时强制执行此操作。换句话说,在您的 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
{
}
于 2013-07-02T20:51:42.997 回答
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.
}
于 2013-07-02T20:50:10.277 回答
0

SomeDataBlob 不能被继承,因为它的唯一构造函数是internal。如果您尝试在客户端应用程序中实现派生类:

class SomeDataBlobClient : SomeDataBlob
{
    SomeDataBlobClient():base("TEST")
    {

    }
}

您将收到以下错误:

'ClassLibrary1.SomeDataBlob' 类型没有定义构造函数

看来您解决了自己的问题。

于 2013-07-03T04:11:38.320 回答
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 */}
}
于 2013-07-02T20:55:06.500 回答