Programavimas

Kaip naršyti apgaulingai paprastą „Singleton“ modelį

„Singleton“ modelis yra apgaulingai paprastas, netgi Java kūrėjams. Šioje klasikoje „JavaWorld“ Straipsnyje Davidas Geary parodo, kaip „Java“ kūrėjai įgyvendina pavienius elementus, pateikdami kodų pavyzdžius, susijusius su daugialypiu gijimu, klasių pakėlėjais ir serijiniu būdu, naudojant „Singleton“ modelį. Jis baigia pažvelgti į pavienių registrų diegimą, kad būtų galima nurodyti pavienius vienetus vykdymo metu.

Kartais tikslinga turėti tiksliai vieną klasės egzempliorių: langų tvarkyklės, spausdinimo kaupikliai ir failų sistemos yra prototipiniai pavyzdžiai. Paprastai prie tų tipų objektų, žinomų kaip pavieniai, prieigą gali pasiekti skirtingi objektai visoje programinės įrangos sistemoje, todėl jiems reikalingas visuotinis prieigos taškas. Žinoma, tik tada, kai būsite tikri, jums niekada nereikės daugiau nei vieno egzemplioriaus, tai gerai, jei pakeisite savo nuomonę.

„Singleton“ dizaino modelis išsprendžia visus šiuos klausimus. Naudodami „Singleton“ dizaino modelį galite:

  • Įsitikinkite, kad sukurtas tik vienas klasės egzempliorius
  • Pateikite visuotinį prieigos prie objekto tašką
  • Ateityje leiskite kelis atvejus, nepaveikdami pavienių klasės klientų

Nors „Singleton“ dizaino modelis - kaip parodyta žemiau esančiame paveikslėlyje - yra vienas iš paprasčiausių dizaino modelių, jis pateikia nemažai spąstų nepastebimam „Java“ kūrėjui. Šiame straipsnyje aptariamas „Singleton“ dizaino modelis ir sprendžiami šie trūkumai.

Daugiau apie „Java“ dizaino modelius

Galite perskaityti visus David Geary „Java Design Patterns“ stulpeliaiarba peržiūrėkite „JavaWorld“ sąrašą naujausi straipsniai apie „Java“ dizaino modelius. Matyti "Dizaino modeliai, bendras vaizdasNorėdami aptarti „Keturių grupių“ naudojimo privalumus ir trūkumus. Norite daugiau? Gaukite „Enterprise Java“ naujienlaiškį į savo pašto dėžutę.

„Singleton“ modelis

Į Dizaino modeliai: daugkartinio naudojimo objektų programinės įrangos elementai, Keturių gauja apibūdina Singletono modelį taip:

Užtikrinkite, kad klasėje yra tik vienas egzempliorius, ir suteikite visuotinį prieigos prie jo tašką.

Žemiau pateiktame paveiksle pavaizduota „Singleton“ dizaino modelio klasės schema.

Kaip matote, „Singleton“ dizaino rašte nėra daug. „Singletons“ palaiko statinę nuorodą į vienintelį pavienį atvejį ir pateikia nuorodą į tą atvejį iš statinio instancija() metodas.

1 pavyzdyje parodytas klasikinis „Singleton“ dizaino modelio įgyvendinimas:

1 pavyzdys. Klasikinis pavienis

viešoji klasė „ClassicSingleton“ {privati ​​statinė „ClassicSingleton“ egzempliorius = niekinis; apsaugotas „ClassicSingleton“ () {// egzistuoja tik tam, kad nugalėtų egzempliorių. } public static ClassicSingleton getInstance () {if (instance == null) {instance = new ClassicSingleton (); } grąžinimo instancija; }}

1 pavyzdyje įgyvendintas pavienis yra lengvai suprantamas. „ClassicSingleton“ klasė palaiko statinę nuorodą į vienišą pavienį egzempliorių ir grąžina tą nuorodą iš statinės „getInstance“ () metodas.

Yra keletas įdomių dalykų, susijusių su „ClassicSingleton“ klasė. Pirmas, „ClassicSingleton“ naudojama technika, žinoma kaip tingus akimirksnis sukurti pavienį; todėl pavienis egzempliorius nesukuriamas iki „getInstance“ () metodas yra vadinamas pirmą kartą. Ši technika užtikrina, kad pavieniai atvejai būtų kuriami tik tada, kai to reikia.

Antra, pastebėkite tai „ClassicSingleton“ įgyvendina saugomą konstruktorių, kad klientai negalėtų akimirksniu „ClassicSingleton“ egzemplioriai; tačiau galite nustebti sužinoję, kad šis kodas yra visiškai teisėtas:

viešoji klasė „SingletonInstantiator“ {public SingletonInstantiator () {ClassicSingleton instance = ClassicSingleton.getInstance (); „ClassicSingleton anotherInstance =“naujas „ClassicSingleton“ (); ... } }

Kaip gali fragmentas ankstesniame kode, kuris neprailgsta „ClassicSingleton“-sukurti „ClassicSingleton“ pavyzdžiui, jei „ClassicSingleton“ konstruktorius yra apsaugotas? Atsakymas yra tas, kad saugomus konstruktorius galima iškviesti poklasiais ir pagal kitas klases toje pačioje pakuotėje. Nes „ClassicSingleton“ ir „SingletonInstantiator“ yra tame pačiame pakete (numatytasis paketas), „SingletonInstantiator“ () metodai gali sukurti „ClassicSingleton“ atvejų. Ši dilema turi du sprendimus: galite padaryti „ClassicSingleton“ konstruktorius privatus, kad tik KlasikinisSingleton () metodai tai vadina; tačiau tai reiškia „ClassicSingleton“ negali būti klasifikuojamas. Kartais tai yra pageidautinas sprendimas; jei taip, verta paskelbti savo pavienę klasę galutinis, kuris aiškiai nurodo tą ketinimą ir leidžia kompiliatoriui pritaikyti našumo optimizavimą. Kitas sprendimas yra įdėti savo pavienių klasių į aiškų paketą, todėl kitų paketų (įskaitant numatytąjį paketą) klasės negali išskirti pavienių egzempliorių.

Trečias įdomus dalykas „ClassicSingleton“: galima turėti kelis pavienius egzempliorius, jei skirtingų klasių pakrovėjų įkeltos klasės pasiekia pavienį. Tas scenarijus nėra taip toli pasiektas; pavyzdžiui, kai kuriuose servletų talpyklose kiekvienam servletui naudojami skirtingi klasių pakrovėjai, taigi, jei dvi servletės pasiekia singletoną, kiekvienas turės savo egzempliorių.

Ketvirta, jei „ClassicSingleton“ įgyvendina java.io.Serializuojama sąsaja, klasės egzemplioriai gali būti nuoseklūs ir deserializuoti. Tačiau jei nuosekliai sujungsite pavienį objektą ir vėliau deserializuosite tą objektą daugiau nei vieną kartą, turėsite kelis pavienio atvejo atvejus.

Galiausiai ir bene svarbiausias 1 pavyzdys „ClassicSingleton“ klasė nėra saugi siūlams. Jei dvi gijos - mes jas vadinsime 1 gija ir 2 gija - skambins „ClassicSingleton.getInstance“ () tuo pačiu metu du „ClassicSingleton“ egzempliorių galima sukurti, jei 1-oji gija yra apsaugota nuo pat jos įvedimo jei blokavimas ir kontrolė vėliau suteikiama 2 gijai.

Kaip matote iš ankstesnės diskusijos, nors „Singleton“ modelis yra vienas iš paprasčiausių dizaino modelių, jį įgyvendinti „Java“ yra viskas, išskyrus paprastą. Likusioje šio straipsnio dalyje nagrinėjami „Java“ specifiniai „Singleton“ modelio aspektai, tačiau pirmiausia pasukime trumpą aplinkkelį, kad sužinotume, kaip galite išbandyti savo pavienio kurso klases.

Išbandykite pavienius vienetus

Visą likusią šio straipsnio dalį aš naudoju „JUnit“ kartu su „log4j“, norėdamas išbandyti pavienes klases. Jei nesate susipažinę su „JUnit“ ar „log4j“, žr.

2 pavyzdyje pateikiamas „JUnit“ bandymo atvejis, išbandantis 1 pavyzdžio pavienį variantą:

2 pavyzdys. Vienetinis bandymo atvejis

importuoti org.apache.log4j.Logger; importuoti junit.framework.Assert; importuoti junit.framework.TestCase; viešoji klasė „SingletonTest“ pratęsia „TestCase“ {private ClassicSingleton sone = null, stwo = null; privatus statinis registruotojas = Logger.getRootLogger (); public SingletonTest (String name) {super (vardas); } public void setUp () {logger.info ("gauti singleton ..."); sone = ClassicSingleton.getInstance (); logger.info ("... gavo singleton:" + sone); logger.info ("gauti singleton ..."); stwo = ClassicSingleton.getInstance (); logger.info ("... gavau pavienį:" + stwo); } public void testUnique () {logger.info ("tikrinti pavienius asmenis dėl lygybės"); „Assert.assertEquals“ (tiesa, sone == stwo); }}

2 pavyzdžio bandomasis atvejis remiasi „ClassicSingleton.getInstance“ () du kartus ir grąžintas nuorodas saugo narių kintamuosiuose. testUnique () metodas patikrina, ar nuorodos yra tapačios. 3 pavyzdyje parodyta, kad bandymo atvejis:

3 pavyzdys. Testo atvejis

„Buildfile“: build.xml init: [echo] Build 20030414 (2003-04-14 03:08) compile: run-test-text: [java]. INFO pagrindinis: gaunu pavienį... [java] INFO pagrindinis: sukūrė pavienį: Singleton @ e86f41 [java] INFO pagrindinis: ... gavo singleton: Singleton @ e86f41 [java] INFO pagrindinis: gaunu pavienį... [java] INFO pagrindinis: ... gavo singletoną: Singleton @ e86f41 [java] INFO pagrindinis: patikrinkite pavienių asmenų lygybę [java] Laikas: 0,032 [java] Gerai (1 testas)

Kaip iliustruoja ankstesnis sąrašas, 2 pavyzdžio paprastas testas išlaikomas skraidančiomis spalvomis - tai dvi atskiros nuorodos, gautos naudojant „ClassicSingleton.getInstance“ () iš tikrųjų yra identiški; tačiau tos nuorodos buvo gautos viena gija. Kitame skyriuje įtempiai išbandomi mūsų pavieniai užsiėmimai su keliais siūlais.

Daugiaslaidžiai svarstymai

1 pavyzdys „ClassicSingleton.getInstance“ () metodas nėra saugus siūlams dėl šio kodo:

1: if (egzempliorius == null) {2: egzempliorius = naujas Singletonas (); 3:}

Jei prieš atliekant užduotį 2 eilutėje yra užkerta kelią gijai, instancija narys kintamasis vis tiek bus niekinisir kita gija gali vėliau įvesti jei blokuoti. Tokiu atveju bus sukurti du skirtingi pavieniai atvejai. Deja, toks scenarijus pasitaiko retai, todėl bandant jį sunku pateikti. Norėdami iliustruoti šią Rusijos ruletės giją, aš priverčiau šią problemą atnaujinti 1 pavyzdžio klasę. 4 pavyzdyje rodoma pataisyta pavienio klasė:

4 pavyzdys. Paklokite kaladę

importuoti org.apache.log4j.Logger; viešoji klasė Singleton {private static Singleton singleton = null; privatus statinis registruotojas = Logger.getRootLogger (); privatus statinis loginis firstThread = tiesa; apsaugotas Singletonas () {// Egzistuoja tik norint nugalėti akimirksnį. } public static Singleton getInstance () { if (singleton == null) {imituotiRandomActivity (); singletonas = naujas Singletonas (); } logger.info ("sukurtas pavienis:" + pavienis); grįžti pavieniui; } privati ​​statinė tuštuma imituotiRandomActivity() { bandyti { jei (firstThread) {firstThread = klaidinga; logger.info („miega ...“); // Šis miegas turėtų suteikti pakankamai laiko antrajai gijai // apeiti pirmą giją.Thread.currentThread (). Miegas (50); }} catch (InterruptedException ex) {logger.warn ("Miego pertraukimas"); }}}

4 pavyzdžio pavienis elementas panašus į 1 pavyzdžio klasę, išskyrus tai, kad ankstesniame sąraše pateiktas pavienis vienetas sukrauna kaladę, kad priverstų sukti daugialypį gijimą. Pirmą kartą „getInstance“ () metodas vadinamas, gija, kuri pasikvietė metodą, miega 50 milisekundžių, o tai suteikia laiko kitam pokalbiui paskambinti „getInstance“ () ir sukurti naują pavienį egzempliorių. Pabudus miego siūlai, tai taip pat sukuria naują pavienį egzempliorių, o mes turime du pavienius atvejus. Nors 4 pavyzdžio klasė yra sugalvota, ji stimuliuoja realią situaciją, kai pirmoji gija, kuri skambina „getInstance“ () gauna iš anksto.

5 pavyzdys testuoja 4 pavyzdžio pavienį variantą:

5 pavyzdys. Nepavykęs bandymas

importuoti org.apache.log4j.Logger; importuoti junit.framework.Assert; importuoti junit.framework.TestCase; viešoji klasė „SingletonTest“ pratęsia „TestCase“ {private static Logger logger = Logger.getRootLogger (); privatus statinis Singletonas pavienis = nulis; public SingletonTest (String name) {super (vardas); } public void setUp () { singleton = null; } public void testUnique () meta InterruptedException {// Abi gijos iškviečia Singleton.getInstance (). Thread threadOne = nauja gija (nauja SingletonTestRunnable ()), threadTwo = nauja gija (nauja SingletonTestRunnable ()); threadOne.start ();threadTwo.start (); threadOne.join (); threadTwo.join (); } privati ​​statinė klasė „SingletonTestRunnable“ įgyvendina „Runnable“ {public void run () {// Gaukite nuorodą į singletoną. Singleton s = Singleton.getInstance (); // Apsaugokite pavienio nario kintamąjį nuo // kelių gijų prieigos. sinchronizuotas („SingletonTest.class“) {if (singleton == null) // Jei vietinė nuoroda yra niekinė ... singletonas = s; // ... nustatykite jį į singletoną} // Vietinė nuoroda turi būti lygi vieninteliam ir / arba vieninteliam Singletono egzemplioriui; kitaip mes turime du // Singletono atvejus. „Assert.assertEquals“ (tiesa, s == pavienis); } } }

5 pavyzdžio bandymo atvejis sukuria dvi gijas, pradeda kiekvieną ir laukia, kol jos baigsis. Bandomasis atvejis palaiko statinę nuorodą į pavienį egzempliorių ir kiekviena gija iškviečia Singleton.getInstance (). Jei statinio nario kintamasis nebuvo nustatytas, pirmoji gija nustato jį kaip singlą, gautą iškvietus „getInstance“ (), o statinio nario kintamasis lyginamas su vietiniu kintamuoju.

Štai kas atsitinka, kai bandomasis atvejis paleidžiamas: iškviečiami pirmieji siūlai „getInstance“ (), įeina į jei blokuoti ir miega. Vėliau skambina ir antroji gija „getInstance“ () ir sukuria pavienį egzempliorių. Tada antroji gija nustato statinio nario kintamąjį pagal jo sukurtą egzempliorių. Antroji gija tikrina statinio nario kintamojo ir vietinės kopijos lygybę, ir testas praeina. Pabudus pirmajai gijai, ji taip pat sukuria pavienį egzempliorių, tačiau ta gija nenustato statinio nario kintamojo (nes antroji gija jį jau nustatė), todėl statinis kintamasis ir vietinis kintamasis nėra sinchronizuojami, o testas nes lygybė žlunga. 6 pavyzdyje pateikiami 5 pavyzdžio bandymo atvejai:

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