Java >> Java tutorial >  >> Tag >> class

OOP Alternativ til Utility Classes

En hjælpeklasse (alias hjælperklasse) er en "struktur", der kun har statiske metoder og ikke indkapsler nogen tilstand. StringUtils , IOUtils , FileUtils fra Apache Commons; Iterables og Iterators fra Guava og Files fra JDK7 er perfekte eksempler på hjælpeklasser.

Denne designidé er meget populær i Java-verdenen (såvel som C#, Ruby osv.), fordi værktøjsklasser giver almindelig funktionalitet, der bruges overalt.

Her ønsker vi at følge DRY princippet og undgå overlapning. Derfor placerer vi almindelige kodeblokke i hjælpeklasser og genbruger dem, når det er nødvendigt:

// This is a terrible design, don't reuse
public class NumberUtils {
  public static int max(int a, int b) {
    return a > b ? a : b;
  }
}

Det er faktisk en meget praktisk teknik!?

Utility Classes er onde

Men i en objektorienteret verden betragtes hjælpeklasser som en meget dårlig (nogle kan endda sige "forfærdelig") praksis.

Der har været mange diskussioner om dette emne; for at nævne nogle få:Er hjælperklasser onde? af Nick Malik, Why helper, singletons and utility classes are mostly bad af Simon Hart, Avoiding Utility Classes af Marshal Ward, Kill That Util Class! af Dhaval Dalal, Helper Classes Are A Code Smell af Rob Bagby.

Derudover er der et par spørgsmål på StackExchange om hjælpeklasser:Hvis en "Utilities"-klasse er ond, hvor skal jeg så placere min generiske kode?, Utility Classes er Evil.

En tør sammenfatning af alle deres argumenter er, at brugsklasser ikke er rigtige objekter; derfor passer de ikke ind i den objektorienterede verden. De blev arvet fra proceduremæssig programmering, mest fordi de fleste var vant til et funktionelt nedbrydningsparadigme dengang.

Forudsat at du er enig i argumenterne og ønsker at stoppe med at bruge hjælpeklasser, vil jeg vise som eksempel, hvordan disse væsner kan erstattes med rigtige objekter.

Procedureeksempel

Sig for eksempel, at du vil læse en tekstfil, opdele den i linjer, trimme hver linje og derefter gemme resultaterne i en anden fil. Dette kan gøres med FileUtils fra Apache Commons:

void transform(File in, File out) {
  Collection<String> src = FileUtils.readLines(in, "UTF-8");
  Collection<String> dest = new ArrayList<>(src.size());
  for (String line : src) {
    dest.add(line.trim());
  }
  FileUtils.writeLines(out, dest, "UTF-8");
}

Ovenstående kode kan se ren ud; dette er dog proceduremæssig programmering, ikke objektorienteret. Vi manipulerer data (bytes og bits) og instruerer eksplicit computeren, hvorfra de skal hentes og derefter, hvor de skal placeres på hver enkelt kodelinje. Vi er ved at definere en udførelsesprocedure .

Objektorienteret alternativ

I et objektorienteret paradigme bør vi instansiere og komponere objekter og dermed lade dem administrere data, hvornår og hvordan de ønske. I stedet for at kalde supplerende statiske funktioner bør vi oprette objekter, der er i stand til at afsløre den adfærd, vi søger:

public class Max implements Number {
  private final int a;
  private final int b;
  public Max(int x, int y) {
    this.a = x;
    this.b = y;
  }
  @Override
  public int intValue() {
    return this.a > this.b ? this.a : this.b;
  }
}

Dette procedurekald:

int max = NumberUtils.max(10, 5);

Bliver objektorienteret:

int max = new Max(10, 5).intValue();

Kartoffel, kartoffel? Ikke rigtig; læs lige videre...

Objekter i stedet for datastrukturer

Sådan ville jeg designe den samme filtransformerende funktionalitet som ovenfor, men på en objektorienteret måde:

void transform(File in, File out) {
  Collection<String> src = new Trimmed(
    new FileLines(new UnicodeFile(in))
  );
  Collection<String> dest = new FileLines(
    new UnicodeFile(out)
  );
  dest.addAll(src);
}

FileLines implementerer Collection<String> og indkapsler alle fillæsnings- og skriveoperationer. En forekomst af FileLines opfører sig nøjagtigt som en samling af strenge og skjuler alle I/O-operationer. Når vi gentager det - bliver en fil læst. Når vi addAll() til det - en fil bliver skrevet.

Trimmed implementerer også Collection<String> og indkapsler en samling af strenge (Decorator mønster). Hver gang den næste linje hentes, bliver den trimmet.

Alle klasser, der deltager i uddraget, er ret små:Trimmed , FileLines og UnicodeFile . Hver af dem er ansvarlige for sin egen enkeltfunktion og følger således princippet om enkelt ansvar.

På vores side, som brugere af biblioteket, er dette måske ikke så vigtigt, men for deres udviklere er det en nødvendighed. Det er meget nemmere at udvikle, vedligeholde og enhedsteste klasse FileLines i stedet for at bruge en readLines() metode i en 80+ metoder og 3000 linjers hjælpeklasse FileUtils . Seriøst, se på dens kildekode.

En objektorienteret tilgang muliggør doven eksekvering. in filen læses ikke, før dens data er påkrævet. Hvis vi undlader at åbne out på grund af en I/O-fejl vil den første fil ikke engang blive rørt. Hele showet starter først, når vi ringer til addAll() .

Alle linjer i det andet uddrag, undtagen den sidste, instansierer og komponerer mindre objekter til større. Denne objektsammensætning er ret billig for CPU'en, da den ikke forårsager nogen datatransformationer.

Udover det er det indlysende, at det andet script kører i O(1) space, mens det første kører i O(n). Dette er konsekvensen af ​​vores proceduremæssige tilgang til data i det første script.

I en objektorienteret verden er der ingen data; der er kun genstande og deres adfærd!

Relaterede indlæg

Du kan også finde disse indlæg interessante:

  • Hvorfor er NULL dårlig?
  • Undgå strengsammenkædning
  • Objekter bør være uforanderlige
  • Typiske fejl i Java-kode

Java tag