Дженерики, Наследование и новый оператор - PullRequest
2 голосов
/ 19 марта 2009

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

Допустим, у меня есть базовый класс:

abstract class RealBase {
    protected RealBase(object arg) {
        Arg = arg;
    }

    public object Arg { get; private set; }

    public abstract void DoThatThingYouDo();
}

Я часто создаю второй базовый класс, который является универсальным, который обрабатывает приведение типа «объект» в базовом классе к типу «Т», например:

abstract class GenericBase<T> : RealBase {
    protected GenericBase(T arg)
        : base( arg ) {
    }

    new public T Arg { get { return (T) base.Arg; } }
}

Это позволяет мне получить доступ к «Arg» как к его явному типу без операции приведения:

class Concrete : GenericBase<string> {
    public Concrete( string arg )
        : base( arg ) {
    }

    public override void DoThatThingYouDo() {
        // NOTE: Arg is type string. No cast necessary.
        char[] chars = Arg.ToLowerInvariant().ToCharArray();  
        // Blah( blah, blah );
        // [...]
    }

}

Все время имея возможность работать с ним через "RealBase":

class Usage {
    public void UseIt() {
        RealBase rb = new Concrete( "The String Arg" );
        DoTheThing(rb);
    }

    private void DoTheThing(RealBase thingDoer) {
        rb.DoThatThingYouDo();
    }
}

Предполагается, что есть много других "Конкретных" типов ... не только один.

Вот мои вопросы / проблемы:

  1. Я "сошел с ума" за использование такой подход?
  2. Есть ли любые очевидные недостатки / предостережения используя этот подход?
  3. Как насчет что "новый публичный T ..." в GenericBase? Хорошая / плохая идея? Неудобно?

Любые отзывы или советы будут с благодарностью.

Ответы [ 6 ]

5 голосов
/ 19 марта 2009

Ну, теперь у нас есть дерево наследования глубиной в три уровня, но вы не указали какой-либо конкретной причины для этого.

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

Если это дает вам явное преимущество над другими подходами, это нормально, но я бы учел сложность, которую вы добавляете для того, кто читает код. Они действительно хотят рассмотреть три уровня иерархии с двумя свойствами с одинаковыми именами? (Я бы переименовал GenericBase.Arg в GenericBase.GenericArg или что-то в этом роде.)

5 голосов
/ 19 марта 2009

У меня нет никаких возражений против этого, если вы достаточно дисциплинированы, чтобы использовать только базовый класс как вспомогательный, и никогда не опускать руки до него. Если вы начнете ссылаться на RealBase, GenericBase и ConcreteClass повсюду, вещи будут очень тесно связаны по-настоящему быстро.

На самом деле, я бы порекомендовал поднять его на ступеньку выше и представить интерфейс

interface IReal {
  void DoThatThingYouDo();
}

И полностью исключить базовый класс (в основном, никогда не ссылаться на него, кроме случаев объявления производного класса). Просто совет, который помогает мне повысить гибкость моего кода.

О, и если вы используете интерфейс, не просто объявляйте его в базовых классах, объявляйте его в конкретных:

class MyConcrete: BaseClass<MyConcrete>, IReal {
  ...
}

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

2 голосов
/ 19 марта 2009

Я думаю, что вы могли бы получить ту же функциональность, что и вам, используя интерфейс, в отличие от двойных абстрактных базовых классов, рассмотрим следующее:

public interface IAmReal
{
    void DoThatThingYouDo();
    ...
}

abstract class GenericBase<T> : IAmReal
{
    protected GenericBase<T>(T arg)
    {
        Arg = arg;
    }
    public T Arg { get; set; }
    public abstract void DoThatThingYouDo();
}

class MyConcrete : GenericBase<string>
{
    public MyConcrete(string s) : base(s) {}
    public override void DoThatThingYouDo()
    {
        char[] chars = Arg.ToLowerInvariant().ToCharArray();
        ...
    }
}

class Usage
{
    public void UseIt()
    {
        IAmReal rb = new MyConcrete( "The String Arg" );
        DoTheThing(rb);
    }

    private void DoTheThing(IAmReal thingDoer)
    {
        rb.DoThatThingYouDo();
    }
}
1 голос
/ 19 марта 2009

Я не думаю, что ты не в своем рокере. Смотрите Любопытно повторяющийся шаблон для чего-то еще более сложного.

0 голосов
/ 19 марта 2009

Я единственный, кто считает, что наследства следует избегать настолько, насколько это возможно? Я видел разные реализации, когда наследование вызывало причудливые проблемы, не только при попытке унизить. Интерфейсы спаситель! Я не говорю, что всегда следует избегать наследования, но, просто взглянув на указанный код, я не вижу преимуществ этого дерева наследования перед обычными интерфейсами.

0 голосов
/ 19 марта 2009

Лично я бы настоятельно рекомендовал не использовать новый оператор, так как это может привести к путанице. На самом деле у меня самого недавно была такая проблема, но я пошел с абстрактным базовым классом с суффиксом AsObject, т.е.

public BaseClass{
    public abstract object ValueAsObject {get;set;}
}

и общий класс вдоль линии:

public BaseClass<T> : BaseClass {
    public T Value {get;set;}
    public override object ValueAsObject {
       get{return (T)this.Value;} 
       set{this.Value = value;} // or conversion, e.g. string -> int
}

Предложение Георга Мауэра об интерфейсе для DoThatThingYouDo также хорошо.

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