我已经阅读了几篇关于何时使用嵌套类的文章,但是我发现没有一篇文章解决了我的具体问题。
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
的实例,这样用户就不必为派生类操心了。抽象类根据文件扩展名和文件内容选择正确的实现和参数。