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

Statiske klasser versus Singleton-mønsteret i Java

1. Introduktion

I denne hurtige tutorial vil vi diskutere nogle fremtrædende forskelle mellem programmering til Singleton-designmønsteret og brug af statiske klasser i Java. Vi vil gennemgå begge kodningsmetoder og sammenligne dem med hensyn til forskellige aspekter af programmering.

Ved slutningen af ​​denne artikel vil vi være i stand til at træffe den rigtige beslutning, når vi skal vælge mellem de to muligheder.

2. Det grundlæggende

Lad os ramme nulpunktet. Singleton er et designmønster, der sikrer en enkelt forekomst af en Klasse i hele en applikations levetid.
Det giver også et globalt adgangspunkt til den instans.

statisk – et reserveret nøgleord – er en modifikator, der laver instansvariabler som klassevariable. Derfor bliver disse variable forbundet med klassen (med ethvert objekt). Når det bruges med metoder, gør det dem tilgængelige kun med klassenavnet. Endelig kan vi også oprette statiske indlejrede indre klasser.

I denne sammenhæng indeholder en statisk klasse statiske metoder og statiske variable .

3. Singleton Versus Static Utility Classes

Lad os nu gå ned i kaninhullet og forstå nogle fremtrædende forskelle mellem de to giganter. Vi begynder vores søgen med nogle objektorienterede koncepter.

3.1. Runtime Polymorphism

Statiske metoder i Java løses ved kompilering og kan ikke tilsidesættes under kørsel. Derfor kan en statisk klasse ikke rigtig drage fordel af runtime polymorfisme:

public class SuperUtility {

    public static String echoIt(String data) {
        return "SUPER";
    }
}

public class SubUtility extends SuperUtility {

    public static String echoIt(String data) {
        return data;
    }
}

@Test
public void whenStaticUtilClassInheritance_thenOverridingFails() {
    SuperUtility superUtility = new SubUtility();
    Assert.assertNotEquals("ECHO", superUtility.echoIt("ECHO"));
    Assert.assertEquals("SUPER", superUtility.echoIt("ECHO"));
}

I modsætning hertil kan singletoner udnytte runtime polymorfismen ligesom enhver anden klasse ved at udlede fra en basisklasse :

public class MyLock {

    protected String takeLock(int locks) {
        return "Taken Specific Lock";
    }
}

public class SingletonLock extends MyLock {

    // private constructor and getInstance method 

    @Override
    public String takeLock(int locks) {
        return "Taken Singleton Lock";
    }
}

@Test
public void whenSingletonDerivesBaseClass_thenRuntimePolymorphism() {
    MyLock myLock = new MyLock();
    Assert.assertEquals("Taken Specific Lock", myLock.takeLock(10));
    myLock = SingletonLock.getInstance();
    Assert.assertEquals("Taken Singleton Lock", myLock.takeLock(10));
}

Desuden kan singletoner også implementere grænseflader , hvilket giver dem en fordel i forhold til statiske klasser:

public class FileSystemSingleton implements SingletonInterface {

    // private constructor and getInstance method

    @Override
    public String describeMe() {
        return "File System Responsibilities";
    }
}

public class CachingSingleton implements SingletonInterface {

    // private constructor and getInstance method

    @Override
    public String describeMe() {
        return "Caching Responsibilities";
    }
}

@Test
public void whenSingletonImplementsInterface_thenRuntimePolymorphism() {
    SingletonInterface singleton = FileSystemSingleton.getInstance();
    Assert.assertEquals("File System Responsibilities", singleton.describeMe());
    singleton = CachingSingleton.getInstance();
    Assert.assertEquals("Caching Responsibilities", singleton.describeMe());
}

Singleton-scoped Spring Beans der implementerer en grænseflade er perfekte eksempler på dette paradigme.

3.2. Metodeparametre

Da det i bund og grund er et objekt, kan vi nemt videregive en singleton til andre metoder som argument:

@Test
public void whenSingleton_thenPassAsArguments() {
    SingletonInterface singleton = FileSystemSingleton.getInstance();
    Assert.assertEquals("Taken Singleton Lock", singleton.passOnLocks(SingletonLock.getInstance()));
}

Men det er værdiløst og en dårlig idé at skabe et statisk hjælpeklasseobjekt og sende det rundt i metoder.

3.3. Objekttilstand, serialisering og kloning

En singleton kan have instansvariabler, og ligesom ethvert andet objekt kan den opretholde en tilstand af disse variable:

@Test
public void whenSingleton_thenAllowState() {
    SingletonInterface singleton = FileSystemSingleton.getInstance();
    IntStream.range(0, 5)
        .forEach(i -> singleton.increment());
    Assert.assertEquals(5, ((FileSystemSingleton) singleton).getFilesWritten());
}

Ydermere kan en singleton serialiseres for at bevare sin tilstand eller for at blive overført over et medium , såsom et netværk:

new ObjectOutputStream(baos).writeObject(singleton);
SerializableSingleton singletonNew = (SerializableSingleton) new ObjectInputStream
   (new ByteArrayInputStream(baos.toByteArray())).readObject();

Endelig sætter eksistensen af ​​en instans også muligheden for at klone den ved hjælp af Objektets klonmetode:

@Test
public void whenSingleton_thenAllowCloneable() {
    Assert.assertEquals(2, ((SerializableCloneableSingleton) singleton.cloneObject()).getState());
}

Derimod har statiske klasser kun klassevariabler og statiske metoder, og derfor har de ingen objektspecifik tilstand. Da statiske medlemmer tilhører klassen, kan vi ikke serialisere dem. Også kloning er meningsløst for statiske klasser på grund af manglen på et objekt, der skal klones.

3.4. Indlæsningsmekanisme og hukommelsestildeling

Singletonen, som enhver anden forekomst af en klasse, lever på dyngen. Til dens fordel kan et stort singleton-objekt dovent indlæses, når det kræves af applikationen.

På den anden side omfatter en statisk klasse statiske metoder og statisk bundne variabler på kompileringstidspunktet og allokeres på stakken.
Derfor indlæses statiske klasser altid ivrigt på tidspunktet for klasseindlæsning i JVM.

3.5. Effektivitet og ydeevne

Som tidligere gentaget kræver statiske klasser ikke objektinitialisering. Dette fjerner overhead af den tid, der kræves til at oprette objektet.

Ved statisk binding på kompileringstidspunktet er de desuden mere effektive end singletons og har tendens til at være hurtigere.

Vi skal vælge singletons kun af designmæssige årsager og ikke som en enkelt instans løsning for effektivitet eller en præstationsgevinst.

3.6. Andre mindre forskelle

Programmering til en singleton i stedet for en statisk klasse kan også gavne mængden af ​​krævet refactoring.

Uden tvivl er en singleton et objekt i en klasse. Derfor kan vi nemt bevæge os væk fra det til en multi-instans verden af ​​en klasse.

Da statiske metoder påkaldes uden et objekt, men med klassenavnet, kan migrering til et miljø med flere forekomster være en relativt større refactor.

For det andet, i statiske metoder, da logikken er koblet til klassedefinitionen og ikke til objekterne, bliver et statisk metodekald fra objektet, der enhedstestes, sværere at blive hånet eller endda overskrevet af en dummy- eller stubimplementering.

4. At træffe det rigtige valg

Gå efter en singleton, hvis vi:

  • Kræv en komplet objektorienteret løsning til applikationen
  • Har kun brug for én forekomst af en klasse på alle givne tidspunkter og for at opretholde en tilstand
  • Ønsker en dovent indlæst løsning til en klasse, så den kun indlæses, når det kræves

Brug statiske klasser, når vi:

  • Du skal bare gemme mange statiske hjælpemetoder, der kun fungerer på inputparametre og ikke ændrer nogen intern tilstand
  • Du behøver ikke køretidspolymorfi eller en objektorienteret løsning

5. Konklusion

I denne artikel gennemgik vi nogle af de væsentlige forskelle mellem statiske klasser og Singleton-mønsteret i Java. Vi udledte også, hvornår vi skulle bruge en af ​​de to tilgange til udvikling af software.

Som altid kan vi finde den komplette kode på GitHub.


Java tag