Типобезопасный способ хранения двух типов объектов в коллекции - PullRequest
5 голосов
/ 28 июля 2011

Я реализовал улучшенный алгоритм Шунтирования-Ярда для анализа арифметического выражения. Одним из аспектов алгоритма является то, что он поддерживает Queue и Stack.

В моей реализации Queue содержит Expressions и Operators. Stack содержит Operators и Parenthesis.

Expressions, Parenthesis и Operators не имеют ничего общего, кроме двух любых из них, имеющих общий интерфейс.

Подходы:

  • Моя текущая реализация состоит из Expression и Operator, реализующих INotParanthesis. Operator и Paranthesis реализуют INotExpression. Затем я объявляю Queue <INotParanthesis> и Stack <INotExpression>.

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

  • С другой стороны, я также не хочу использовать коллекции <Object>, так как может быть сложно убедиться в правильности такого кода.

  • Пока я придумал только один вариант - реализовать собственные контейнеры NonParanthesisQueue и NonExpressionStack. Это дает преимущество более последовательной проверки типов на объектах, извлекаемых из этих контейнеров, и недостатком гораздо большего количества кода.

Есть ли разумные альтернативы моим подходам?

Ответы [ 3 ]

4 голосов
/ 28 июля 2011

Похоже, что вы действительно хотите, это сумма типа. Хотя в C # они не встроены, в функциональном программировании есть хитрость, которую вы можете использовать под названием церковное кодирование для достижения этой цели. Он полностью безопасен для типов, без приведения типов, однако его использование в C # немного странно, в основном из-за ограничений вывода типов.

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

var stack = new Stack<IEither<Operator, Parenthesis>>();

stack.Push(new Left<Operator, Parenthesis>(new Operator()));
stack.Push(new Right<Operator, Parenthesis>(new Parenthesis()));

while (stack.Count > 0)
{
    stack.Pop().Map(op  => Console.WriteLine("Found an operator: " + op),
                    par => Console.WriteLine("Found a parenthesis: " + par));
}

Вот реализация IEither, Left и Right. Они полностью универсальны и могут использоваться в любом месте, где вам нужен тип суммы.

public interface IEither<TLeft, TRight>
{
    TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight);
    void Map(Action<TLeft> onLeft, Action<TRight> onRight);
}

public sealed class Left<TLeft, TRight> : IEither<TLeft, TRight>
{
    private readonly TLeft value;

    public Left(TLeft value)
    {
        this.value = value;
    }

    public TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight)
    {
        return onLeft(value);
    }

    public void Map(Action<TLeft> onLeft, Action<TRight> onRight)
    {
        onLeft(value);
    }
}

public sealed class Right<TLeft, TRight> : IEither<TLeft, TRight>
{
    private readonly TRight value;

    public Right(TRight value)
    {
        this.value = value;
    }

    public TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight)
    {
        return onRight(value);
    }

    public void Map(Action<TLeft> onLeft, Action<TRight> onRight)
    {
        onRight(value);
    }
}

Ссылки:

1 голос
/ 28 июля 2011

Возможно, вы могли бы определить тип малого держателя для каждого, один со свойством Expression и свойством Operator, а другой со свойством Operator и свойством Parenthesis.Средства доступа и конструкторы могут утверждать или иным образом гарантировать, что заполнен только один.Очередь и стек будут содержать соответствующий тип держателя.

Немного неуклюжий, но безопасный и работоспособный.

Надеюсь, у кого-нибудь появится более умная идея.

0 голосов
/ 28 июля 2011

Если имя вас беспокоит, как насчет IQueueable и IStackable? Вот еще несколько вопросов о стековом потоке, похожих на ваши (об интерфейсах маркеров).

Для чего нужен интерфейс маркера?

Интерфейс без каких-либо членов - плохая практика?

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