Java >> Java tutorial >  >> Java

At holde tingene TØRRE:Metodeoverbelastning

Et godt rent applikationsdesign kræver disciplin i at holde tingene TØRRE:Alt skal gøres én gang.
At skulle gøre det to gange er en tilfældighed.
At skulle gøre det tre gange er et mønster.

— En ukendt klog mand Hvis du nu følger Xtreme-programmeringsreglerne, ved du, hvad der skal gøres, når du støder på et mønster:refactor nådesløst, fordi vi alle ved, hvad der sker, når du ikke gør det:

Ikke TØRT:Metodeoverbelastning

En af de mindst TØRRE ting, du kan gøre, som stadig er acceptabel, er metodeoverbelastning – på de sprog, der tillader det (i modsætning til Ceylon, JavaScript). Da det er et internt domænespecifikt sprog, gør jOOQ API'en stor brug af overbelastning. Overvej typen Field (modellering af en databasekolonne):

public interface Field<T> {

    // [...]

    Condition eq(T value);
    Condition eq(Field<T> field);
    Condition eq(Select<? extends Record1<T>> query);
    Condition eq(QuantifiedSelect<? extends Record1<T>> query);

    Condition in(Collection<?> values);
    Condition in(T... values);
    Condition in(Field<?>... values);
    Condition in(Select<? extends Record1<T>> query);

    // [...]

}

Så i visse tilfælde er ikke-tørhed uundgåelig, også i et givet omfang i implementeringen af ​​ovenstående API. Den vigtigste tommelfingerregel her er dog altid at have så få implementeringer som muligt også for overbelastede metoder. Prøv at kalde en metode fra en anden. For eksempel er disse to metoder meget ens:

Condition eq(T value);
Condition eq(Field<T> field);

Den første metode er et specialtilfælde af den anden, hvor jOOQ-brugere ikke eksplicit ønsker at erklære en bind-variabel. Det er bogstaveligt talt implementeret som sådan:

@Override
public final Condition eq(T value) {
    return equal(value);
}

@Override
public final Condition equal(T value) {
    return equal(Utils.field(value, this));
}

@Override
public final Condition equal(Field<T> field) {
    return compare(EQUALS, nullSafe(field));
}

@Override
public final Condition compare(Comparator comparator, Field<T> field) {
    switch (comparator) {
        case IS_DISTINCT_FROM:
        case IS_NOT_DISTINCT_FROM:
            return new IsDistinctFrom<T>(this, nullSafe(field), comparator);

        default:
            return new CompareCondition(this, nullSafe(field), comparator);
    }
}

Som du kan se:
  • eq() er kun et synonym for den gamle equal() metode
  • equal(T) er en mere specialiseret, bekvemmelighedsform af equal(Field<T>)
  • equal(Field<T>) er en mere specialiseret, bekvemmelighedsform af compare(Comparator, Field<T>)
  • compare() giver endelig adgang til implementeringen af ​​denne API
Alle disse metoder er også en del af den offentlige API og kan kaldes af API-forbrugeren direkte, hvorfor nullSafe() kontrol gentages i hver metode.

Hvorfor al den ballade?

Svaret er enkelt.
  • Der er kun meget lille mulighed for en copy-paste-fejl i hele API'en.
  • ... fordi den samme API skal tilbydes for ne , gt , ge , lt , le
  • Uanset hvilken del af API'et der tilfældigvis er integrationstestet, er selve implementeringen bestemt dækket af en eller anden test.
  • På denne måde er det ekstremt nemt at give brugerne en meget rig API med masser af bekvemmelighedsmetoder, da brugerne ikke ønsker at huske, hvordan disse mere generelle metoder (som compare()) ) virker virkelig.
Det sidste punkt er særligt vigtigt, og på grund af risici relateret til bagudkompatibilitet, f.eks. ikke altid efterfulgt af JDK. For at oprette en Java 8 Stream fra en Iterator skal du gennemgå alt dette besvær, for eksempel:

// Aagh, my fingers hurt...
   StreamSupport.stream(iterator.spliterator(), false);
// ^^^^^^^^^^^^^                 ^^^^^^^^^^^    ^^^^^
//       |                            |           |
// Not Stream!                        |           |
//                                    |           |
// Hmm, Spliterator. Sounds like      |           |
// Iterator. But what is it? ---------+           |
//                                                |
// What's this true and false?                    |
// And do I need to care? ------------------------+

Når du intuitivt gerne vil have:

// Not Enterprise enough
iterator.stream();

Med andre ord vil subtile Java 8 Streams implementeringsdetaljer snart lække ind i en masse klientkode, og mange nye hjælpefunktioner vil pakke disse ting ind igen og igen. Se Brian Goetz' forklaring om Stack Overflow for detaljer. På bagsiden af ​​uddelegering af overbelastning implementeringer, er det selvfølgelig sværere (dvs. mere arbejde) at implementere sådan en API. Dette er særligt besværligt, hvis en API-leverandør også tillader brugere at implementere API'en selv (f.eks. JDBC). Et andet problem er længden af ​​stakspor, der genereres af sådanne implementeringer. Men vi har tidligere vist på denne blog, at dybe stakspor kan være et tegn på god kvalitet. Nu ved du hvorfor.

Takeaway

Takeaway er enkel. Når du støder på et mønster, refaktorér. Find den mest fællesnævner, indregn den i en implementering, og se, at denne implementering næsten aldrig bliver brugt ved at uddelegere enkeltansvarstrin fra metode til metode. Ved at følge disse regler vil du:
  • Har færre fejl
  • Har en mere praktisk API
Glædelig refaktorering!
Java tag