我已经阅读了几篇关于何时使用嵌套类的文章,但我找到的没有解决我的具体问题。
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)'包含一个调用链,该调用链导致调用该类定义的虚方法。查看以下调用堆栈是否存在意外后果” 信息:“不应该从构造函数中调用类上定义的虚方法。如果派生类重写了方法,则将调用派生类版本(在调用派生类构造函数之前)”。如果子类在其构造函数中执行了某些操作,我觉得可能会成为一个问题,但由于它们没有,我不确定这是一个问题。有没有更好的方法来强制类以某种方式加载而不在构造函数中使用抽象方法?
非常感谢你的帮助!
答案 0 :(得分:2)
不,您不能使用命名空间来执行此操作,尽管可以使用程序集执行此操作 - 即阻止程序集之外的任何人创建实例。
您绝对可以使用嵌套类来完成它,但是您通常应该使构造函数本身私有以防止从类派生的任何其他内容。您也可以将嵌套类本身设为私有,除非您需要将它们发送到外部世界。
您可以使用此模式创建之类的 Java枚举,以及有限的工厂。我已经将它用于Noda Time中的歧视联盟 - 实际细节并不重要,但您可能希望看一下the source以获得更多灵感。
你不正确地从构造函数调用虚方法。它偶尔会有用,但应该非常使用重文档完成。
答案 1 :(得分:1)
考虑让课程内部化。通过这种方式,它们可以在程序集中实例化,但不能由库的客户端实例化。出于测试目的,您可以使测试程序集成为程序集的明确好友,以便它可以“查看”内部类型,并创建它们的实例 - 更好地进行测试。
答案 2 :(得分:0)
已编辑:以下是一个示例:
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
的实例,这样用户就不会对派生类产生麻烦。抽象类根据文件扩展名和文件内容选择正确的实现和参数。
答案 3 :(得分:0)
这是一个粗略的想法。如果构造函数是公共的(允许您调用它),但需要用户无法获取的内容,那该怎么办呢?
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。感觉更好扔,而不必写一百万次无效支票。