Czy istnieje sposób na wykonanie przeciążenia metod w języku TypeScript?


109

Czy istnieje sposób na wykonanie przeciążenia metod w języku TypeScript?

Chcę osiągnąć coś takiego:

class TestClass {
    someMethod(stringParameter: string): void {
        alert("Variant #1: stringParameter = " + stringParameter);
    }

    someMethod(numberParameter: number, stringParameter: string): void {
        alert("Variant #2: numberParameter = " + numberParameter + ", stringParameter = " + stringParameter);
    }
}

var testClass = new TestClass();
testClass.someMethod("string for v#1");
testClass.someMethod(12345, "string for v#2");

Oto przykład tego, czego nie chcę robić (naprawdę nienawidzę tej części przeładowywania hackowania w JS):

class TestClass {
    private someMethod_Overload_string(stringParameter: string): void {
        // A lot of code could be here... I don't want to mix it with switch or if statement in general function
        alert("Variant #1: stringParameter = " + stringParameter);
    }

    private someMethod_Overload_number_string(numberParameter: number, stringParameter: string): void {
        alert("Variant #2: numberParameter = " + numberParameter + ", stringParameter = " + stringParameter);
    }

    private someMethod_Overload_string_number(stringParameter: string, numberParameter: number): void {
        alert("Variant #3: stringParameter = " + stringParameter + ", numberParameter = " + numberParameter);
    }

    public someMethod(stringParameter: string): void;
    public someMethod(numberParameter: number, stringParameter: string): void;
    public someMethod(stringParameter: string, numberParameter: number): void;

    public someMethod(): void {
        switch (arguments.length) {
        case 1:
            if(typeof arguments[0] == "string") {
                this.someMethod_Overload_string(arguments[0]);
                return;
            }
            return; // Unreachable area for this case, unnecessary return statement
        case 2:
            if ((typeof arguments[0] == "number") &&
                (typeof arguments[1] == "string")) {
                this.someMethod_Overload_number_string(arguments[0], arguments[1]);
            }
            else if ((typeof arguments[0] == "string") &&
                     (typeof arguments[1] == "number")) {
                this.someMethod_Overload_string_number(arguments[0], arguments[1]);
            }
            return; // Unreachable area for this case, unnecessary return statement
        }
    }
}


var testClass = new TestClass();
testClass.someMethod("string for v#1");
testClass.someMethod(12345, "string for v#2");
testClass.someMethod("string for v#3", 54321);

1
Tak, jest szansa, język jeszcze nie umarł. Jakieś pytania?
hakre

6
@hakre To dziwna rzecz, biorąc pod uwagę, że TypeScript już obsługuje przeciążanie metod.
Svick

@svick: cóż, czy nazywasz to przeciążeniem metody? W twojej odpowiedzi sama metoda nie jest przeciążona, jedno ciało krąży wokół.
hakre

2
@hakre Specyfikacja nazywa to przeciążeniem metody. Z pewnością można argumentować, że nie jest to szczególnie ładna wersja tego, ale myślę, że nie można powiedzieć, że w ogóle nie istnieje.
Svick

@svick: Ja też nie powiedziałem. Ale wydaje mi się, że szanse, o które pyta OP, są specyficzne dla mentalnego modelu przeciążania metod. Do podziału włosów można powiedzieć, że jest to przeciążenie sygnatury metody;)
hakre

Odpowiedzi:


165

Zgodnie ze specyfikacją, TypeScript obsługuje przeciążanie metod, ale jest to dość niewygodne i zawiera wiele ręcznej pracy sprawdzającej typy parametrów. Myślę, że dzieje się tak głównie dlatego, że najbliższe przeciążenie metod w zwykłym JavaScript obejmuje również sprawdzanie, a TypeScript stara się nie modyfikować rzeczywistych treści metody, aby uniknąć niepotrzebnych kosztów wydajności w czasie wykonywania.

Jeśli dobrze to rozumiem, musisz najpierw napisać deklarację metody dla każdego z przeciążeń, a następnie jedną implementację metody, która sprawdza jej argumenty, aby zdecydować, które przeciążenie zostało wywołane. Podpis implementacji musi być zgodny ze wszystkimi przeciążeniami.

class TestClass {
    someMethod(stringParameter: string): void;
    someMethod(numberParameter: number, stringParameter: string): void;

    someMethod(stringOrNumberParameter: any, stringParameter?: string): void {
        if (stringOrNumberParameter && typeof stringOrNumberParameter == "number")
            alert("Variant #2: numberParameter = " + stringOrNumberParameter + ", stringParameter = " + stringParameter);
        else
            alert("Variant #1: stringParameter = " + stringOrNumberParameter);
    }
}

3
@NicoVanBelle JavaScript w ogóle nie obsługuje przeciążania metod, prawda? Jak więc powrót do JS może pomóc?
svick

A co ze sprawdzaniem interfejsów? Czy masz lepsze rozwiązanie niż to: stackoverflow.com/questions/14425568/… ?
DiPix

hmm .. naprawdę mi się to nie podoba, po prostu mając opcjonalny parametr, lepiej przeczytać.
LuckyLikey

3
Myślę, że ważne jest, aby kod był czytelny bez mnóstwa sprawdzania typów instrukcji if. Kogo obchodzi, w co się przenosi.
Brain2000

34

Zaktualizuj dla jasności. Przeciążanie metod w języku TypeScript jest przydatną funkcją, ponieważ umożliwia tworzenie definicji typów dla istniejących bibliotek z interfejsem API, który musi być reprezentowany.

Pisząc własny kod, możesz jednak uniknąć przeciążeń poznawczych przy użyciu parametrów opcjonalnych lub domyślnych. Jest to bardziej czytelna alternatywa dla przeciążeń metod, a także zapewnia uczciwość interfejsu API, ponieważ unikniesz tworzenia przeciążeń przy nieintuicyjnym porządkowaniu.

Ogólne prawo dotyczące przeciążeń TypeScript to:

Jeśli możesz usunąć podpisy przeciążenia i wszystkie testy przejdą pomyślnie, nie potrzebujesz przeciążeń TypeScript

Zwykle można osiągnąć to samo z opcjonalnymi lub domyślnymi parametrami - lub z typami unii lub z odrobiną orientacji obiektowej.

Rzeczywiste pytanie

Rzeczywiste pytanie dotyczy przeciążenia:

someMethod(stringParameter: string): void {

someMethod(numberParameter: number, stringParameter: string): void {

Teraz nawet w językach, które obsługują przeciążenia z oddzielnymi implementacjami (uwaga: przeciążenia TypeScript współużytkują jedną implementację) - programiści radzą, aby zapewnić spójność w kolejności. To sprawiłoby, że podpisy:

someMethod(stringParameter: string): void {

someMethod(stringParameter: string, numberParameter: number): void {

stringParameterZawsze jest wymagane, więc to idzie pierwszy. Możesz napisać to jako działające przeciążenie TypeScript:

someMethod(stringParameter: string): void;
someMethod(stringParameter: string, numberParameter: number): void;
someMethod(stringParameter: string, numberParameter?: number): void {
    if (numberParameter != null) {
        // The number parameter is present...
    }
}

Ale zgodnie z prawem przeciążeń TypeScript możemy usunąć sygnatury przeciążenia, a wszystkie nasze testy będą nadal działać.

someMethod(stringParameter: string, numberParameter?: number): void {
    if (numberParameter != null) {
        // The number parameter is present...
    }
}

Rzeczywiste pytanie w rzeczywistym zamówieniu

Gdybyś był zdecydowany pozostać przy pierwotnej kolejności, przeciążenia byłyby następujące:

someMethod(stringParameter: string): void;
someMethod(numberParameter: number, stringParameter: string): void;
someMethod(a: string | number, b?: string | number): void {
  let stringParameter: string;
  let numberParameter: number;

  if (typeof a === 'string') {
    stringParameter = a;
  } else {
    numberParameter = a;

    if (typeof b === 'string') {
      stringParameter = b;
    }
  }
}

To dużo rozgałęziania, aby dowiedzieć się, gdzie umieścić parametry, ale naprawdę chciałeś zachować tę kolejność, jeśli czytasz tak daleko ... ale czekaj, co się stanie, jeśli zastosujemy prawo przeciążeń TypeScript?

someMethod(a: string | number, b?: string | number): void {
  let stringParameter: string;
  let numberParameter: number;

  if (typeof a === 'string') {
    stringParameter = a;
  } else {
    numberParameter = a;

    if (typeof b === 'string') {
      stringParameter = b;
    }
  }
}

Już wystarczająco dużo rozgałęzień

Oczywiście, biorąc pod uwagę ilość sprawdzanych typów, które musimy wykonać ... może najlepszą odpowiedzią jest po prostu wybranie dwóch metod:

someMethod(stringParameter: string): void {
  this.someOtherMethod(0, stringParameter);
}

someOtherMethod(numberParameter: number, stringParameter: string): void {
  //...
}

to jest powszechnie znane jako przeciążanie metod. zobacz także pytanie, rodzaj pierwszego parametru zmienia się.
hakre

3
Przyznałem, że w mojej odpowiedzi - opcjonalny parametr umieściłbyś na końcu, więc numberparametr byłby drugim argumentem i byłby opcjonalny. TypeScript nie obsługuje „właściwych” przeciążeń metod - ale nawet świat C # odchodzi od przeciążeń w kierunku parametrów opcjonalnych, ponieważ w wielu przypadkach prowadzi to do bardziej czytelnego kodu.
Fenton

Masz na myśli if (typeof numberParameter != 'undefined'), prawda;)
Juan Mendes

To zależy od twojego przypadku użycia, w szczególności od tego, czy akceptujesz zera. Jeśli musisz to zrobić, pamiętaj, !==aby unikać żonglerki.
Fenton

1
@Sebastian, to brzmi jak szansa na zastrzyk zależności. Biorąc pod uwagę, że przeciążenie metody w języku TypeScript obejmuje jedną metodę z wieloma dekoracjami, parametrami domyślnymi i typami unii, zapewnia lepsze wrażenia. Jeśli metody różnią się w bardziej istotny sposób, możesz użyć abstrakcji lub zaimplementować wiele metod.
Fenton,

7

Chciałbym. Chcę też tej funkcji, ale TypeScript musi być kompatybilny z nietypowym JavaScriptem, który nie ma przeciążonych metod. tzn. jeśli przeciążona metoda jest wywoływana z JavaScript, może zostać wysłana tylko do jednej implementacji metody.

Jest kilka istotnych dyskusji na temat codeplex. na przykład

https://typescript.codeplex.com/workitem/617

Nadal uważam, że TypeScript powinien generować wszystkie if'ing i przełączanie, abyśmy nie musieli tego robić.


2

Dlaczego nie użyć opcjonalnego interfejsu zdefiniowanego przez właściwość jako argumentu funkcji.

W przypadku tego pytania użycie wbudowanego interfejsu zdefiniowanego tylko z niektórymi opcjonalnymi właściwościami mogłoby bezpośrednio utworzyć kod podobny do poniższego:

class TestClass {

    someMethod(arg: { stringParameter: string, numberParameter?: number }): void {
        let numberParameterMsg = "Variant #1:";
        if (arg.numberParameter) {
            numberParameterMsg = `Variant #2: numberParameter = ${arg.numberParameter},`;
        }
        alert(`${numberParameterMsg} stringParameter = ${arg.stringParameter}`);
    }
}

var testClass = new TestClass();
testClass.someMethod({ stringParameter: "string for v#1" });
testClass.someMethod({ numberParameter: 12345, stringParameter: "string for v#2" });

Ponieważ przeciążenie zawarte w TypeScript jest, jak wspomniano w komentarzach innych, tylko listą różnych sygnatur funkcji bez obsługi odpowiednich kodów implementacji, takich jak inne języki statyczne. Tak więc implementacja nadal musi być wykonana tylko w jednym ciele funkcji, co sprawia, że ​​użycie przeciążania funkcji w Typescript nie jest tak wygodne, jak takie języki obsługujące rzeczywistą funkcję przeciążania.

Jednak nadal istnieje wiele nowych i wygodnych elementów zawartych w skrypcie, które nie są dostępne w starszym języku programowania, gdzie opcjonalna obsługa właściwości w anonimowym interfejsie jest takim podejściem, aby sprostać wygodnej strefie przeciążenia starszej funkcji, jak sądzę.


0

Jeśli istnieje wiele odmian przeciążeń metod, innym sposobem jest po prostu utworzenie klasy ze wszystkimi argumentami wewnątrz. Możesz więc przekazać tylko wybrany parametr w dowolnej kolejności.

class SomeMethodConfig {
  stringParameter: string;
  numberParameter: number;

 /**
 *
 */
 constructor(stringParameter: string = '012', numberParameter?: number) { // different ways to make a param optional
   this.numberParameter = 456; // you can put a default value here
   this.stringParameter = stringParameter; // to pass a value throw the constructor if necessary
 }
}

Możesz także utworzyć konstruktor z wartościami domyślnymi i / lub przekazać kilka obowiązkowych argumentów. Następnie użyj w ten sposób:

const config = new SomeMethodConfig('text');
config.numberParameter = 123; // initialize an optional parameter only if you want to do it
this.SomeMethod(config);

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.