Могу ли я использовать вложенные классы для предотвращения создания экземпляров класса или?

Я прочитал несколько статей о том, когда использовать вложенные классы, но ни одна из найденных мной статей не отвечает на мой конкретный вопрос.

В C # есть класс XmlReader, который предоставляет только метод 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. Или измените его доступность, чтобы он не был виден извне». Информация: «Не используйте общедоступные, защищенные или защищенные внутренние вложенные типы в качестве способа группировки типов. Используйте для этой цели пространства имен. Есть очень ограниченные сценарии, в которых вложенные типы являются лучшим дизайном». Я считаю, что Джон Скит определил, что вы не можете добиться этого с помощью пространств имен. Я просто хотел убедиться, поскольку эта ошибка говорит о том, что существует ограниченное количество сценариев, в которых это лучший дизайн, поэтому, если есть лучший вариант, я открыт для идей: D

Также ему не нравилась цепочка виртуальных вызовов, вызываемая из конструктора. Есть причина для этого? Есть ли способ обойти это? РЕДАКТИРОВАТЬ: В частности, сообщение: «DoNotCallOverridableMethodsInConstructors». Решение: «CompiledLoaderV2.CompiledLoaderV2 (String)» содержит цепочку вызовов, которая приводит к вызову виртуального метода, определенного классом. Просмотрите следующий стек вызовов на предмет непредвиденных последствий »Информация:« Виртуальные методы, определенные в классе, не должны быть вызывается из конструкторов. Если производный класс переопределил метод, будет вызываться версия производного класса (до вызова конструктора производного класса) ». Я чувствую, что это могло быть проблемой, если бы подклассы что-то сделали в своих конструкторах, но поскольку они этого не делают, я не уверен, что это проблема. Есть ли лучший способ заставить классы загружаться определенным образом без использования абстрактных методов в конструкторе?

Большое спасибо за вашу помощь!


person MPavlak    schedule 18.08.2011    source источник
comment
Что именно вам сказал FxCop? Укажите более подробную информацию и свой вопрос, иначе это будет рассматриваться как расплывчатый вопрос и закрыто.   -  person sll    schedule 18.08.2011
comment
Я добавлю сообщения FxCop завтра. я только что вышел из офиса   -  person MPavlak    schedule 18.08.2011


Ответы (4)


Нет, вы не можете сделать это с пространствами имен, хотя вы можете сделать это со сборками, т. Е. Запретить кому-либо за пределами сборки создать экземпляр.

Вы можете сделать это с помощью вложенных классов, но обычно вы должны сделать сам конструктор закрытым, чтобы предотвратить возникновение чего-либо еще от класса. Вы также можете сделать сами вложенные классы закрытыми, если они вам не нужны для внешнего мира.

Вы можете использовать этот шаблон для создания чего-то подобного перечисления Java, а также ограниченных фабрик. Я использовал его для дифференцированного объединения в Noda Time - фактические детали не имеют значения, но вы можете посмотреть источник для получения дополнительных сведений.

Вы правы, не доверяя вызову виртуальных методов из конструкторов. Иногда это может быть полезно, но следует делать очень осторожно с тяжелой документацией.

person Jon Skeet    schedule 18.08.2011
comment
Это узор? Я чувствую, что у меня есть веская причина делать это таким образом, но я не уверен, что есть лучший образец для этого. Я использовал XmlReader в качестве справочника по функциональности. - person MPavlak; 18.08.2011
comment
@Tigran: только для вложенных классов, которые в любом случае могут быть частными, поэтому было бы возможно расширяться только в одном и том же исходном коде. - person Jon Skeet; 18.08.2011
comment
@ user652688: Да, это определенно шаблон, когда вам нужен четко фиксированный набор потенциальных подтипов. - person Jon Skeet; 18.08.2011

Подумайте о том, чтобы сделать классы внутренними. Таким образом, они могут быть созданы в вашей сборке, но не клиентами вашей библиотеки. В целях тестирования вы можете сделать тестовую сборку явным другом вашей сборки, чтобы она могла «видеть» внутренние типы и создавать их экземпляры - гораздо лучше для тестирования.

person Tim Lloyd    schedule 18.08.2011

Вот примерная идея. Что делать, если конструктор является общедоступным (позволяет вызывать его), но требует чего-то, что пользователь не может получить.

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. Намного лучше бросить и не писать миллион нулевых чеков.

person Amy B    schedule 18.08.2011
comment
Я также предпочел бы выбросить, но в этом конкретном случае мы предполагаем, что файлы правильные, поэтому в случае ошибки разыменование нуля или выброс кажутся примерно одинаковыми. Я бы не поймал свой бросок и не позволил бы приложению вылететь или просто дать ему вылететь при нулевом удалении. Хотя мне нравится эта идея; я должен буду рассмотреть это дальше. Спасибо!! - person MPavlak; 19.08.2011

Отредактировано. Вот пример:

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, используя вложенные классы для конкретных классов, чтобы пользователь никогда не беспокоился о производных классах. Абстрактный класс выбирает правильную реализацию и аргументы на основе расширения файла и содержимого файла.

person John Alexiou    schedule 18.08.2011
comment
Зачем вам вкладывать класс коробки? Имеется в виду, есть ли причина, по которой пользователь не может создать свой собственный ящик? Мне кажется, что в моем примере цель состоит в том, чтобы помешать пользователю создать новый объект неправильного типа. В этом случае кажется, что у вас есть Shape Box (float), Shape Sphere (float) и Shape Pyramid (float) ... все еще позволяя пользователю создавать то, что они хотят. Если бы у вас был Shape Create (/ * args для создания формы * /), я думаю, он был бы более похож. - person MPavlak; 19.08.2011