Programmēšana

Kā orientēties maldinoši vienkāršajā Singletona modelī

Singleton modelis ir maldinoši vienkāršs, pat un it īpaši Java izstrādātājiem. Šajā klasikā JavaWorld raksts, David Geary parāda, kā Java izstrādātāji īsteno vieniniekus, izmantojot koda piemērus daudzsavienošanai, klases ielādētājiem un sērijveidošanai, izmantojot Singleton modeli. Viņš noslēdz ieskatu atsevišķu reģistru ieviešanā, lai izpildes laikā norādītu vieniniekus.

Dažreiz ir pareizi, ja klasē ir tieši viens gadījums: logu pārvaldnieki, drukas spolētāji un failu sistēmas ir prototipiski piemēri. Parasti šiem objektu veidiem, kas pazīstami kā vieninieki, visā programmatūras sistēmā piekļūst atšķirīgi objekti, un tāpēc tiem ir nepieciešams globāls piekļuves punkts. Protams, tikai tad, kad esat pārliecināts, ka nekad nevajadzēs vairāk nekā vienu gadījumu, tas ir labs solījums, ka jūs mainīsit savas domas.

Singleton dizaina modelis novērš visas šīs problēmas. Izmantojot Singleton dizaina modeli, varat:

  • Pārliecinieties, vai ir izveidots tikai viens klases gadījums
  • Nodrošiniet globālu piekļuves punktu objektam
  • Nākotnē atļaujiet vairākus gadījumus, neietekmējot atsevišķas klases klientus

Lai gan Singleton dizaina modelis - kā to pierāda zemāk redzamais attēls - ir viens no vienkāršākajiem dizaina modeļiem, tas neuzmanīgajam Java izstrādātājam rada vairākas nepilnības. Šajā rakstā ir apspriests Singleton dizaina modelis un aplūkoti šie trūkumi.

Vairāk par Java dizaina modeļiem

Jūs varat izlasīt visus David Geary Java Design Patterns kolonnasvai skatiet JavaWorld sarakstu jaunākie raksti par Java dizaina modeļiem. Skatīt "Dizaina modeļi, kopaina"diskusijai par četru modeļu bandas izmantošanas plusi un mīnusi. Vai vēlaties vairāk? Iegūstiet Enterprise Java biļetenu savā iesūtnē.

Singletona modelis

In Dizaina modeļi: atkārtoti lietojamas objektorientētas programmatūras elementi, Četru banda Singletona modeli raksturo šādi:

Pārliecinieties, ka klasē ir tikai viens gadījums, un nodrošiniet tai globālu piekļuves punktu.

Zemāk redzamais attēls ilustrē Singleton dizaina modeļa klases diagrammu.

Kā redzat, Singleton dizaina modelim nav daudz. Singletoni saglabā statisku atsauci uz vienīgo vienreizējo instanci un atgriež atsauci uz šo gadījumu no statiskās instance () metodi.

1. piemērā parādīta klasiska Singleton dizaina modeļa ieviešana:

1. piemērs. Klasiskais vienskaitlis

publiskā klase ClassicSingleton {private static ClassicSingleton instance = null; aizsargāts ClassicSingleton () {// eksistē tikai, lai uzvarētu instantiation. } public static ClassicSingleton getInstance () {if (instance == null) {instance = new ClassicSingleton (); } atgriešanās instance; }}

1. piemērā ieviestais vienskaitlis ir viegli saprotams. The ClassicSingleton klase saglabā statisku atsauci uz vientuļo vieninieka gadījumu un atgriež šo atsauci no statiskās getInstance () metodi.

Ir vairākas interesantas lietas, kas attiecas uz ClassicSingleton klasē. Pirmkārt, ClassicSingleton izmanto tehniku, kas pazīstama kā slinks acumirklis izveidot vienīgo; kā rezultātā vienskaitļa gadījums tiek izveidots tikai pēc getInstance () metode tiek izsaukta pirmo reizi. Šis paņēmiens nodrošina to, ka vienreizējie gadījumi tiek veidoti tikai nepieciešamības gadījumā.

Otrkārt, pamaniet to ClassicSingleton ievieš aizsargātu konstruktoru, lai klienti nevarētu instantiate ClassicSingleton gadījumi; tomēr jūs varat būt pārsteigts, atklājot, ka šāds kods ir pilnīgi likumīgs:

publiskā klase SingletonInstantiator {public SingletonInstantiator () {ClassicSingleton instance = ClassicSingleton.getInstance (); ClassicSingleton anotherInstance =jauns ClassicSingleton (); ... } }

Kā var fragmenta iepriekšējā koda fragments - kas nepagarina ClassicSingleton—Izveidojiet a ClassicSingleton piemēram, ja ClassicSingleton konstruktors ir aizsargāts? Atbilde ir tāda, ka aizsargātos konstruktorus var izsaukt pēc apakšklasēm un pa citām klasēm vienā iepakojumā. Tā kā ClassicSingleton un SingletonInstantiator ir vienā paketē (noklusējuma pakete), SingletonInstantiator () metodes var radīt ClassicSingleton gadījumi. Šai dilemmai ir divi risinājumi: jūs varat izveidot ClassicSingleton konstruktors privāts, lai tikai KlasikaSingleton () metodes to sauc; tomēr tas nozīmē ClassicSingleton nevar apakšklasē. Dažreiz tas ir vēlams risinājums; ja tā, tā ir laba ideja pasludināt savu klasi galīgais, kas padara šo nodomu skaidru un ļauj kompilatoram piemērot veiktspējas optimizāciju. Otrs risinājums ir ievietot vienklases klasi nepārprotamā paketē, tāpēc citās paketēs (ieskaitot noklusējuma paketes) esošās klases nevar noteikt vienreizējus gadījumus.

Trešais interesants jautājums par ClassicSingleton: ir iespējami vairāki vienreizēji gadījumi, ja dažādu klases ielādētāju ielādētas klases piekļūst vieniniekam. Šis scenārijs nav tik tāls. piemēram, dažos servletu konteineros katram servletam tiek izmantoti atšķirīgi klases krāvēji, tādēļ, ja divi servleti piekļūst vienskaitlim, viņiem katram būs savs gadījums.

Ceturtkārt, ja ClassicSingleton īsteno java.io.Serializējams saskarni, klases gadījumus var seriālizēt un deserializēt. Tomēr, ja jūs sērijveidojat atsevišķu objektu un pēc tam deserializējat šo objektu vairāk nekā vienu reizi, jums būs vairāki vienreizēja gadījumi.

Visbeidzot, un, iespējams, vissvarīgākais, 1. piemērs ClassicSingleton klase nav droša ar diegu. Ja divi pavedieni - mēs tos sauksim par 1. un 2. pavedienu - zvana ClassicSingleton.getInstance () tajā pašā laikā divi ClassicSingleton gadījumus var izveidot, ja 1. pavediens tiek novērsts tūlīt pēc tā ievadīšanas ja bloks un vadība pēc tam tiek piešķirta 2. pavedienam.

Kā redzams no iepriekšējās diskusijas, kaut arī Singleton modelis ir viens no vienkāršākajiem dizaina modeļiem, tā ieviešana Java ir nekas cits kā vienkāršs. Šajā rakstā ir apskatīti Java specifiskie apsvērumi attiecībā uz Singleton modeli, taču vispirms veiksim nelielu apkārtni, lai uzzinātu, kā jūs varat pārbaudīt savas singletona klases.

Pārbaudiet vieniniekus

Visā pārējā šī raksta daļā es izmantoju JUnit kopā ar log4j, lai pārbaudītu vienklases nodarbības. Ja neesat pazīstams ar JUnit vai log4j, skatiet resursus.

2. piemērā ir uzskaitīts JUnit testa gadījums, kurā tiek pārbaudīts 1. piemēra vienskaitlis:

2. piemērs. Vienskaitļa testa gadījums

importēt org.apache.log4j.Logger; importēt junit.framework.Assert; importēt junit.framework.TestCase; publiskā klase SingletonTest paplašina TestCase {private ClassicSingleton sone = null, stwo = null; privāts statiskais reģistrētāja reģistrētājs = Logger.getRootLogger (); publiskais SingletonTest (virknes nosaukums) {super (nosaukums); } public void setUp () {logger.info ("kļūst viens pats ..."); sone = ClassicSingleton.getInstance (); logger.info ("... got singleton:" + sone); logger.info ("kļūst viens pats ..."); stwo = ClassicSingleton.getInstance (); logger.info ("... got singleton:" + stwo); } public void testUnique () {logger.info ("pārbaudīt vienādības vienādību"); Assert.assertEquals (true, sone == stwo); }}

Izmanto 2. piemēra testa gadījumu ClassicSingleton.getInstance () divreiz un atgriezušās atsauces glabā dalībnieku mainīgajos. The testUnique () metode pārbauda, ​​vai atsauces ir identiskas. 3. piemērā parādīts, ka testa gadījuma rezultāts:

3. piemērs

Buildfile: build.xml init: [echo] Build 20030414 (14.04.2003. 03:08) compile: run-test-text: [java]. INFO galvenais: kļūst viens... [java] INFO galvenais: izveidots viens: Singleton @ e86f41 [java] INFO galvenais: ... got singleton: Singleton @ e86f41 [java] INFO galvenais: kļūst viens... [java] INFO main: ... got singleton: Singleton @ e86f41 [java] INFO main: singletonu pārbaude par līdztiesību [java] Laiks: 0,032 [java] Labi (1 tests)

Kā parādīts iepriekšējā sarakstā, 2. piemēra vienkāršais tests tiek nokārtots ar krāsainām krāsām - abas atsevišķās atsauces iegūtas ar ClassicSingleton.getInstance () ir patiešām identiski; tomēr šīs atsauces tika iegūtas vienā pavedienā. Nākamajā sadaļā stresa pārbaude mūsu vienreizējai klasei ar vairākiem pavedieniem.

Apsvērumi par daudzsavienojumu

1. piemērs ClassicSingleton.getInstance () metode nav droša diegiem šāda koda dēļ:

1: if (instance == null) {2: instance = new Singleton (); 3:}

Ja pirms uzdevuma veikšanas 2. Rindā pavediens ir novērsts, instancē locekļa mainīgais joprojām būs nulle, un pēc tam var ievadīt vēl vienu pavedienu ja bloķēt. Tādā gadījumā tiks izveidotas divas atšķirīgas vienreizējas lietas. Diemžēl šāds scenārijs notiek reti, tāpēc testēšanas laikā to ir grūti izveidot. Lai ilustrētu šo krievu ruletes pavedienu, esmu piespiedis šo problēmu, atkārtoti papildinot 1. piemēra klasi. 4. piemērā parādīta pārskatītā vienklases klase:

4. piemērs. Sakraujiet klāju

importēt org.apache.log4j.Logger; publiskā klase Singleton {private static Singleton singleton = null; privāts statiskais reģistrētāja reģistrētājs = Logger.getRootLogger (); privāts statiskais būla firstThread = patiess; aizsargāts Singletons () {// eksistē tikai, lai pieveiktu instantiāciju. } publiskais statiskais Singletons getInstance () { if (singleton == null) {simulētRandomActivity (); singletons = jauns Singletons (); } logger.info ("izveidots vienskaitlis:" + vienskaitlis); atgriešanās vienskaitlis; } private static void simulētRandomActivity() {mēģiniet { ja (firstThread) {firstThread = nepatiesa; logger.info ("guļ ..."); // Šim napam vajadzētu dot pietiekami daudz laika otrajam pavedienam // tikt cauri pirmajam pavedienam.Thread.currentThread (). Gulēt (50); }} catch (InterruptedException ex) {logger.warn ("Miega režīms pārtraukts"); }}}

4. piemēra vienskaitlis atgādina 1. piemēra klasi, izņemot to, ka iepriekšējā saraksta vienskaitlis sakrauj klāju, lai piespiestu vairāku pavedienu kļūdu. Pirmo reizi getInstance () metodi sauc, pavediens, kas izsauca metodi, guļ 50 milisekundes, kas dod citu pavedienu laiku, lai izsauktu getInstance () un izveidojiet jaunu vienreizēju instanci. Kad miega pavediens pamostas, tas rada arī jaunu vienreizēju gadījumu, un mums ir divi vienreizējie gadījumi. Lai gan 4. piemēra klase ir izdomāta, tā stimulē reālo situāciju, kurā pirmais pavediens, kas izsauc getInstance () tiek novērsts.

5. piemērs pārbauda 4. piemēra vienīgo:

5. piemērs. Pārbaude, kas neizdodas

importēt org.apache.log4j.Logger; importēt junit.framework.Assert; importēt junit.framework.TestCase; publiskā klase SingletonTest paplašina TestCase {private static Logger logger = Logger.getRootLogger (); privāts statiskais Singletons vienskaitlis = nulle; publiskais SingletonTest (virknes nosaukums) {super (nosaukums); } public void setUp () { singleton = null; } public void testUnique () izmet InterruptedException {// Abi pavedieni izsauc Singleton.getInstance (). Thread threadOne = new Thread (jauns SingletonTestRunnable ()), threadTwo = jauns Thread (jauns SingletonTestRunnable ()); threadOne.start ();threadTwo.start (); threadOne.join (); threadTwo.join (); } privāta statiskā klase SingletonTestRunnable īsteno Runnable {public void run () {// Iegūstiet atsauci uz singlu. Singleton s = Singleton.getInstance (); // Aizsargājiet atsevišķa locekļa mainīgo no // daudzjoslu piekļuves. sinhronizēts (SingletonTest.class) {if (singleton == null) // Ja vietējā atsauce ir nulle ... singletons = s; // ... iestatiet to uz singleton} // Vietējai atsaucei jābūt vienādai ar Singleton vienu un // vienīgo gadījumu; pretējā gadījumā mums ir divas // Singleton instances. Assert.assertEquals (taisnība, s == vienskaitlis); } } }

5. piemēra testa gadījums izveido divus pavedienus, katru sāk un gaida, kamēr tie beigsies. Pārbaudes gadījumā tiek saglabāta statiska atsauce uz vienreizēju gadījumu, un katrs pavediens izsauc Singleton.getInstance (). Ja statiskā locekļa mainīgais nav iestatīts, pirmais pavediens to iestata kā vieninieku, kas iegūts ar izsaukumu uz getInstance (), un statisko locekļu mainīgo salīdzina ar lokālo mainīgo vienādībai.

Lūk, kas notiek, kad testa lieta darbojas: Pirmais pavediens izsauc getInstance (), ienāk ja bloķēt, un guļ. Pēc tam zvana arī otrais pavediens getInstance () un izveido atsevišķu gadījumu. Pēc tam otrais pavediens statisko locekļu mainīgo iestata tā izveidotajam gadījumam. Otrais pavediens pārbauda statiskā locekļa mainīgā un lokālās kopijas vienlīdzību, un pārbaude iztur. Kad pirmais pavediens pamostas, tas rada arī atsevišķu gadījumu, taču šis pavediens nenosaka statiskā locekļa mainīgo (jo otrais pavediens to jau ir iestatījis), tāpēc statiskais mainīgais un lokālais mainīgais nav sinhronizēti, un pārbaude par vienlīdzību neizdodas. 6. piemērā ir uzskaitīti 5. piemēra testa gadījuma rezultāti:

$config[zx-auto] not found$config[zx-overlay] not found