C # абстрактный метод с реализацией только базового класса? - PullRequest
7 голосов
/ 15 сентября 2010

Рассмотрим следующую проблему:

У вас есть класс 'A', который служит базовым классом для многих других подобных классов, например.класс с именем B.

Класс A сам по себе полезен (и уже используется повсеместно) и, следовательно, не является абстрактным.

Суть в том, что теперь вы хотите применитьновый протокол, требующий, чтобы все классы, наследуемые от A (включая саму A), реализовали метод 'func ()'.Если подкласс забывает реализовать func (), должна быть ошибка компилятора.

class A {
  A func() { ret new A(...) }
}

class B : A {
  A func() { ret new B(...) }
}

class C : A {
  // Should give error : no func()
}

Проблема ясна?Я не могу сделать A :: func () абстрактным, так как хочу иметь возможность использовать A в качестве конкретного класса.Я не могу сделать его виртуальным, так как это приведет к тому, что подклассы обратятся к суперклассу (и не приведут к ошибкам компилятора).

Единственное, что близко подходит к решению, - это создание нового абстрактного класса A *, в котором все пользовательские типы наследуются от него и заменяет все текущие использования A в качестве конкретного класса новым классом DefaultA, которыйнаследуется от A *.Это кажется грязным.Скажите, пожалуйста, есть ли другой способ сделать это?

Ответы [ 6 ]

10 голосов
/ 15 сентября 2010

Просто нет способа сохранить A конкретный тип и вызвать ошибку компилятора, если подтипы не переопределяют конкретный метод.

2 голосов
/ 15 сентября 2010

Ну, как вы говорите, вы можете создать промежуточный абстрактный класс X, который наследуется от A и требует, чтобы производные классы наследовали от X. X затем объявляет защищенный абстрактный метод с сигнатурой, совпадающей с func и переопределите func от A, чтобы вызвать этот метод. Вот пример:

class A {
    public virtual A func() { ... }
}

abstract class X : A {
    protected abstract A funcImpl();

    public override A func() { return funcImpl(); }
}

class B : X  { /* must now implement funcImpl() to avoid compiler error */ }

class C : X  { /* must now implement funcImpl() to avoid compiler error */ }

Основная проблема этого подхода заключается в том, что все производные классы теперь наследуются от X, а не от A. Таким образом, вы просто изменили проблему принудительного применения с одного вида на другой.

Более чистый подход, на мой взгляд, состоит в том, чтобы преобразовать A в базовый класс ABase и сделать func абстрактным. Затем запечатайте A и потребуйте B и C для наследования от ABase, а не A:

class ABase {
    public abstract ABase func();    }

sealed class A : ABase {
    public override ABase func() { ... }
} 

class B : ABase { 
    // forced to override func()
    public override ABase func() { ... }
}

Вам придется заменить все варианты использования A на ABase - но в Visual Studio есть несколько превосходных инструментов рефакторинга (и сторонних инструментов, таких как ReSharper), которые делают это существенно менее болезненным, чем когда-то.

1 голос
/ 15 сентября 2010

По сути, вы просите нас: «Сделайте этот перерыв, но не делайте этот перерыв».Возможно, вы видите внутренний конфликт.

Это одна из причин, по которой базовые классы должны использоваться только как базовые классы, а не как сами по себе.

1 голос
/ 15 сентября 2010

Вы не можете создать экземпляр класса A, если он абстрактный.Таким образом,

new A();

и

abstract A func(); // within the A class definition

противоречивы.

Если вы уклоняетесь от абстрактного и вместо этого переходите в виртуальный режим, вы можете получить runtime (не во время компиляции) проверка:

// In the A class definition.
protected virtual A func()
{
    if (GetType() != typeof(A))
        throw // ...
}
1 голос
/ 15 сентября 2010

Если это то, что вам нужно сделать, тогда да.

На мой взгляд, вы можете пересмотреть это требование. Я часто внедрял подобные фреймворки, где форсировать реализации в подклассах было умным трюком. Затем я обнаружил, что в качестве потребителя этих фреймворков я буду дублировать код или использовать по умолчанию какую-то бессмысленную реализацию (например, пустой метод). Ни один из них не является идеальным, поскольку они добавляют беспорядок и шум и снижают удобство обслуживания.

Кроме того, если применение этого шаблона является самообработкой (т. Е. В вашем примере есть подклассы, возвращающие экземпляры самих себя), то вы можете попробовать что-то другое, например, правильный шаблон фабрики. Э-э, и под «попробуй» я подразумеваю использование Инверсии Контейнера Контроля, как Castle Windsor, Ninject, Unity.

0 голосов
/ 15 сентября 2010

Возможно, это даже сложнее, но вы могли бы заставить B, C, D и т. Д. Реализовать интерфейс, требующий func ().Затем убедитесь, что они всегда создаются с помощью заводского метода.Так что-то смутно вроде:

public class B: A, IFunc{
   private B(){}
   public static B Create(){ return new B(); }

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