Перегрузки против общих аргументов - PullRequest
8 голосов
/ 24 августа 2009

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

а)

Parse(int data)
Parse(long data)
Parse(string data)
..etc

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

б)

Parse<T>(T data)

и затем используйте операторы ifs / switch с typeof (), чтобы попытаться определить, что это за типы и что с ними делать.

Что такое лучшая практика? Или какие идеи помогут мне выбрать между а) и б)?

Ответы [ 5 ]

20 голосов
/ 24 августа 2009

ИМХО, если вам нужны операторы if / switch, вам лучше перегрузить. Обобщения должны использоваться там, где реализация не зависит от конкретного типа, чтобы все еще использовать его.

Итак, как общее правило:

  • перегрузка, если для каждого типа будет отдельная реализация
  • используйте дженерики, если у вас может быть одна реализация, которая работает для всех возможных типов.
7 голосов
/ 24 августа 2009

Кодовый запах.

Если у вас есть " какой-то тип if / switch ", то это кодовый запах, который просто кричит полиморфизм Это говорит о том, что дженерики - это , а не решение этой проблемы. Обобщения должны использоваться, когда код не зависит от конкретных типов , которые вы передаете в него.

Просмотрите это видео Google Tech Talks: " Чистые беседы о коде - наследование, полиморфизм и тестирование ". Это касается конкретно того, о чем вы говорите.

4 голосов
/ 24 августа 2009

Шаблон, который вы описываете, когда использование обобщений приводит к множеству операторов ifs / switch, является анти-шаблоном.

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

Пример:

class SomeParser
{
    void Parse<T>(ParseStrategy<T> parseStrategy, T data)
    {
        //do some prep

        parseStrategy.Parse(data);

        //do something with the result;
    }
}

class ParseStrategy<T>
{
    abstract void Parse(T data);
}

class StringParser: ParseStrategy<String>
{
    override void Parse(String data)
    {
        //do your String parsing.
    }
}

class IntParser: ParseStrategy<int>
{
    override void Parse(int data)
    {
        //do your int parsing.
    }
}

//use it like so
[Test]
public void ParseTest()
{
    var someParser = new SomeParser();
    someParser.Parse(new StringParser(), "WHAT WILL THIS PARSE TO");
}

и тогда вы сможете перейти в любую из разработанных вами стратегий. Это позволит вам должным образом изолировать ваши проблемы по нескольким классам и не нарушать SRP (принцип единой ответственности.

2 голосов
/ 24 августа 2009

Одна проблема здесь - если вам требуется, чтобы операторы / switch заставляли генерики работать, у вас, вероятно, есть большая проблема. В этой ситуации наиболее вероятно, что универсальный аргумент не будет работать (правильно) для КАЖДОГО типа, только с фиксированным набором типов, с которыми вы работаете. В этом случае вам гораздо лучше обеспечить перегрузки для индивидуальной обработки определенных типов.

Это имеет много преимуществ:

  • Нет вероятности неправильного использования - ваш пользователь не может передать неверный аргумент
  • В вашем API есть больше ясности - очень очевидно, какие типы подходят
  • Общие методы более сложны в использовании и не так очевидны для начинающих пользователей
  • Использование универсального типа предполагает, что любой тип допустим - они действительно должны работать с каждым типом
  • Общий метод, скорее всего, будет медленнее, с точки зрения производительности

Если ваш аргумент МОЖЕТ работать с любым типом, это становится менее ясным. В этом случае я бы все еще рассматривал возможность включения перегрузок, а также универсальный резервный метод для типов. Это обеспечивает повышение производительности, когда вы передаете «ожидаемый» тип методу, но вы все равно можете работать с другими, не ожидаемыми типами.

1 голос
/ 24 августа 2009

Хотя для этого нет единого правила, подходящего для всех, универсальные шаблоны следует использовать, когда конкретный тип не имеет значения . Это не означает, что вы не можете ограничить тип, но этот конкретный тип на самом деле не имеет значения. В этом случае метод синтаксического анализа полностью зависит (и отличается для) каждого типа. Дженерики здесь не кажутся хорошим решением.

...