Когда уместна перегрузка метода?

10

Предположим, я работаю над существующей, достаточно большой системой. У меня есть объект myObjectкласса MyClass(для примера, предположим, я работаю в Java). myObjectэто композиция, содержащая Collection, скажем, а Listи другие объекты, которые (я думаю) не имеют значения. Он содержит методы делегата, которые просто служат для вызова методов, из которых Listон состоит, для того, чтобы гарантировать, что Listон не раскрыт (извините, если я неправильно понял мою терминологию).

Допустим , что это Listявляется , List<String>но, по некоторым причинам, основной метод доступа является методом маски для класса SomeOtherClass. Если бы я хотел вставить новую пару значений в свой List, то у меня был бы объект SomeOtherClassвызванного someObject. Я бы позвонил, myObject.insert(someObject)и внутри insertметода была бы какая-то магия, которая бы извлекала и Stringпомещала в List<String>.

Предположим теперь, что у меня есть только Stringзначение и нет SomeOtherClassобъекта для вставки. Предполагая, что я не могу изменить insertметод, потому что он сломает все в этой системе. Тогда я должен перегрузить insertметод? Или я должен создавать новый объект SomeOtherClassкаждый раз, когда я хочу позвонить insert?

Я думаю, если бы я перегрузил его, это выглядело бы примерно так ...

public void insert(String s) {
    ...
}

public void insert(SomeOtherObject obj) {
    this.insert(obj.magicStringMethod());
}

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

Будет ли это подходящим местом для перегрузки метода? Если нет, когда я должен перегружать метод?

blahman
источник
Предполагая Java, вы задумывались об использовании Generics для решения этой проблемы? Я думаю, что я действительно спрашиваю, что на самом деле представляет String? Если это на самом деле представление реального доменного объекта, то есть еще один потенциальный способ решения этой проблемы
Мартейн Вербург
@MartijnVerburg У меня нет, на данном этапе. Поскольку я всего лишь стажер, я все еще немного незнаком с такими вещами, как шаблоны проектирования, и у меня пока нет хороших привычек в области дизайна. В ответ на ваш второй вопрос, это представление фактического объекта домена. magicStringMethod()в моем примере, в моем случае, получить Stringпредставление о том, SomeOtherObjectчто был объект домена.
Blahman
Я предлагаю вам держаться подальше от перегрузки, когда вы можете. Это иногда вызывает путаницу. Лучше быть уверенным в вызываемом методе во время разработки. Смотрите это: programmers.stackexchange.com/questions/132369/…
NoChance

Ответы:

7

Вы перегружены, когда хотите поддерживать разные типы:

public overload void MyMethod(int value)
{ 
}

public overload void MyMethod(bool value)
{
}

public overload void MyMethod(string value)
{
}

или для поддержки прогрессивного интерфейса с использованием разных списков параметров:

public overload void MyOtherMethod()
{
    this.MyOtherMethod(DefaultValue);
}

public overload void MyOtherMethod(int value)
{
    this.MyOtherMethod(value, DefaultOtherValue);
}

public overload void MyOtherMethod(int value, bool otherValue)
{
    ...
}

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

S.Robins
источник
7

Я бы сказал, что перегрузка уместна, когда оба метода семантически эквивалентны. Чтобы украсть из примеров Dukeofgaming:

Они перегружены соответственно:

public int sum(int a, int b){
    return a+b;
}

public double sum(double a, double b){
    return a+b;
}

Это не:

public int sum(int a, int b){
    return a+b;
}

public double sum(double a, double b){
    return a-b;
}

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

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

Гьян ака Гари Буйн
источник
1
Спасибо за ответ. Я спрашивал кого-то постарше, но, поскольку код находится в довольно интересном состоянии, их решение было не совсем уверенным, но, тем не менее, реализованным (это не было перегрузкой).
Blahman
5

По существу, чтобы параметры метода диктовали, как метод будет вести себя.

Быстрый пример будет:

class Calc{
    //...
    public int sum(int a, int b){
        return a+b;
    }
    public double sum(double a, double b){
        return a+b;
    }
    //...
}

Если вы передадите методу sum (a, b) пару целых чисел, он знает, что он должен вызвать первую реализацию, поскольку вызов метода совпадает с сигнатурой метода (т. Е. Double sum (double, double) не будет работать, если вы дадите метод суммы двух целых чисел).

Сигнатура вызова должна соответствовать доступным реализациям, поэтому попытка вызова sum (строка, строка) не будет работать, если у вас нет этого:

class Calc{
    //...
    public int sum(int a, int b){
        return a+b;
    }
    public double sum(double a, double b){
        return a+b;
    }
    public string sum(String a, String b){
        return "" + (Double.parseDouble(a) + Double.parseDouble(b));
    }
    //...
}

tl; dr: чтобы класс обрабатывал правильный метод в соответствии с параметрами, которые вы даете методу с тем же именем.

При наследовании это называется переопределением

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

Представьте, что у вас есть class Robotи называется метод fireAtTarget(Target target)... который вызывает fireWeaponA(target); fireWeaponB(target); fireWeaponC(target);один за другим. Вы также хотите иметь коллекцию с именем robot_army, в которую вы можете добавлять только объекты класса Robot. Робот стреляет из пулеметов по умолчанию в своих fireWeaponX()методах.

Тогда вы хотите иметь, class LazorRobotи class MissileRobotвы внедряете Robot заново? Нет, просто LazorRobot и MissileRobot наследуют Robot и перегружают каждый fireWeaponX()метод, чтобы использовать lazorz или ракеты, и у вас будет одинаковое поведение с различным оружием, без необходимости переопределения остальные методы.

tl; dr: чтобы поведение метода зависело от класса без нарушения интерфейса (robot_army принимает только Robot, но по расширению принимает любые классы, которые наследуют Robot).

dukeofgaming
источник
Ваш первый пример - переопределение . Когда вы поддерживаете интерфейс метода, измените поведение потомка. Подразумевается, что вы не собираетесь предлагать альтернативный метод. Ваш второй пример, однако, корректно перегружен. Если вы намерены выделить момент переопределения, а не перегрузку, вы можете сделать это более понятным в своем ответе, иначе весь when inheritingраздел, вероятно, не нужен. :)
S.Robins
Сумасшедший, кофеин действительно получил меня на этот раз = P, оставил вторую часть как первую и первую как вторую для общего интереса. Спасибо за исправление.
dukeofgaming
Если я правильно понимаю ваш ответ, я должен быть перегружен только тогда, когда просмотр параметров позволит мне сделать вывод о различиях в поведении, скажем, sum(String, String)и sum(int, int)?
Blahman
@blahman вам следует перегружать, когда вы хотите использовать разные типы, смесь типов или даже добавлять дополнительные параметры. Перегрузка предоставляет способ создания методов с параметрами по умолчанию, где синтаксис по умолчанию param = значение не поддерживается. Смотрите мой ответ, чтобы понять, что я имею в виду.
С.Робинс
1
@blahman Кстати, и по другому вопросу, когда у вас слишком много параметров и некоторые из них являются необязательными, иногда лучше просто отправить один объект или структуру данных, которая имеет все значения по умолчанию, поэтому вы изменяете атрибуты такого объекта или данных структура. Таким образом, вам не нужно создавать 20 версий одного и того же метода, просто чтобы каждый раз добавлять один параметр.
dukeofgaming