Деревья наследования и защищенные конструкторы в C#

StackOverflow https://stackoverflow.com/questions/200079

  •  03-07-2019
  •  | 
  •  

Вопрос

Учитывая следующее дерево наследования, каков был бы наилучший способ реализовать его таким образом, чтобы это работало?

abstract class Foo<T> : IEnumerable<T>
{
    public abstract Bar CreateBar();
}

class Bar<T> : Foo<T>
{
    // Bar's provide a proxy interface to Foo's and limit access nicely.
    // The general public shouldn't be making these though, they have access
    // via CreateBar()
    protected Bar(Foo base)
    {
        // snip...
    }
}

class Baz<T> : Foo<T>
{
    public Bar CreateBar()
    {
        return new Bar(this);
    }
}

Это не удается с: 'Bar.Bar()' is inaccessible due to its protection level.

Я не хочу, чтобы конструктор был общедоступным, только классы, которые наследуются от Foo должен уметь создавать Bars. Bar является специализированным Foo, и любой тип Foo должен быть в состоянии создать его.Public internal здесь является "опцией", поскольку большинство предопределенных расширений для Foo будет внутренним для библиотеки DLL, но я считаю это неаккуратным ответом, поскольку любой, кто придет позже, кто захочет создать свой собственный тип Foo или Baz (что, скорее всего, произойдет) застрянет с значением по умолчанию CreateBar() реализация, которая может соответствовать, а может и не соответствовать их потребностям.

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

Редактировать (Дополнительная информация):

Немного более конкретный:Foo реализует IEnumerable и, короче говоря, Bar предоставляет тот же интерфейс, но для ограниченного подмножества этого перечислимого объекта.Все Foo должны иметь возможность создавать подмножества самих себя (т.е.Бар) и верните его.Но я не хочу, чтобы всем, кто когда-либо захочет реализовать Foo, приходилось беспокоиться об этом, потому что Bar будет выполнять проксирование и беспокоиться об ограничении диапазона и т.д.

Это было полезно?

Решение

Хорошо, новый ответ:

  1. Разделите панель на интерфейс и конкретный класс.
  2. Выразите общедоступный абстрактный метод в терминах IBar.
  3. Сделайте Bar частным вложенным классом в Foo, реализующим IBar.Дайте ему внутренний конструктор, который вы можете вызвать из Foo.
  4. Напишите защищенный метод в Foo, который создает экземпляр Bar из самого себя.Классы, производные от Foo, могут использовать это для реализации абстрактного метода, если достаточно простого проксирования, а классы с более сложными потребностями могут просто реализовать IBar напрямую.Вы даже могли бы изменить абстрактный метод на виртуальный и создать новую панель из "этого" по умолчанию.

Редактировать:Одним из вариантов этого было бы сделать Bar a защищенный вложенный класс внутри Foo, с открытым конструктором.Таким образом, любой производный класс смог бы создать его для себя, но ни один несвязанный класс вообще не смог бы его "увидеть".Вам все равно нужно будет отделить интерфейс от реализации (чтобы интерфейс мог быть общедоступным), но я думаю, что это в любом случае хорошо.

Другие советы

Возможно ли было бы для вас сделать Baz вложенным типом внутри Bar?Это единственный способ предоставить ему больший доступ к Bar, чем он имел бы в противном случае.Простое наличие одного и того же родительского класса дает ему доступ только к защищенным членам Foo, а Foo не имеет специального доступа к Bar .Я подозреваю, что есть и другие извилистые способы сделать это с вложенными типами, но на самом деле это будет довольно неприятно для инженеров по техническому обслуживанию.

Однако это довольно странный дизайн - заставлять один производный класс создавать экземпляр другого класса, производного от того же базового класса.Это действительно то, что вам нужно?Возможно, если бы вы сформулировали это более конкретно, вам было бы легче придумать альтернативные проекты.

Вы можете получить доступ к конструктору Bar через вложенный тип внутри Foo:

abstract class Foo<T> : IEnumerable<T>
{
  public abstract Bar<T> CreateBar();

  protected Bar<T> CreateBar(Foo<T> f) { return new FooBar(f); }

  private class FooBar : Bar<T> 
   { public FooBar(Foo<T> f) : base(f) {}   
   }
}

class Bar<T> : Foo<T>
{ protected Bar(Foo<T> @base) {}
}

class Baz<T> : Foo<T>
{
    public override Bar<T> CreateBar() 
    {
        return CreateBar(this);
    }
}

Забудьте на мгновение, что Bar происходит от Foo.Если вы сделаете это, похоже, проблема заключается в следующем: "Как мне сделать так, чтобы каждый подкласс Foo мог создавать Bar, даже если у подкласса нет доступа к конструктору Bar?"

Эту проблему довольно легко решить:

public class Foo
{
   protected static Bar CreateBarInstance()
   {
      return new Bar();
   }
   public virtual Bar CreateBar()
   {
      return CreateBarInstance();
   }
}

public class Bar
{
    internal Bar()
    {
    }
}

public class Baz : Foo
{
    public override Bar CreateBar()
    {
        Bar b = base.CreateBar();
        // manipulate the Bar in some fashion
        return b;
    }
}

Если ты захочешь гарантия чтобы подклассы Foo не имели доступа к конструктору Bar, поместите их в другую сборку.

Теперь, чтобы вывести Bar из Foo, это простое изменение:

public class Bar : Foo
{
    internal Bar()
    {
    }
    public override Bar CreateBar()
    {
        throw new InvalidOperationException("I'm sorry, Dave, I can't do that.");
    }

}

"Я не хочу, чтобы конструктор был общедоступным". Проверьте.

"Только классы, наследуемые от Foo, должны иметь возможность создавать столбцы". Проверьте.

"Любой тип Foo должен иметь возможность создать его". Проверьте,

"Любой, кто придет позже и захочет создать свой собственный тип Foo или Baz (что, вероятно, произойдет), застрянет с реализацией CreateBar() по умолчанию, которая может соответствовать или не соответствовать их потребностям". Это довольно сильно зависит от того, что должно произойти в Foo .Я думаю, что метод CreateBar() .

C # не предоставляет прямого эквивалента ключевому слову C ++ friend.Похоже, что ваш дизайн требует такой конструкции.

В C ++ вы могли бы указать, что определенный класс имеет доступ к закрытым / защищенным членам другого класса, используя "friend".Примечание:это не то же самое, что C # internal, модификатор, который предоставляет доступ ко всем классам в одной сборке.

Глядя на ваш дизайн, кажется, что вы пытаетесь сделать что-то в том же духе, для чего потребовался бы друг в стиле C ++.Джон Скит прав, обычный дизайн в C #, позволяющий компенсировать это, - использовать вложенный класс.

Это сообщение на форуме далее объясняется и показано несколько примеров того, как это сделать.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top