JavaRush /Java Blog /Random-TL /Pagsasalin ng aklat. Functional na programming sa Java. K...
timurnav
Antas

Pagsasalin ng aklat. Functional na programming sa Java. Kabanata 1

Nai-publish sa grupo
Ikalulugod kong tulungan kang maghanap ng mga pagkakamali at pagbutihin ang kalidad ng pagsasalin. Nagsasalin ako para pagbutihin ang aking mga kasanayan sa wikang Ingles, at kung magbabasa at maghahanap ka ng mga error sa pagsasalin, mas mapapabuti mo pa kaysa sa akin. Isinulat ng may-akda ng aklat na ang libro ay may maraming karanasan sa pagtatrabaho sa Java; sa totoo lang, ako mismo ay hindi partikular na nakaranas, ngunit naunawaan ko ang materyal sa aklat. Ang libro ay tumatalakay sa ilang teorya na mahirap ipaliwanag sa mga daliri. Kung may mga disenteng artikulo sa wiki, magbibigay ako ng mga link sa kanila, ngunit para sa isang mas mahusay na pang-unawa inirerekumenda ko na ikaw mismo ang mag-Google. good luck sa lahat. :) Para sa mga gustong iwasto ang aking pagsasalin, pati na rin sa mga nahihirapang magbasa sa Russian, maaari mong i-download ang orihinal na aklat dito . Mga Nilalaman Kabanata 1 Kumusta, Mga Ekspresyon ng Lambda - kasalukuyang binabasa ang Kabanata 2 Paggamit ng Mga Koleksyon - nasa pag-unlad Kabanata 3 Mga String, Paghahambing at Mga Filter - sa pag-unlad Kabanata 4 Pag-unlad na may mga Ekspresyon ng Lambda - sa pag-unlad Kabanata 5 Paggawa gamit ang Mga Mapagkukunan - sa pag-unlad Kabanata 6 Pagiging Tamad - sa pag-unlad Kabanata 7 Pag-optimize ng mga mapagkukunan - sa pag-unlad Kabanata 8 Layout na may mga expression ng lambda - sa pag-unlad Kabanata 9 Pagsasama-sama ng lahat - sa pag-unlad

Kabanata 1 Hello, Lambda Expressions!

Ang aming Java code ay handa na para sa mga kahanga-hangang pagbabago. Ang mga pang-araw-araw na gawain na ginagawa namin ay nagiging mas simple, mas madali at mas nagpapahayag. Ang bagong paraan ng pagprograma ng Java ay ginamit nang ilang dekada sa ibang mga wika. Sa mga pagbabagong ito sa Java, maaari tayong sumulat ng maikli, eleganteng, nagpapahayag na code na may mas kaunting mga error. Magagamit namin ito para madaling maglapat ng mga pamantayan at magpatupad ng mga karaniwang pattern ng disenyo na may mas kaunting linya ng code. Sa aklat na ito, ginalugad namin ang functional na istilo ng programming gamit ang mga direktang halimbawa ng mga problema na ginagawa namin araw-araw. Bago tayo sumabak sa eleganteng istilong ito at sa bagong paraan ng pagbuo ng software, tingnan natin kung bakit ito mas mahusay.
Baguhin ang iyong pag-iisip
Ang istilong imperative ay ang ibinigay sa atin ng Java mula nang simulan ang wika. Iminumungkahi ng istilong ito na ilarawan namin sa Java ang bawat hakbang ng kung ano ang gusto naming gawin ng wika, at pagkatapos ay tinitiyak lang namin na matapat na sinusunod ang mga hakbang na iyon. Ito ay nagtrabaho nang mahusay, ngunit ito ay mababa pa rin ang antas. Ang code ay naging masyadong verbose, at madalas naming gusto ang isang wika na medyo mas matalino. Pagkatapos ay maaari nating sabihin ito nang declarative - kung ano ang gusto natin, at hindi alamin kung paano ito gagawin. Salamat sa mga developer, matutulungan na kami ngayon ng Java na gawin ito. Tingnan natin ang ilang halimbawa upang maunawaan ang mga benepisyo at pagkakaiba sa pagitan ng mga diskarteng ito.
Ang karaniwang paraan
Magsimula tayo sa pamilyar na mga pangunahing kaalaman upang makita ang dalawang paradigms sa pagkilos. Gumagamit ito ng isang mahalagang paraan upang maghanap para sa Chicago sa koleksyon ng mga lungsod - ang mga listahan sa aklat na ito ay nagpapakita lamang ng mga snippet ng code. boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); Ang imperative na bersyon ng code ay maingay (ano ang kinalaman ng salitang ito dito?) at mababang antas, mayroong ilang nababagong bahagi. Una, gagawin namin itong mabahong boolean flag na tinatawag na found at pagkatapos ay inuulit namin ang bawat elemento sa koleksyon. Kung nakita namin ang lungsod na aming hinahanap, itinakda namin ang bandila sa true at sinira ang loop. Sa wakas, nai-print namin ang resulta ng aming paghahanap sa console.
May mas magandang paraan
Bilang mapagmasid na mga programmer ng Java, ang isang sandali na sulyap sa code na ito ay maaaring maging isang bagay na mas nagpapahayag at mas madaling basahin, tulad nito: System.out.println("Found chicago?:" + cities.contains("Chicago")); Narito ang isang halimbawa ng istilong deklaratibo - ang contains() na paraan ay tumutulong sa atin na makarating nang direkta sa kung ano ang kailangan natin.
Mga aktwal na pagbabago
Ang mga pagbabagong ito ay magdadala ng isang disenteng halaga ng mga pagpapabuti sa aming code:
  • Walang kaguluhan sa mga nababagong variable
  • Ang mga pag-ulit ng loop ay nakatago sa ilalim ng hood
  • Mas kaunting code kalat
  • Higit na kalinawan ng code, nakatutok ng pansin
  • Mas kaunting impedance; Malapit na sinusundan ng code ang layunin ng negosyo
  • Mas kaunting pagkakataon ng pagkakamali
  • Mas madaling maunawaan at suportahan
Higit pa sa mga simpleng kaso
Ito ay isang simpleng halimbawa ng isang declarative function na sumusuri para sa pagkakaroon ng isang elemento sa isang koleksyon; ito ay ginamit sa mahabang panahon sa Java. Ngayon isipin na hindi mo kailangang magsulat ng imperative code para sa mas advanced na mga operasyon tulad ng pag-parse ng mga file, pagtatrabaho sa mga database, paggawa ng mga kahilingan para sa mga serbisyo sa web, paglikha ng multithreading, atbp. Ginagawa na ngayon ng Java na magsulat ng maigsi, eleganteng code na nagpapahirap sa paggawa ng mga pagkakamali, hindi lamang sa mga simpleng operasyon, ngunit sa kabuuan ng aming buong aplikasyon.
Ang dating daan
Tingnan natin ang isa pang halimbawa. Gumagawa kami ng koleksyon na may mga presyo at susubukan namin ang ilang paraan para kalkulahin ang kabuuan ng lahat ng may diskwentong presyo. Ipagpalagay na hiniling sa amin na buod ang lahat ng mga presyo na ang halaga ay lumampas sa $20, na may 10% na diskwento. Gawin muna natin ito sa karaniwang paraan ng Java. Ang code na ito ay dapat na napakapamilyar sa amin: una ay lumikha kami ng isang nababagong variable na totalOfDiscountedPrices kung saan namin iimbak ang resultang halaga. Pagkatapos ay umiikot kami sa koleksyon ng presyo, pumili ng mga presyong higit sa $20, kunin ang may diskwentong presyo, at idagdag ang halagang iyon sa totalOfDiscountedPrices . Sa dulo ipinapakita namin ang kabuuan ng lahat ng mga presyo na isinasaalang-alang ang diskwento. Nasa ibaba kung ano ang output sa console final List prices = Arrays.asList( new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"), new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"), new BigDecimal("45"), new BigDecimal("12")); BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; for(BigDecimal price : prices) { if(price.compareTo(BigDecimal.valueOf(20)) > 0) totalOfDiscountedPrices = totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9))); } System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);
Kabuuan ng mga may diskwentong presyo: 67.5
Gumagana ito, ngunit mukhang magulo ang code. Pero hindi namin kasalanan, ginamit namin yung available. Ang code ay medyo mababa ang antas - ito ay naghihirap mula sa isang pagkahumaling sa mga primitives (google ito, mga kagiliw-giliw na bagay) at ito ay lumilipad sa harap ng nag-iisang prinsipyo ng responsibilidad . Ang mga nagtatrabaho sa bahay ay dapat na ilayo ang gayong code sa mga mata ng mga bata na naghahangad na maging programmer, maaaring maalarma ang kanilang marupok na isipan, maging handa sa tanong na "Ito ba ang kailangan mong gawin upang mabuhay?"
Mayroong isang mas mahusay na paraan, isa pa
Ngayon ay maaari tayong gumawa ng mas mahusay, mas mahusay. Ang aming code ay maaaring maging katulad ng isang kinakailangan sa pagtutukoy. Makakatulong ito sa amin na bawasan ang agwat sa pagitan ng mga pangangailangan ng negosyo at ang code na nagpapatupad ng mga ito, na higit pang binabawasan ang posibilidad na ma-misinterpret ang mga kinakailangan. Sa halip na lumikha ng isang variable at pagkatapos ay baguhin ito nang paulit-ulit, magtrabaho tayo sa isang mas mataas na antas ng abstraction, tulad ng sa sumusunod na listahan. final BigDecimal totalOfDiscountedPrices = prices.stream() .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9))) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Total of discounted prices: " + totalOfDiscountedPrices); Basahin natin ito nang malakas - mas malaki sa 20 ang filter ng presyo, i-map (lumikha ng mga pares ng "key" na "value") gamit ang key na "presyo", presyo kasama ang diskwento, at pagkatapos ay idagdag ang mga ito
- Ang komento ng tagasalin ay nangangahulugan ng mga salitang lumalabas sa iyong ulo habang binabasa ang code .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0)
Ang code ay isinagawa nang magkasama sa parehong lohikal na pagkakasunud-sunod tulad ng nabasa natin. Pinaikli ang code, ngunit gumamit kami ng maraming bagong bagay mula sa Java 8. Una, tinawag namin ang stream() na paraan sa listahan ng mga presyo . Binubuksan nito ang pinto sa isang custom na iterator na may maraming hanay ng mga feature ng kaginhawahan na tatalakayin natin sa ibang pagkakataon. Sa halip na direktang pag-loop sa lahat ng mga halaga sa listahan ng mga presyo , gumagamit kami ng ilang mga espesyal na pamamaraan tulad ng filter() at map() . Hindi tulad ng mga pamamaraan na ginamit namin sa Java at sa JDK, ang mga pamamaraan na ito ay gumagamit ng isang hindi kilalang function - isang lambda expression - bilang isang parameter sa mga panaklong. Pag-aaralan natin ito nang mas detalyado mamaya. Sa pamamagitan ng pagtawag sa reduce() method , kinakalkula namin ang kabuuan ng mga value (discounted price) na nakuha sa map() method . Nakatago ang loop sa parehong paraan tulad noong ginagamit ang contains() method . Ang filter() at map() na mga pamamaraan ay gayunpaman ay mas kumplikado. Para sa bawat presyo sa listahan ng mga presyo , tinatawag nila ang naipasa na function ng lambda at i-save ito sa isang bagong koleksyon. Ang paraan ng reduce() ay tinatawag sa koleksyong ito upang makagawa ng huling resulta. Nasa ibaba kung ano ang output sa console
Kabuuan ng mga may diskwentong presyo: 67.5
Mga pagbabago
Nasa ibaba ang mga pagbabago na nauugnay sa karaniwang pamamaraan:
  • Ang code ay nakalulugod sa mata at hindi kalat.
  • Walang mababang antas na operasyon
  • Mas madaling pagbutihin o baguhin ang lohika
  • Ang pag-ulit ay kinokontrol ng isang library ng mga pamamaraan
  • Mahusay, tamad na pagsusuri ng loop
  • Mas madaling iparallelize kung kinakailangan
Tatalakayin natin sa ibang pagkakataon kung paano ibinibigay ng Java ang mga pagpapahusay na ito.
Lambda to the rescue :)
Ang Lambda ay ang functional na susi sa pagpapalaya sa amin mula sa mga abala ng imperative programming. Sa pamamagitan ng pagbabago sa paraan ng pagpo-program namin, gamit ang mga pinakabagong feature ng Java, maaari kaming magsulat ng code na hindi lamang elegante at maigsi, ngunit mas kaunting error-prone, mas mahusay, at mas madaling i-optimize, pagbutihin, at gawing multi-threaded.
Manalo ng Malaki mula sa Functional Programming
Ang functional na istilo ng programming ay may mas mataas na ratio ng signal-to-ingay ; Nagsusulat kami ng mas kaunting mga linya ng code, ngunit ang bawat linya o expression ay gumaganap ng mas maraming functionality. Kami ay nakakuha ng kaunti mula sa functional na bersyon ng code kumpara sa imperative:
  • Iniwasan namin ang mga hindi gustong pagbabago o muling pagtatalaga ng mga variable, na pinagmumulan ng mga error at nagpapahirap sa pagproseso ng code mula sa iba't ibang thread nang sabay-sabay. Sa imperative na bersyon, nagtakda kami ng iba't ibang mga halaga para sa variable na totalOfDiscountedPrices sa buong loop . Sa functional na bersyon, walang tahasang pagbabago sa variable sa code. Ang mas kaunting mga pagbabago ay humahantong sa mas kaunting mga bug sa code.
  • Ang functional na bersyon ng code ay mas madaling iparallelize. Kahit na ang mga kalkulasyon sa map() na pamamaraan ay mahaba, maaari nating patakbuhin ang mga ito nang magkatulad nang walang takot sa anuman. Kung maa-access namin ang imperative-style na code mula sa iba't ibang mga thread, kakailanganin naming mag-alala tungkol sa pagbabago ng variable ng totalOfDiscountedPrices sa parehong oras . Sa functional na bersyon, ina-access lang namin ang variable pagkatapos magawa ang lahat ng pagbabago, pinalalaya kami nito mula sa pag-aalala tungkol sa kaligtasan ng thread ng code.
  • Ang code ay mas nagpapahayag. Sa halip na isagawa ang code sa ilang hakbang - paglikha at pagsisimula ng variable na may dummy value, pag-loop sa listahan ng mga presyo, pagdaragdag ng mga presyong may diskwento sa variable, at iba pa - hinihiling lang namin sa map() na paraan ng listahan na magbalik ng isa pang listahan ng mga may diskwentong presyo at idagdag ang mga ito .
  • Ang istilo ng pagganap ay mas maigsi: mas kaunting mga linya ng code ang kinakailangan kaysa sa kinakailangang bersyon. Ang ibig sabihin ng mas compact na code ay mas kaunti ang pagsusulat, mas kaunting basahin, at mas madaling mapanatili.
  • Ang functional na bersyon ng code ay intuitive at madaling maunawaan, kapag alam mo na ang syntax nito. Inilalapat ng paraan ng map () ang ipinasang function (na kinakalkula ang may diskwentong presyo) sa bawat elemento ng koleksyon at gumagawa ng koleksyon na may resulta, tulad ng makikita natin sa larawan sa ibaba.

Larawan Figure 1 - inilalapat ng paraan ng mapa ang ipinasang function sa bawat elemento ng koleksyon
Sa suporta ng mga expression ng lambda, lubos naming magagamit ang kapangyarihan ng functional na istilo ng programming sa Java. Kung master natin ang istilong ito, makakagawa tayo ng mas nagpapahayag, mas maigsi na code na may mas kaunting pagbabago at error. Noong nakaraan, ang isa sa mga pangunahing bentahe ng Java ay ang suporta nito para sa object-oriented na paradigm. At ang functional na istilo ay hindi sumasalungat sa OOP. Tunay na kahusayan sa paglipat mula sa imperative tungo sa declarative programming. Sa Java 8 maaari naming pagsamahin ang functional programming sa isang object-oriented na istilo nang lubos na epektibo. Maaari naming patuloy na ilapat ang estilo ng OO sa mga bagay, ang kanilang saklaw, estado at mga relasyon. Bilang karagdagan, maaari naming i-modelo ang pag-uugali at estado ng pagbabago, mga proseso ng negosyo at pagpoproseso ng data bilang isang serye ng mga set ng function.
Bakit ang code sa functional na istilo?
Nakita namin ang pangkalahatang mga benepisyo ng functional na istilo ng programming, ngunit sulit bang matutunan ang bagong istilong ito? Ito ba ay isang maliit na pagbabago sa wika o ito ba ay magbabago sa ating buhay? Dapat nating makuha ang mga sagot sa mga tanong na ito bago natin sayangin ang ating oras at lakas. Ang pagsulat ng Java code ay hindi ganoon kahirap; ang syntax ng wika ay simple. Kumportable kami sa mga pamilyar na library at API. Ang talagang nangangailangan sa amin na magsikap na magsulat at magpanatili ng code ay ang mga karaniwang Enterprise application kung saan ginagamit namin ang Java para sa pag-unlad. Kailangan nating tiyakin na ang mga kapwa programmer ay nagsasara ng mga koneksyon sa database sa tamang oras, na hindi nila ito hawak o nagsasagawa ng mga transaksyon nang mas mahaba kaysa sa kinakailangan, na nakakakuha sila ng mga eksepsiyon nang buo at nasa tamang antas, na nag-aaplay at naglalabas sila ng mga kandado nang maayos. ... ang sheet na ito ay maaaring ipagpatuloy sa napakahabang panahon. Ang bawat isa sa mga argumento sa itaas lamang ay walang bigat, ngunit magkasama, kapag pinagsama sa likas na mga kumplikadong pagpapatupad, ito ay nagiging napakalaki, nakakaubos ng oras at mahirap na ipatupad. Paano kung maaari nating i-encapsulate ang mga kumplikadong ito sa maliliit na piraso ng code na maaari ring pamahalaan ang mga ito nang maayos? Kung gayon ay hindi kami patuloy na gumagastos ng enerhiya sa pagpapatupad ng mga pamantayan. Magbibigay ito ng malubhang kalamangan, kaya tingnan natin kung paano makakatulong ang isang functional na istilo.
Tanong ni Joe
Ang isang maikling* code ba ay nangangahulugan lamang ng mas kaunting mga titik ng code?
* pinag-uusapan natin ang salitang concise , na nagpapakilala sa functional na istilo ng code gamit ang mga expression ng lambda
Sa kontekstong ito, ang code ay sinadya upang maging maigsi, walang mga frills, at binawasan sa direktang epekto upang mas epektibong maihatid ang layunin. Ang mga ito ay malalayong benepisyo. Ang pagsulat ng code ay tulad ng pagsasama-sama ng mga sangkap: ang paggawa nitong maigsi ay parang pagdaragdag ng sarsa dito. Minsan nangangailangan ng higit na pagsisikap upang magsulat ng naturang code. Mas kaunting code ang babasahin, ngunit ginagawa nitong mas transparent ang code. Mahalagang panatilihing malinaw ang code kapag pinaikli ito. Ang maigsi na code ay katulad ng mga trick sa disenyo. Ang code na ito ay nangangailangan ng mas kaunting pagsasayaw na may tamburin. Nangangahulugan ito na maaari nating mabilis na maipatupad ang ating mga ideya at magpatuloy kung gagana ang mga ito at aabandunahin ang mga ito kung hindi nila naaabot ang mga inaasahan.
Mga pag-ulit sa mga steroid
Gumagamit kami ng mga iterator upang iproseso ang mga listahan ng mga bagay, pati na rin upang gumana sa Sets at Maps. Ang mga iterator na ginagamit namin sa Java ay pamilyar sa amin; bagama't sila ay primitive, hindi sila simple. Hindi lamang sila kumukuha ng ilang linya ng code, medyo mahirap din silang isulat. Paano natin inuulit ang lahat ng elemento ng isang koleksyon? Maaari tayong gumamit ng for loop. Paano tayo pipili ng ilang elemento mula sa koleksyon? Gamit ang pareho para sa loop, ngunit gumagamit ng ilang karagdagang nababagong variable na kailangang ikumpara sa isang bagay mula sa koleksyon. Pagkatapos, pagkatapos pumili ng isang partikular na halaga, paano kami nagsasagawa ng mga operasyon sa isang halaga, tulad ng minimum, maximum, o ilang average na halaga? Muli ay umiikot, muli bagong variable. Ito ay nakapagpapaalaala sa salawikain, hindi mo makikita ang mga puno dahil sa kagubatan (ang orihinal ay gumagamit ng isang dula sa mga salitang nauugnay sa mga pag-ulit at nangangahulugang "Lahat ay kinuha, ngunit hindi lahat ay nagtagumpay" - tala ng tagasalin). Nagbibigay na ngayon ang jdk ng mga panloob na iterator para sa iba't ibang pahayag: isa upang gawing simple ang pag-loop, isa upang itali ang kinakailangang dependency ng resulta, isa upang i-filter ang mga halaga ng output, isa upang ibalik ang mga halaga, at ilang mga function ng kaginhawahan para sa pagkuha ng min, max, average, atbp. Bilang karagdagan, ang functionality ng mga operasyong ito ay maaaring pagsamahin nang napakadali, upang maaari naming pagsamahin ang iba't ibang hanay ng mga ito upang ipatupad ang lohika ng negosyo nang mas madali at mas kaunting code. Kapag tapos na tayo, mas madaling maunawaan ang code dahil lumilikha ito ng lohikal na solusyon sa pagkakasunud-sunod na kinakailangan ng problema. Titingnan natin ang ilang mga halimbawa ng naturang code sa Kabanata 2 at sa ibang pagkakataon sa aklat na ito.
Application ng mga algorithm
Ang mga algorithm ay nagtutulak ng mga aplikasyon ng enterprise. Halimbawa, kailangan naming magbigay ng operasyon na nangangailangan ng pagsuri ng awtoridad. Kailangan nating tiyakin na ang mga transaksyon ay nakumpleto nang mabilis at ang mga tseke ay nakumpleto nang tama. Ang ganitong mga gawain ay kadalasang binabawasan sa isang napaka-ordinaryong pamamaraan, tulad ng sa listahan sa ibaba: Transaction transaction = getFromTransactionFactory(); //... Операция выполняющаяся во время транзакции... checkProgressAndCommitOrRollbackTransaction(); UpdateAuditTrail(); Mayroong dalawang problema sa diskarteng ito. Una, madalas itong humahantong sa pagdodoble ng pagsisikap sa pag-unlad, na humahantong naman sa pagtaas ng halaga ng pagpapanatili ng aplikasyon. Pangalawa, napakadaling makaligtaan ang mga pagbubukod na maaaring itapon sa code ng application na ito, kaya nalalagay sa panganib ang pagpapatupad ng transaksyon at ang pagpasa ng mga tseke. Maaari tayong gumamit ng tamang try-finally block, ngunit sa tuwing may hahawak sa code na ito, kakailanganin nating muling suriin kung ang logic ng code ay hindi nasira. Kung hindi, maaari naming iwanan ang pabrika at iikot ang buong code sa ulo nito. Sa halip na makatanggap ng mga transaksyon, maaari kaming magpadala ng processing code sa isang mahusay na pinamamahalaang function, tulad ng code sa ibaba. runWithinTransaction((Transaction transaction) -> { //... Операция выполняющаяся во время транзакции... }); Ang maliliit na pagbabagong ito ay nagdaragdag ng malaking tipid. Ang algorithm para sa pagsusuri ng katayuan at mga pagsusuri sa aplikasyon ay binibigyan ng bagong antas ng abstraction at naka-encapsulate gamit ang runWithinTransaction() method . Sa pamamaraang ito naglalagay kami ng isang piraso ng code na dapat isagawa sa konteksto ng isang transaksyon. Hindi na namin kailangang mag-alala tungkol sa pagkalimot na gawin ang isang bagay o kung nakuha namin ang pagbubukod sa tamang lugar. Algorithmic function na ang bahala dito. Ang isyung ito ay tatalakayin nang mas detalyado sa Kabanata 5.
Mga Extension ng Algorithm
Ang mga algorithm ay ginagamit nang higit at mas madalas, ngunit upang ang mga ito ay ganap na magamit sa pagbuo ng application ng enterprise, ang mga paraan upang mapalawak ang mga ito ay kinakailangan.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION