Деревья наследования и защищенные конструкторы в C # - PullRequest
2 голосов
/ 14 октября 2008

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

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, могут создавать Bar s. Bar является специализированным Foo, и любой тип Foo должен иметь возможность его создать. Public internal - это «опция», так как большинство предопределенных расширений Foo будут внутренними для DLL, но я считаю это неаккуратным ответом, поскольку каждый, кто придет позже и захочет создать свой собственный тип Foo или Baz (что может произойти) будет зависеть от реализации по умолчанию CreateBar(), которая может соответствовать или не соответствовать их потребностям.

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

Редактировать (Подробнее):

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

Ответы [ 5 ]

4 голосов
/ 14 октября 2008

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

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

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

1 голос
/ 14 октября 2008

Забудьте на мгновение, что Бар происходит от 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, должны иметь возможность создавать Bars." Проверьте.

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

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

1 голос
/ 14 октября 2008

Вы можете получить доступ к конструктору 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);
    }
}
1 голос
/ 14 октября 2008

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

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

0 голосов
/ 14 октября 2008

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...