我是否使用嵌套类来防止类实例化或…



我已经阅读了几篇关于何时使用嵌套类的文章,但是我发现没有一篇文章解决了我的具体问题。

c#有一个叫做XmlReader的类,它只提供了一个Create()方法。我假设create创建了XmlReader的一个子类。如果没有,那么在本例中,假设它存在。

考虑这个关系:

/// <summary>
/// Class to read information in a file on disk
/// </summary>
interface ILoad
{
  /// <summary> Version number of the file </summary>
  int Version {get;}
  /// <summary> Content of the file </summary>
  string Content {get;}
  /// <summary> Full path to the file </summary>
  string FullPath {get;}
}
/// <summary> Provides base loading functionality </summary>
class LoaderBase : ILoad
{
    public int Version {get; protected set;}
    public string Content {get; protected set;}
    public string FullPath{get; protected set;}
    /* Helpers omitted */
    protected abstract void Load(string pathToFile);
    public static LoaderBase Create(string pathToFile)
    {
      switch(Path.GetExtension(pathToFile))
      {
         // Select the correct loader based on the file extension and return
      }
      return null;//unknown file type
    }
}
/// <summary> Base class functionality to load compiled files </summary>
public abstract class CompiledLoaderBase : LoaderBase
{
  protected CompiledLoaderBase(string path)
  {
    Load(path);
  }
  protected override Load(string path)
  {
     /* read the file and create an XmlReader from it */
     ReadVersionNumber(reader);
     ReadContent(reader); 
  }
  protected abstract void ReadVersionNumber(XmlReader reader);
  protected abstract void ReadContent(XmlReader reader);
  // Wish I could call this Create, but inherited a static Create method already
  public static CompiledLoaderBase CreateCompiled(string path)
  {
     //Figure out which loader to create and return it
     // ... Assume we figured out we need V1
     return new CompiledLoaderV1(path);
  }

  // Here's the fun stuff!
  protected class CompiledLoaderV1 : CompiledLoaderBase
  {
    public CompiledLoaderV1(string path)
    : base(path)
    {}
    protected override ReadVersionNumber(XmlReader reader)
    { /* read the version number and store in Version */ }
    protected override ReadContent(XmlReader reader)
    { /* read the content and store in Content */ }
  }
  // ... More classes with their own methods for reading version and content
}

现在,我使用嵌套类来防止用户直接创建特定的加载器;它们必须使用抽象基类的Create*方法之一。FxCop对我大发雷霆,我希望能得到一些解释。

它提到不要使用嵌套类,而是使用名称空间。是否有一种方法可以使用名称空间来实现这一点?
编辑:具体来说,消息是:"NestedTypesShouldNotBeVisible"。不嵌套类型'CompiledLoaderBase+CompiledLoaderV1'。或者,改变它的可访问性,使其不对外可见。"不要使用公共的、受保护的或受保护的内部嵌套类型作为分组类型的一种方式。为此使用名称空间。在非常有限的情况下,嵌套类型是最佳设计。"现在,我相信Jon Skeet已经指出,您无法通过名称空间实现这一点。我只是想确定一下,既然这个错误说明在有限的情况下这是最好的设计,所以如果有更好的设计,我愿意接受各种想法:D

也不喜欢从构造函数调用的虚调用链。这有什么原因吗?有别的办法吗?编辑:具体来说,消息是:"DoNotCallOverridableMethodsInConstructors"。'CompiledLoaderV2.CompiledLoaderV2(String)'包含一个调用链,导致调用类定义的虚方法。查看以下调用堆栈以查看意外结果。在类上定义的虚方法不应该从构造函数中调用。如果派生类重写了该方法,则将调用派生类版本(在调用派生类构造函数之前)"。如果子类在它们的构造函数中做了一些事情,我觉得这个可能是一个问题,但因为它们没有,我不确定这是一个问题。是否有更好的方法来强制类以某种方式加载,而不使用构造函数中的抽象方法?

非常感谢你的帮助!

不,您不能对命名空间这样做,尽管您可以对程序集这样做-即阻止程序集以外的任何人创建实例。

你完全可以用嵌套类来做到这一点,但你通常应该使构造函数本身私有,以防止从该类派生任何其他东西。你也可以将嵌套类本身设为私有,除非你需要将它们对外公开。

您可以使用此模式创建一些内容,例如 Java枚举,以及有限的工厂。我把它用于野田时间的一个歧视联盟-实际的细节不重要,但你可能想看看源代码以获得更多灵感。

你不信任从构造函数调用虚方法是对的。它偶尔是有用的,但应该在重型文档非常小心地完成。

考虑使这些类成为内部的。这样,它们就可以在程序集中实例化,但不能由库的客户端实例化。出于测试的目的,您可以将测试程序集设置为程序集的显式朋友,以便它可以"看到"内部类型,并创建它们的实例——这对测试来说要好得多。

这是一个粗略的想法。如果构造函数是公共的(允许你调用它),但是需要一些用户无法得到的东西呢?

public interface ILoad
{
}
public abstract class LoaderBase : ILoad
{
    public LoaderBase(InstanceChooser dongle)
    {
      if (dongle == null)
      {
        throw new Exception("Do not create a Loader without an InstanceChooser");
      }
    }
    public abstract void Load(string path);
}
public class InstanceChooser
{
    private InstanceChooser()
    {
    }
    //construction and initialization
    public static ILoad Create(string path)
    {
        InstanceChooser myChooser = new InstanceChooser();
        LoaderBase myLoader = myChooser.Choose(path);
        if (myLoader != null)
        {
            myLoader.Load(path); //virtual method call moved out of constructor.
        }
        return myLoader;
    }
    //construction
    private LoaderBase Choose(string path)
    {
        switch (System.IO.Path.GetExtension(path))
        {
            case "z":  //example constructor call
                return new CompiledLoaderV1(this);
        }
        return null;
    }
}
public class CompiledLoaderV1 : LoaderBase
{
    public CompiledLoaderV1(InstanceChooser dongle)
        : base(dongle)
    {
    }
    public override void Load(string path)
    {
        throw new NotImplementedException();
    }
}

PS,我讨厌返回null。扔出去感觉好多了,不用写一百万张空支票

编辑:下面是一个例子:

class Program
{
    static void Main(string[] args)
    {
        Shape shape = Shape.Create(args[0]);
    }
}
public abstract class Shape
{
    protected Shape(string filename) { ...  }
    public abstract float Volume { get; }
    public static Shape Create(string filename)
    {
        string ext = Path.GetExtension(filename);
        // read file here
        switch (ext)
        {
            case ".box":
                return new BoxShape(filename);
            case ".sphere":
                return new SphereShape(filename);
        }
        return null;
    }
    class BoxShape : Shape
    {
        public BoxShape(string filename)
            : base(filename)
        {
            // Parse contents 
        }
        public override float Volume { get { return ... } }
    }
    class SphereShape : Shape
    {
        float radius;
        public SphereShape(string filename)
            : base(filename)
        {
            // Parse contents
        }
        public override float Volume { get { return ... } }
    }
}

使用嵌套类为具体类创建Shape的实例,这样用户就不必为派生类操心了。抽象类根据文件扩展名和文件内容选择正确的实现和参数。

相关内容

  • 没有找到相关文章

最新更新