Sunday, 26 March 2017

დაკავშირებული სიები

                                     
დაკავშირებული სიები არის სტუკტურული მონაცემების (Data Structure) ნაწილი და მასზე წესით მასივურ სიებთან, რიგებთან, სეტებთან, სტაკ და ჰიპთან ერთად უნდა გვესაუბრა, მაგრამ ვინაიდან წინა ბლოგი იყო ჩაბუდებულ კლასებზე და მომდევნო ბლოგი(ები) იქნება სტრუქტურულ მომაცემებზე, დაკავშირებული სიები ამ ბლოგში შეითავსებენ ადაპტერის როლს, ჩაბუდებულ კლას გამოვიყენებთ  და მომავალ ბლოგზე წარმოდგენა შეგვექმნება. დასახელებაც უხდება სიტუაციას.

კომპიუტერულ მეცნიერებაში დაკავშირებული სიები არის წრფივი კოლექცია ელემენტების, რომლებიც ერთმანეთან დაკავშირებული არიან მიმთითებლებით(ცვლადებით) და ისინი ინახებიან კვანძებში(in nodes). ყველა კვანძი მეხსიერებაში ინახავს ინფორმაციას რომელიც დაკავშირებულია შემდეგ კვანძთან,  კვანძები ერთად წარმოადგენენ თანმიმდევრობას. მარტივი ენით რომ ავხსნათ , ყველა კვანძი ინახავს ინფორმაციას და რეფერენიულ ცვლადს რომელიც მიგვითითებს შემდეგ ინფორმაციაზე მეხსირებაში. (გახსოვთ იმედია , რომ რეფერენციული ცვლადი არის მიმთითებელი). ამ სახის ოპერაცია პროგრამისტებს საშუალებას აძლევს ეფეკტურად დაამატონ და წაშალონ ინფორმაცია მეხსიერებაში, ნებისმიერ ადგილას. გაიხსენეთ მასივები, მასივი არის განსაზღვრული სიდიდის სტრუკტურული მონაცემი, მას ინფორმაციას ვერ დავამატებთ და  ვერ მოვაკლებთ, როგორც წესი. თუ დეფინრიებული გვაქ მასივი 10 ელემენტით, ის 10 ელემენტი იქნება მიუხედავად ყველაფრისა(გზები არსებობს თუ როგორ დავამატოთ და მოვაკლოთ მასივს ელემენტი, მაგრამ ეს არ ითვლება კარგ პრაკტიკად) გამომდინარე აქედან, დაკავშირებული სიების პრინციპული უპირატესობა მასივთან არის ის, რომ მას ადვილად ვამატებთ და ვაკლებთ ელემენტს, მაგალითისთვის, თუ მასივში რამის შეცვლა გვინდა მთლიან მასივთან უნდა ვიმუშაოთ, ყველა ელემენტი უნდა დავამუშაოთ, მოვახდინოთ როტაციები და ა.შ. დაკავშირებულ სიებთან კი ამის აუცილებლობა არ არსებობს რადგან ის მეხსიერებაში ინახება არა თანმიმდევრულად. ნახეთ მაგალითი ფოტოზე.

მასივი მეხსიერებაში ინახება თანმიმდევრულად, მისი ინიციალიზაცია და მეხსიერებაში ალოკაცია ხდება ეგრევე, თუ გაიხსენებთ სტატიკურ და დინაიურ შეკვრას, მასივი გამოვა სტატიკური კვრა, რადგან მისი ინიციალიზაცია ხდება კომპილირების დროს, ანუ კომპილატორი ხედავს მეხსიერებაში სად მოხდა მასივისთვის ადგილის გამოყოფა, განსხვავებით დაკავშირებული სიებისა, სადაც მეხსიერებაში მისთვის ადგილის გამოყოფა ხდება At Run Time, რაც ნიშნავს იმას რომ ის იკვრება დინამიურად. ამიტომაც არის მისი ელემენტები მიმოფანტული მეხსიერებაში, მასივის კი თანმიმდევრულად დალაგებული. თუ გაიხსენებთ მასივს, დაკავშირებული სიების გათავისება ბევრად გაგიადვილდებათ. თუ არ იცით რა არის მასივი, მიატოვეთ ამ ბლოგის კითხვა და დაბრუნდით საწყისებთან, სადაც მასივებზე, რეფერენციულ ცვლადებზე, სტექ და ჰიპ მეხსირებაზე, სტატიკურ და დინაურ შეკვრებზე ვისაუბრეთ. სხვანაირად გაგიჭირდებათ ამ ყველაფრის აღქმა.

მაშ ასე: დაკავშირებული სიები არის ელემენტების ერთობა, რომელიც ინახება კვანძებში, მათი ერთმანეთან დაკავშირება კი ხდება რეფერენციული ცვლადის მეშვეობით. დაკავშირებული სია არის აბსტრაკტული ტიპის დატა, ADT (ADT-ს დეტალურად სტუკტურებზე საუბრის დროს დაუბრუნდები)  რაც ნიშნავს იმას რომ მას იყენებს c++, c#, java და სხვა ენები, ყველა ენას თავის გამოყენების წესი აქვს, ჩვენ ვნახავთ როგორ ხდება მისი გამოყენება ჯავაში.

უკეთ რო გავერკვეთ რა არის ელემენტი და რა არის მიმთითებელი, რომელსაც კვანძი ინახავს უნდა დაუბრუნდეთ მასივს, როგორც მაღლა ავღნიშნე, მასივი მეხსიერებაში ინახება თანმიმდევრულად. როდესაც კომპილატორი ხედავს რო იქმება მასივი, ის მეხსიერებაში ეძებს ზუსტად იმ სიდიდის ადგილს, რამდენიც დეფინირებული მასივის ალოკაციისთვის არის საჭირო, ახდენს მის რეზერვირებას ინიცირებული  ელემენტებისთვის, ან იმ ელემენტებისთვის რომელიც ჯერ არ არის ინცირებული, მაგრამ მომავალში მოხდება მისი ინიციალიზაცია. მაგალითად int[] a = {1,2,3} ამ შემთხვევაში ზუსტად იცის კომპილატორმა რო 3 ელემენტია მასივში და ახდენს 12 სლოტის გამოყოფას, int [] a = new int[100]; a[0] = 1; a[1] =2; a[2] = 3; ინიციალიზაცია მოხდა 12 სლოტის მაგრამ კომპილატორმა მასივისთვის გამოყო 400 სლოტი და დანარჩენი 388 სლოტი ცარიელია.  ამ ყველაფერში საინტერესო არის ის , თუ როგორ ხდება მეხსიერებაში თანმიმდევრულად განლაგებულ ელემენტებს შორის კავშირი, ანუ საიდან იცის [1] მასივმა რო მისი შემდეგი ელემენტი [2] მასივშია? დააკვირდით ფოტოს:

შემდეგ ელემენტზე მიმთითებელი მასივში არის თვითონ სლოტი, 8 ფოტოზე მოთავსებულია 101,102,103,104 სლოტებე, მისი მიმთიტებელი კი არის 101, რომელიც მიუთითებს 105 ნომერ სლოტზე სადაც შემდეგი ელემენტია მასივის შენახული, წარმოიდგინეთ კონტეინერი,

სადაც ყველა შემდეგი ელემენტი დაკავშირებულია ერთმანეთთან სლოტის ნომერით, ამას მანქანა  აკეთებს ჩვენთვის, პირველი ნომერი სლოტის კი გამოდის რეფერენციული ცვლადი, (პირობითად).

წარმოდგენა შეგვექმნა როგორ მუშაობს მასივი მეხსირებაში, არ არის რთული, ახლა ვნახოთ როგორ მუშაობს დაკავშიებული სიები მეხსიერებაში, მასივისგან განსხვავებით დაკავშირებულ სიებში არ გვაქ მსგავსი ფუფუნება, კომპილატორი არ გვეუბნება სად არის შემდეგი ელემენტი მეხსიერებაში, ის ჩვენ უნდა მოვნახოთ, მოსანახ ისტრუმენტად კი ვიყენებთ რეფერენციულ ცვლადს. ნახეთ იგივე დიაგრამა, ამ ჯერად დაკავშირებულ სიებზე.

დააკვირდით როგორ არის მიმოფანტული ელემენტები მეხსიერებაში, 8,6,7 და 5 არის ელემენტი, რომლებიც იმყოფებიან გამოყოფილ სლოტებში, ყველა ელემენტის პირველი სლოტის ნომერი არის რეფერენცია შემდეგი ელემენტის, კონტეინერი რომელიც ინახავს არსებულ რეფერენციას, ელემენტს და შემდეგ რეფერენციას არის Node.

რეალურ პროგრამირებაში რა თქმა უნდა სლოტის ნაცვლად რეფერენციულ ცვლადს გამოიყენებთ. ნახეთ მაგალითი:

ფოტოზე ხედავთ Node-ის შექმნის მაგალითს, რომელიც მიიღებს ელემენტს და რეფერენციულ ცვლადს, ელემენტს შეინახავს Object და რეფერენციული ცვლადი(მიმთითებელი) შეინახავს დასახელებას რომელიც მიგვითითებს შემდეგ ელემენტზე მეხსიერებაში. სავარაუდოდ რთულად ჯღერს, ეგრეც არის კომპლექსური და მრავალ საფეხურიანი ოპერაციების ერთობა გახლავთ სტრუკტურული მონაცემი. მაგრამ წარმოდგენა უნდა შეგექმნათ რო გამოყენება გაგიადვილდეთ. უფრო გამარტივების მიზნით ვნახოთ ჯავა ბიბლიოთეკის მიერ შემოთავაზებული დაკავშირებული სიები. დიახ ამის შექმნის აუცილებლობა არ არსებობს იმიტომ რო უკვე შექმნილია, თქვენ უბრალოდ გამოყენებას ისწავლით. თუმცა ბლოგის ბოლოში შექვმნით საკუთარ დაკავშირებულ სიას.

ჯავა ბიბლიოთეკის თანახმად სიას შეუძლია შეინახოს ნებისმიერი სახის ელემენტი, რადგან მუშაობს რეფერენციული ტიპის დატასთან, Object,  String, Integer, Double და ა.შ არ აგერიოთ პრიმიტიული ტიპის დატაში, int, double, short და ა.შ რეფერენციული ტიპის დატასთან მუშაობას განაპირობებს null-ის მიღების აუცილებლობა, მეხსიერებას ვერ გავანულებთ, მას ვა- null-ებთ. თუ მაგალითების ნახვის დროს წავაწყდით პრიმიტიული ტიპის დატას , მხოლოდ და მხოლოდ ინდექსის მოძებნის მიზნით.

არსებობს სამი სახის დაკავშირებული სია და ესენია:

Singly Linked List:

რაც ნიშნავს რო ელემენტის ძიება ხდება ერთი მიმართულებთ. როგორც ფოტოზეა ნაჩვენები.

Doubly Linked List:

სადაც ელემენტების ძიება ხდება ორმხრივად, ხშირად ბოლო ელემენტს პროგრამისტები head-ს ეძახიან, პირველ ელემენტს კი tail-ს.

Circular Linked List:

მასში ბოლო ელემენტი მიუთითებს პირველ ემემენტზე. ან პირიქით.

შესაძლებელია Circular Linked List-ის გამოყენება Single Linked List-ან, ისევე როგორც Doubly Linked List-ან.

ჩვენ მხოლოდ Single Linked List-ს განვიხილავთ ამ ბლოგში, დეტალურად მას სტრუკტურებზე საუბრის დროს დაუბრუნდებით.

მაშ ასე, როგორ ხდება სიის დეფინირება, მისი გამოყენება და რა მეთოდები გააჩნია ვნახოთ მაგალითებზე:

1. როგორ ხდება ელემენტის დამატება სიაში:

ახსნა განმარტებას არ საჭიროებს, საკმაოდ მარტივია.

2. შესაძლებელია ელემენტების სიის თავში ან ბოლოში დამატება:

კომპილირების შემდგომ:

3 შესაძლებელია ყველა ელემენტის სხვა სიაში გადატანა.

ამის შემდგომ შესაძლებელია პირველი სიის წაშლა, მეორე სია ინახავს ყველაფერს. კომპილირების შემდგომ:

4 შესაძლებელია პირველი, ბოლო და ნებისმიერი ელემენტის წაშლა სიიდან:

კომპილირების შემდგომ:


5 შესაძლებელია ელემენტის ნებისმიერ ინდექსზე ჩანაცვლება, ისევე როგორც შესაძლებელია მთლიანი სიის ნებისმიერ ინდექსზე ჩანაცვლება.

კომპილირების შემდგომ:

6 შესაძლებელია სიის შემოწმება, მისი სიდიდის გაგება, და მთლიანად წაშლა.

კომპილირების შემდგომ:

ყველა მეთოდს და ოპერაციას ვერ განვიხილავთ. რადგან სია ინფორმაცის იღებს 1 კლასის და 4 ინტერფეისისგან. დამატებით მეთოდებს, რომელიც მაგალითებში ვერ მოხვდნენ მიაგნებთ შემდეგ ბმულზე: https://docs.oracle.com/javase/7/docs/api/java/util/LinkedList.html
დასათაურებას დააკვირდით, ზოგიერთი მეთოდი არ ეკუთვნის დაკავშირებულ სიებს.

მნიშვნელოვანი უპირატესობები სიის:

1.     შეუძლია მიიღოს კოპირებული ელემენტები.
2.     არ ხდება მისი სინქრონიზება
3. ის არის სწრაფი რადგან ცვლილების შესატანად არ არის აუცილებელი ელემენტებს ადგილი შეუცვალო, განსხვავებით მასივისგან.


ჯავა ბიბლიოთეკის მიერ შემოთაავზებული დაკავშირებული სიები საკმაოდ საინტერესოდ გამოიყურება, ისეთი შეგრძნებაც რჩება რომ მას ყველაფერი გააჩნია და ჩვენი მხრიდან არაფრის დამატება არ არის საჭირო, თუმცა არის სიტუაციები როცა გიწევთ საკუთარი სიის შექმნა, რომელიც თქვენ გემოვნებაზე და მოთხოვნაზე არის მორგებული. შემდეგ ვიდეო ტუტორიალში ნახავთ საკუთარი სიის შექმნის მაგალითს. 
(დააჭირეთ ვიდეო-ს რათა გადახვიდეთ ბმულზე)

პ.ს ამ ბლოგით სრულდება "ჯავა დამწყებთათვის". შემდეგი ბლოგები იქნება მოწინავე ჯავა ტექნოლიგიებზე, Generic, Enum, DataBase, JDBC, JSP, Data Structure's, Regular Expressios, Files & Streams, SQL, HTML, HTML5, CSS, CSS3, Lambda, Data Serialization, JSON & XML, Tomcat, Maven, Servlet's, Design Pattern's, Service's, Tread's, Multi Treads და სხვა მრავალ მნიშვნელოვან ოპერაციებზე. 

Thursday, 16 March 2017

ჩაბუდებული კლასები


ჩაბუდებული კლასი არის კლასი რომლის დეკლალირებაც ხდება არსებულ კლასში,  ჩაბუდების რამოდენიმე სახეობა არსებობს, რომლებზეც ამ ბლოგში ვისაუბრებთ.
საბაზისო გამყოფი ზოლი სახეობებს შორის გადის შიდა ჩაბუდებულ კლასზე და სტატიკურ ჩაბუდებულ კლასზე. (Inner Class and Static Nested Class). ამ ორიდან შიდა ჩაბუდებული კლასი იყოფა ცალკე ტიპებად და ესენია, ლოკალური შიდა კლასი და ანონიმური შიდა კლასი. (Local Inner Class and Anonymous Inner Class) რაც საერთო ჯამში გვაძლევს 4 ძირითად კონცეპციას ჩაბუდებისა, რომლებიც განსხვავდებიან ერთმანეთისგან, აქვთ განსხვავებული ქცევის და გამოყენების წესები. ყველა კლას სათითაოდ განვიხილავთ, გავიგებთ როგორ ხდება მათი გამოყენება, რა არის ნებადართული, როგორ ხდება მათი ინსტანცირება და ა.შ ბლოგის ბოლოში მოკლედ ვისაუბრებთ ჩაბუდებულ ინტერფეისებზე.
კლას-ს რომელშიც ხდება ჩაბუდება ქვია OuterClass და კლასი, რომლის ჩაბუდებაც ხდება ქვია InnerClass;

შიდა და სტატიკური კლასები არის წევრი მისი ტოპ კლასის, რაც მათ განსხვავებულ დაშვების დონეს აძლევს, შიდა კლას წვდომა აქვს ყველაფერზე ტოპ ლეველის კლასში, private წევრებზეც კი და შეუძლია იყოს private მოდიფიკატორით, ალბათ ხვდებით რატომაც, ის ეკუთვნის ტოპ ლეველის კლას და მისი დეკლალირება შეიძლება private მოდიფიკატორით ისევე როგორც ნებისმიერი სხვა რამის დეკლალირება, კლასში, როცა თვითონ ტოპ ლეველის კლას არ შეუძლია იყოს private მოდიფიკატორით. სტატიკურ კლას არ აქვს წვდომა ინსტანციურ ინფორმაციაზე ტოპ ლეველის კლასში, ის ხედავს მხოლოდ სტატიკურ წევრებს, ინსტანციურ წევრებს მხოლოდ ინსტანცირების შემდგომ წვდება.

ამ მაგალითს არც უნდა გაჩვენებდეთ, private ტოპ კლასის ქონას არ აქვს აზრი რადგან ამ კლას ვერავინ ვერ გამოიყენებს, კლასი თუ ვერ გამოიყენე მისი ქონის არსიც იკარგება, თუმცა:

ნებისმიერი სხვა მოდიფიკატორით შესაძლებელია ვიქონიოთ ჩაბუდებული კლასები, მათ შორის აბსტრაკტული და ფინალური მოდიფიკატორებითაც.

რატო ან სად უნდა გამოვიყენოთ ჩაბუდებული კლასები? ეს კითხვა ჩდება ხოლმე ხშირად დამწყებ პროგრამისტებში: ველოსიპედის გამოგონებას არ დავიწყებ და პირდაპირ მოვახდენ ციტირებას ჯავას ოფიციალური ვებ გვერდიდან:
1.   ჩაბუდება გვაძლევს საშუალებას მოვახდინოთ გრუპირება კლასების, რომლის გამოყენებაც ხდება ერთი და იგივე ადგილას. ანუ კლას თუ მხოლოდ ერთი კლასი იყენებს არ არის აუცილებელი შევქმნათ მეორე ტოპ ლეველის კლასი და მოვახდინოთ მისი ინსტანცირება, უმჯობესია თუ ასეთ შემთხვევაში გამოვიყენებთ ჩაბუდებას.
2. ზრდის ენკაპსულაციას: დაუშვათ თუ გვაქ ორი ტოპ ლეველის კლასი სადაც არის დეკლალირებული private წევრები, მაშინ ერთმა კლასმა რომ შეაღწიოს მეორე კლასის დამალულ წევრებში მოუწევს რამოდენიმე საფეხუს გავლა, (ენკაპსულაციაზე მემკვიდრეობის დროს ვისაუბრებთ და გადახედედ რა საფეხურების გავლა ხდება რო კლასის დამალულ წევრებს მივწვდეთ) რაც არ არის რელევანტური, უმჯობესია თუ ერთი კლასი მოახდენს private წევრების დეკლალირებას, რომელსაც მიწვდება private მოდიფიკატორით დეფინირებული ჩაბუდებული კლასი, დაამუშავებს და სააშკარაოზე ინფორმაციას გამოიტანს ტოპ ლეველის კლასი, ასეთი სცენარის დროს გარე სამყაროსგან იმალება ტოპ ლეველ კლასის მნიშვნელოვანი ინფორმაცია და ჩაბუდებული კლასი, რაც გვაძლევს იდეალურ ენკაპსულაციას. (ეს ყველაფერი, ისევ იმ შემთხვევაში თუ ერთი კლასი იყენებს მეორე კლას, რომელსაც არ იყენებს სხვა არც ერთი კლასი.)
3. კოდი უფრო წაკითხვადი და განახლებადი ხდება(ობიეკტზე ორიენტირებული პროგრამირება ამ იდეაზე დგას) რაც გვიმარტივებს დებაგირების პროცესაც, ჩაბუდების მეშვეობით კოდი ახლოს დგას იმ ოპერაციასთან სადაც მისი გამოყენება ხდება(ვიზუალურად ახლოს, არ არის მიმოფანტული სხვა და სხვა პაკეტებში, კლასებში და ქვე კლასებში)


ყველაზე ხშირად ჩაბუდებული ოპერაციების გამოყენებას JavaFx-ში და არა მარტო JavaFx არამედ ბევრ ჯავა პაკეტში შეხვდებით, რომლებიც ამუშავებენ დიზაინს და ქმნიან პატერნებს.
მაშ ასე, დავიწყოთ ყველა ჩაბუდებული კლასის განხილვა.
·        Inner Class
·        Static Nested Class
·        Local Inner Class
·        Anonymous Inner Class

შიდა კლასი(inner class) - მსგავსად ინსტანციური ცვლადისა და მეთოდისა, ინსტანციური კლასიც ხვდება კლასის ცვლადის ზეგავლენის ქვეშ. ანუ ტოპ კლასის ცვლადით შესაძლებელია ინსტანციურ კლასზე წვდომა, ისევე როგორც ხდება მეთოდებზე და ცვალდებზე წვდომა. საკმაოდ მარტივი პრინციპია, არაფერი განსხვავებული. შიდა კლას აქვს წვდომა ნებისმიერი მოდიფიკატორის მქონე ოპერაციაზე გარე კლასში, პირიქითაც სწორია, გარე კლავს აქვს წვდომა ნებისმიერი მოდიფიკატორის მქონე ინფორმაციაზე შიდა კლასში.  შიდა კლას არ შეუძლია იქონიოს სტატიკური წევრები, ალბათ ხვდებით რატომაც, სტატიკური წევრები კლასის დონის წევრებია, მის დეფინირებას თუ შიდა კლასში მოახდენთ რომელი კლასის წევრი უნდა იქნეს სტატიკური წევრი? შიდა კლასის თუ გარე კლასის? რა თქმა უნდა სტატიკური წევრი მხოლოდ ტოპ ლეველის მქონე კლასის წევრია და მის დეფინირებას თუ მოვახდენთ შიდა კლასში , მაშინ მთლიანად სტატიკური ოპერაცია აზრს კარგავს, თუ გაქვთ ჩემი ბლოგები გადაკითხული ნახავდით რო სტატიკურ წევრზე კლასის სახელით ხდება წვდომა და არა რეფერენციული ცვლადით. ობიეკტის შექმნის დროს,  შიდა კლასის რეფერენცია მთლიანად დამოკიდებულია გარე კლასის რეფერენციაზე და მის წევრებზე გარე კლასის რეფერენციის მეშვეობით ხდება წვდომა, გამომდინარე აქედან ლოგიკურად რო ვიმსჯელოთ ობიეკტის რეფერენციით სტატიკურ წევრზე წვდომა ცოტა არ იყოს არა სწორი პროგრამირება გახლავთ, აქედან დასკვნა შიდა კლას არ აქვს უფლება იქონიოს სტატიკური წევრი. თუმცა ლეგალურია იქონიოთ static final კონსტანტური ცვლადი. მაგალითად თუ შიდა კლასი extends ან implements რომელიმე კლას საიდანაც კონსტანტური ცვლადის აღება სურს კომპილატორი ხელს არ შეუშლის ამის გაკეთებაში, ისევე როგორც საკუთარი დეფინირების დროს. ბევრი რომ არ გაგვიგრძელდეს, ვნახოთ სინტაქსური მაგალითი:

სინტაქსი მარტივია. წარმოდიგინეთ რო ამ კლასის კომპილირება მოვახდინეთ კომანდ პრომპიდან,  მაშინ ვირტუალური მანქანა შექმნის 2 კლას ფაილს ჩვენთვის.  javac Outer.java გაუშვით თქვენ კომანდ პრომპში და ნახავთ რო Outer.class და Outer$Inner.class ფაილები შეიქმნება, ტექნიკურად შიდა კლასი სხვა კლასია ამიტომაც ქმნის კომპილატორი მისთვის ცალკე ფაილს, ეს არ ნიშნავს იმას რომ შიდა კლასზე ინფორმაციას ისევე მივწვდებით როგორც ჩვეულებრივ კლასში, თუ სურვილი გვაქ მსგავსი ოპერაციის შესრულების, მოგვიწევს გარე გკლასის ინსტანცირება და მისი ცვლადით შესაძლებელი გახდება მივწვდეთ შიდა კლას. ნახეთ მაგალითები:

ენკაპსულაციის თვალსაზრისით, პატარა კოდის მონაკვეთით ვაღწევთ მთლიან ენკაპსულაციას.

დააკვირდით როგორ აქვს დამალულ წევრებზე წვდომა ორივე კლასს. ფოტოზე აგრეთვე ჩანს თუ როგორ მოვახდინეთ ინსტანცირება შიდა კლასის გარე კლასში, მაგრამ რა მოხდება თუ სურვილი გაგვიჩდა და გვინდა შიდა კლასის ინსტანცირება სადმე სხვაგან?

გამოძახების სტილი შეიცვლებოდა შემდეგნაირად. კარგად დააკვირდით ფოტოს.  შიდა კლასის ინსტანცირება ხდება გარე კლასის ცვლადის დახმარებით. თუ არ მოვახდინეთ ინსტანცირება გარე კლასის შიდა კლას ვერასდროს ვერ მივწვდებით. შესაძლებელია  უცნაური ოპერაციის წარმოება, გარე კლასის ინსტანცირებას მოვახდენთ მაგრამ მას არ ექნება ცვლადი, ცვალდი მხოლოდ შიდა კლას ექნება და მისი მეშვეობით მოვახდენთ ინფორმაციაზე წვდომას, ნახეთ მაგალითი:

ამ ფოტოზე ნაჩვენებია 3 სხვა და სხვა გზა შიდა კლასის ინსტანცირების. რა სხვაობებია მათ შორის?:

   1 თუ სურვილი გაქვთ შექმნათ შიდა კლასის ინსტანცია გარე კლასში შეგიძლიათ მიმართოთ შემდეგ ხერხს.  Outer out = new Outer();
2.   თუ სურვილი გაქვთ გამოიყენოთ შიდა კლასის კოდი გარე კლასში ან რომელიმე სხვა კლასში მაშინ მიმართეთ შემდეგ  2 ვარიანტს. მე-2 ვარიანტი ფოტოზე სადაც ინსტანცირება ხდება გარე კლასის ცვლადის დახმარებით და მესამე ვარიანტი სადაც ინსტანცირება ხდება შიდა კლასის ცვლადის დახმარებით.
 
სამივე შემთხვევაში ხდება გარე კლასის ინსტანცირება რათა 
მიწვდეთ ინფორმაციას შიდა კლასში, ჩემი აზრით ვარიანტი 
ნომერი 1 უფრო წაკითხვადია დანარჩენ ორთან შედარებით.                       
 
                                  შიდა კლასი და დაჩრდილვა
თუ გარე კლასში, შიდა კლასში და მეთოდში პარამეტრად გაქვთ 
ერთი და იგივე ტიპის ცვლადი ერთი და იგივე დასახელებით, 
მაშინ დაჩრდილვა ცვლადების ხდება იერარქიულად. დაუშვათ 
გაქვთ მეთოდი რომელსაც აქვს პარამეტრი int number; იგივე 
ტიპის დასახელებით თუ გაქვთ ცვლადი შიდა კლასში, მაშინ 
პარამეტრი ჩრდილავს შიდა კლასის ცვლადს, რაც ავტომატურად 
ჩრდილავს გარე კლასის ცვლადს, ანუ საკვანძო ფიგურა 
დაჩრდილვაში არის მეთოდის პარამეტრი, რომელიც ჩრდილავს 
შიდას დ შიდა ჩრდილავს გარეს. თუ სურვილი გაქვთ მათ მიწვდეთ 
უნდა გაითვალისწინოთ შემდეგი წესი. ნახეთ მაგალითი:

ფოტოზე ხედავთ სამ ცვლადს, რომლებიც არიან ერთი ტიპის 
და დასახელების, კომპილირების დროს რომელი ცვლადი გამოვა 
ეკრანზე? რა თქმა უნდა პარამეტრი, 
რომელიც გამოიტანს კონზოლში 25-ს. თუმცა ჯადოსნური სიტყვა 
this-ს დახმარებით შეგვიძლია სამივე გამოვიტანოთ ეკრანზე, 
ნახეთ მაგალითი:
ომპილირების შემდგომ:
საკმაოდ მარტივი პრინციპებით ხელმძღვანელობს შიდა კლასი,
არაფერი გამორჩეული მას არ გააჩნია, (თუ არ ჩავთვლით 3 სახის ინსტანცირებას) 
ან რაიმე სახის შეზღუდვა, გარდა იმისა რომ არ შეუძლია იქონიოს 
სტატიკური მეთოდები. ყველაფერი დანარჩენი იგივე აქვს რაც ჩვეულებრივ კლას. 


სტატიკური ჩაბუდებული კლასი(static nested class) - სტატიკური ჩაბუდებული კლასი არის კლასი, რომელიც დეფინირებულია სხვა კლასში მაგრამ ინარჩუნებს ტოპ კლასის პრინციპებს, ანუ მისი გამოყენება კლას გარეთ შესაძლებელია როგორ ტოპ კლასის. ამიტომაც არ ეძახიან მას შიდა კლას (inner class) მისი სწორი დასახელება გახლავთ static nested class, როდისმე თუ მოგიწიათ გასაუბრებაზე ყოფნა ან თუნდაც არგუმენტირებული კამათი პროგრამისტებთან, არასდროს გამოიყენოთ ტერმინოლოგია inner class სტატიკურ ჩაბუდებულ კლასზე საუბრის დროს. მარტივი მიზეზის გამო, ჩაბუდებული კლასი მთლიანად იმყოფება ტოპ კლასის ზეგავლენის ქვეშ, მისი ინსტანცირება შეუძლებელია თუ არ მოვახდენთ ტოპ კლასის ინსტანცირებას, სტატიკურ კონსტექსტში კი ეს ყველაფერი მეორახისხოვანია, ის დამოუკიდებელია როგორც ნებისმიერი სხვა სტატიკური წევრი კლასისა, ცვლადი იქნება ის თუ მეთოდი. თამამად შეგიძლიათ თქვათ (გასაუბრებაზე) რო შიდა კლასი ჩაბუდებული კლასია მაგრამ პირიქით ჩაბუდებული კლასი არ არის ყოველთვის შიდა კლასი, ამ ლოგიკით სტატიკური შიდა კლასი გვევლინება ტოპ ლეველის კლასად, მიუხედაავდ იმისა რო ის სხვა კლასშია დეფინრიებული.
როდესაც ახდენთ სტატიკურ ჩაბუდებას, თქვენ ამით ამბობთ, რომ ეს კლასი არ იქნება არც ერთი ობიეკტის ზეგავლენის ქვეშ, ის კლასის დონის კლასია და არა „ატრიბუტი და ქცევის წესი“, რომელიმე კონკრეტული ობიეკტის, თქვენ შეგიძლიათ იფიქრობთ მასზე როგორც რესურსზე, რომელიც ხელმისაწვდომია ამა თუ იმ კლასის გამოყენების დროს, რომელშიც იმყოფება სტატიკური კლასი.
სანამ ვისაუბრებთ სტატიკური ჩაბუდებული კლასის გამოყენების წესებზე, ნახეთ მაგალლითი:

წესი 1: სტატიკური კლასის ინსტანცირებისთვის არ არის საჭირო გარე კლასის ობიეკტის შექმნა, საერთოდ არ იქნებოდა ობიეკტის შექმნა საჭირო თუ სტატიკურ კლასში მეთოდი სტატიკური იქნებოდა.

წესი 2: სტატიკური კლასი არის კლასის სტატიკური წევრი და მისი გამოყენება შეიძლება ისევე როგორც სტატიკური ცვლადის ან მეთოდის.

წესი 3: სტატიკურ ჩაბუდებულ  კლას წვდომა აქვს მხოლოდ სტატიკურ ინფორმაციაზე გარე კლასში, ინსტანციურ ინფორმაციას რომ მიწვდეს უნდა მოახდინოს ინსტანცირება გარე კლასის. მსგავსად სტატიკური მეთოდისა.

წესი 4: სტატიკურ ჩაბუდებულ კლას შეუძლია იქონიოს სტატიკური წევრები. განსხვავებით შიდა კლასისა, რომელსაც ამის ფუფუნება არ გააჩნია.

როდის გამოვიყენოთ სტატიკური კლასი? როდესაც ვხედავთ რო კლას “X”-ს ჭირდება ერთი კონკრეტული კლასი, რათა შეასრულოს მისი მოვალეობა, ამისთვის არ არის აუცილებელი ცალკე მდგომი ტოპ ლეველის კლასის შექმნა, ჩვენ შეგვიძლია ჩავაბუდოთ სტატიკურად კლას “X”-ში საჭირო ინფორმაცია და ნებისმიერ ადგილზე გამოვიყენოთ ის, როგორც რესურსი. მაგალითზე უკეთ მიხვდებით რასაც ვგულისხმობ:

ამ შემთხვევაში ხელფასზე დანამატი რესურსია, რომელიც კლას გააჩნია. არ არის აუცილებელი დანამატისთვის ცალკე კლასი შევქმნათ. მის გამოყენებას ნებისმიერი სხვა კლასიც შეძლებს. კომპილირების შემდგომ:

ლოკალური შიდა კლასი(local inner class) -ლოკალური შიდა კლასი არის კლასი რომელიც დეფინირებულია ბლოკში  ან მეთოდში. მეთოდში დეფინირებას როცა ვამბობ ეს ნიშნავს იმას რომ ლოკალური კლასის დეფინირება შესაძლებელია ისეთ მეთოდებში როგორიც არის for-while loop, if-else statement და ა.შ. ნებისმიერი ოპერაცია, რომელიც სრულდება ფრჩხილებით და იხსნება ბლოკით იღებს ლოკალურ კლას.
ლოკალური კლასი ძალიან გავს შიდა კლას,  მას არ შეუძლია იქონიონს სტატიკური წევრები, რადგან მისი დეფინირება ხდება მეთოდში, თუ  გახსოვთ მეთოდში დეფინირებული ცვლადი არის ლოკალური რომელსაც არ გააჩნია მოდიფიკატორი, გამომდინარე აქედან კლასზეც იგივე წესები ვრცელდება, ლოკალური კლასი არის მოდიფიკატორის გარეშე, მოდიფიკატორის გარეშე ნიშნავს იმას რომ მას default მოდიფიკატორიც კი არ აქვს ვირტუალური მანქანისთვის. თუ ლოკალური კლასი დეფინირებულია სტატიკურ მეთოდში, მაშინ მას პირდაპირი წვდომა გააჩნია მხოლოდ სტატიკურ ინფორმაციაზე გარე კლასში.  თუმცა ლოკალურ კლას შეუძლია ქონდეს სტატიკური ფინალური კონსტანტა, როგორც შიდა კლას.  
გამოყენების წესები მაგალითებთან ერთად:

1.   ლოკალურ ცვლადს გაანია მხოლოდ ფინალურ ცვლადზე წვდომა მეთოდში ან მხოლოდ ეფეკტურად ფინალურ ცვლადზე. ეფეკტურად ფინალური ცვლადი არის ცვლადი რომლის ინიციალიზაციაც ხდება და შემდგომ არ იცვლება, დაუშვათ სურათზე გვაქ someN ცვლადი რომელიც = 10-ს. მისი ღირებულება რომ არ შეგვეცვალა ლოკალური ცვლადის მეთოდში კომპილირება მოხდებოდა რადგან ცვლადი არის ეფეკტურად ფინალური ლოკალური კლასისთვის.
2.     ლოკალურ კლას არ გააჩნია მოდიფიკატორები. სტატიკური, საჯარი, დამალული, დაცული მოდიფიკატორების ქონა აკრძალულია, მაგრამ მას შეუძლია იყოს ფინალური და აბსტრაკტული. Abstract class LocalClass, final class LocalClass სრულიად ლეგალური ოპერაციებია.
3.      ლოკალურ კლას გააჩნია წვდომა მეთოდის პარამეტრებზე რომელშიც არის დეფინირებული.
4.     ლოკალური კლასის გამოყენება შეუძლებელია მეთოდს გარეთ, რომელშიც მოხდა მისი დეფინირება, ამიტომაც მისი ინსტანცირება ხდება მეთოდშივე.


იგივე ფოტო აღწერით, კომპილირების შემდგომ:

5.     ლოკალურ კლას გააჩნია წვდომა ნებისმიერი ტიპის ცვლადზე მის გარე კლასში:

კომპილირების შემდგომ:

6.დაჩრდილვა ისევე ხდება ლოკალური კლასის შემთხვევაში, როგორც შიდა კლასის დროს ხდება. შიდა ცვლადი ჩრდილავს გარე ცვლადს.
7. თუ მეთოდი, სადაც ხდება ლოკალური კლასის დეფინირება არის სტატიკური, მაშინ კლას გარე კლასში გააჩნია პირდაპირი წვდომა მხოლოდ სტატიკურ ინფორმაციაზე. ნახეთ მაგალითი:

თუ არ მოვახდენდით ობიეკტის შექმნას გარე კლასიდან, მაშინ მახოს ვერასდროს ვეღარ მივწვდებოდით.

ანონიმური შიდა კლასი(Anonymous inner class)-ანონიმური შიდა კლასი არის კლასი, რომლის დეკლალირებაც და ინსტანცირებაც ხდება ერთდროულად.  მას პროგრამისტები იყენებენ მაშინ როდესაც სურვილი აქვთ მოახდინონ მხოლოდ ერთხელ ინსტანცირება, მათვის საჭირო კლასის. სინტაქსი ანონიმური კლასის ცოტა რთულია მაგრამ თუ ისწავლით მისი გამოყენების წესებს, საკმაოდ ხშირად გამოიყენებთ, რადგან ძალიან მოსახერხებელი და კომფორტულია. ის ძალიან გავს „წესებით“ ლოკალურ კლას, ერთი დიდი განსხვავებით, ანონიმურ კლას არ აქვს სახელი. ამიტომაც მას ხშირად გამოხატულებას უფრო უწოდებენ ვიდრე კლას.
ანონიმური კლასის გამოყენების წესებს სანამ გავეცნობოდეთ, ნახეთ მარტივი მაგალითი:

1.ანონიმური კლასი რო შეიქმნას, აუცილებლად უნდა მოხდეს გარე კლასის ინსტანცირება, რომლის ცვლადიც დაიჭერს ანონიმური კლასის შიგთავს, როგორც ფოტოზეა ნაჩვენები.
2. ანონიმური კლასი ახდენს იმპლემენტირებას ინტერფეისის ან extends კლას (ან აბსტრაკტულ კლას), ფოტოზე მოცემული კლასი, გამოიყურება შემდეგნაირად,   static SomeClass$1 extends Anon, მაგრამ ამას ჩვენ ვერ ვხედავთ, SomeClass$1 ადგილას ვირტუალური მანქანა თვითონ ირჩევს კლასის სახელს, რომელიც ხდება ზემდგომი კლასის რეფერენციის ზეგავლენის ქვეშ, ანუ ნებისმიერი ანონიმური კლასი არის გარე კლასის ტიპის, რასაც მივყავართ პოლიმორფიზმამდე. დეტალურად დაბლა ვისაუბრებთ.
3. მსგავსად ლოკალური კლასისა ანონიმურ კლასაც შეუძლია იქონიოს წვდომა ლოკალურ ცვლადებზე, თუ ის ფინალურია ან ეფეკტურად ფინალური.
4. ანონიმურ კლას გააჩნია წვდომა იმ მეთოდის პარამეტრებზე , რომელშიც მოხდა მისი დეფინირება და ინსტანცირება.
5. ანონიმურ კლას გააჩნია წვდომა გარე კლასის ცვლადებზე.
6. მსგავსად შიდა და ჩაბუდებული  კლასისა, ანონიმურ კლასში დეკლალირებული ცვლადი ჩრდილავს სხვა ცვლადებს იგივე დასახელებით.
7. მსგავსად ლოკალური კლასისა, ანონიმურ კლასშიც ვერ იქონიებთ სტატიკურ წევრებს.
8. სტატიკური წევრი, რომლის ქონაც ნებადართულია, არის სტატიკური კონსტანტა.
9. ანონიმურ კლასში შეგიძლიათ მოახდინოთ დეფინირება ლოკალური კლასის, ველების, დამატებითი მეთოდების.
10. ანონიმურ კლას არ გააჩნია კონსტრუკტორი, მარტივი მიზეზის გამო, არ არ აქვს სახელი, შესაბამისად ვერ ექნება კონსტრუკტორი.

ანონიმური კლასის სინტაქსი:

ასე გამოიყურება ნორმალური კლასის სიტაქსი.

და ასე გამოიყურება ანონიმური კლასის სიტაქსი.

როდესაც ქმნით ანონიმურ კლას, მისი დეკლალირება და ინსტანცირება ხდება ბლოკში, დააკვრდით პირველ შავ ისარს და მეორე შავ ისარს, ამ შავ ისრებშია ანონიმური კლასი, რომელსაც სახელი არ აქვს და რომლის რეფერენციული ცვლადიც გახლავთ ans. როდესაც ქმნით ანონიმურ კლას საკამრისია კონსტრუკტორის შემდგომ გახსნათ ფრჩხილი, მაოხდინოთ მეთოდების გადაწერა, მოახდინოთ საკუთარი მეთოდების დეფინირება და ფრცილი რომელიც ხურავს ოპერაციას უნდა დაასრულოთ }; ß ამ ნიშნით. ცოტა უცნაურია ჯავა-ში მსგავსი ფრჩხილის ქონა, მაგრამ როდისმე ჯავასკრიპტზე ან ანგულარზე თუ გიცდიათ წერა , უცხოდ არ მოგეჩვენებათ. ფრჩხილი, რომელიც იხურება წერტილ-მძიმეს ნიშნით დახურავს ანონიმურ კლას და მხოლოდ ამის შემდგომ შეგიძლიათ მისი მეთოდების გამოძახება. დააკვირდით როგორ ხდება მეთოდების გადაწერა კლასში, რადგან ანონიმურ კლას არ გააჩნია საკუთარი სახელი ის არის პოლიმორპიული, ანუ ხედავს მხოლოდ სუპერ კლასის მეთოდებს, გამოძახებაც მეთოდების პოლიმორპიული პრინციპით ხდება, ვიძახებთ იმ მეთოდებს რომლებიც დეფინირებულია სუპერ კლასში, არანაირი ხელის შემშლელი ფაკტორი არ გვაქ რომ მოვახდინოთ საკუთარი მეთოდის დეფინირება, ფოტოზე ეს ნათლად ჩანს, თუმცა მისი გამოძახება შეუძლებელია. ამიტომაც ვიძახებთ სხვა მეთოდში. ერთ-ერთი მინუსი რაც აქვს ანონიმურ კლას , შეუძლებელია DownCasting მოვახდინოთ, რათა მივწვდეთ ჩვენ მიერ დეფინირებულ მეთოდს. პოლიმორფიულ ობიეკტში გვაქ ამის უფლება, გვიწევს კიდეც გამოძახება ჩვენი მეთოდის, მაგრამ ანონიმურ კლასში ამის თეორიული შანსი არ არსებობს, თუმცა არსებობს პრაკტიკული შანსი, მაგრამ ამის ცოდნა არსად არ წაგადგებათ, ამიტომაც არ გაჩვენებთ.
რადგან პოლიმორფიზმს შევეხეთ ანონიმურ კლასში, ბარემ განვავრცობ ამ თემას, რათა რამე გაუგებარი არ დარჩეს.  როცა ვიყენებთ ანონიმურ შიდა კლას, ვიყენებთ პოლიმორფიზმს ირიბად.  დაკვირდით ans ცვლადი რეალურად არის ზემდგომი კლასის ცვლადი, რომელიც იჭერს ქვემდგომი კლასის ობიეკტს, ჩვენ შემთხვევაში იჭერს ანონიმურ ობიეკტს, ეს ნიშნავს იმას რომ ანონიმური კლასი არის ზემდგომი კლასის ტიპის და ზემდგომი კლასის რეფერენციით ვახდენთ ქვემდგომი კლასის ინფორმაციაზე წვდომას, ამიტომ ანონიმური კლასი გახლავთ პოლიმორფიული, ეს ყველაფერი ნიშნავს იმას რომ წვდომა ინფორმაციებზე გვაქ მხოლოდ იმ ნაწილში, რომელიც არსებობს სუპერ კლასში. აქედან რა დასკვნის გაკეთება შეგიძლიათ? თუ ჩემი ბლოგები გადაკითხული გაქვთ ეს ერთი დასკვნის გაკეთების საშუალებას მოგცემთ, გამოიყენოთ ანონიმური კლასი აბსტრაკტულ კლასებთან და ინტერფეისებთან მუშაობის დროს. მაგალითად თუ კლასი იყენებს მხოლოდ ერთი სახის ინტერფეის არ არის მისი იმპლემენტირება საჭირო, საკმარისია მოვახდინოთ მისი ჩაბუდება როგორც ანონიმური კლასის და ეს იქნება მაღალი დონის კოდის წერის პრაკტიკა. დააკვირდით შემდეგ ფოტოს და შეაფასეთ, რომელი უფრო მოსახერხებელი წერის სტილია:

შავ ბლოკში არის ჩვეულებრივი კლასი, წითელ ბლოკში ანონიმური კლასი, ჩემი აზრით ანონიმური კლასის გამოყენება ბევრად უფრო ელეგანტური და მოსახერხებელია,  არ არის საჭირო ცალკე კლასის შექმნა, მერე ამ კლასიდან ობიეკტის შექმნა, რამოდენიმე ფაილის გახსნა, პირდაპირ ბლოკის ტანშივე ახდენ დეკლარირებას და ინსტანცირებას ერთდროულად.

                                     ანონიმური კლასი როგორც არგუმენტი
სანამ ანონიმურ კლას როგორ არგუმენტს გაგაცნობთ, ერთ პატარა მაგალითს გაჩვენებთ რო აღქმა გაგიადვილდეთ ოპერაციის.

როგორ მოვახდინოთ ამ ინფორმაციის კომპილირება? ლოგიკურად რო მივყვეთ დაგვჭირდება კლასი, რომელიც მოახდენს ინტერფეისის იმპლემენტირებას, აბსტრაკტული მეთოდის გადაწერას, მერე ამ კლასიდან ობიეკტს შევქმნით და რეფერენციულ ცვლადს გადავაწოდებთ კლასის მეთოდს რომლისგანაც ობიეკტს ვქმნით ფოტოზე.

დააკვირდით 3 კლასი დაგვჭირდა რო ერთი ინტერფეისის შიგთავსი გამოგვეყენებინა, სამი სხვა და სხვა ფაილი, რომელიც წაგვართმევს უამრავ დროს თუ კლასი დიდია, თუ უამრავი პაკეტი გვაქ და ვმუშაობთ პროეკტზე. როგორ ჩავეტიოთ დროში კოდის წერის დროს? თუ პროგრამისტი ხართ ეს ნომერ პირველი თავსატკივარია თქვენთვის,  ანონიმური კლასი როგორც არგუმენტი ამ შემთხვევაში უდრის უამრავ კლას, უბრალოდ ვახდენთ მის დეფინირებას პირდაპირ, როგორც არგუმენტის. რაც ნიშნავს რო ვახდენთ დეკლალირებას და ინსტანცირებას კლასის არგუმენტის(პარამეტრის) ადგილას. ცოტა უცნაურად ჟღერს ხო? დამიჯერეთ ისევე უცნაურად გამოიყურება როგორც ჟღერს. ნახეთ მაგალითი:

ინტერფეისის დეკლალირება და ინსტანცირება მოვახდინეთ მეთოდის პარამეტრის ადგილას, ანონიმური კლასის როგორ არგუმენტის სინტაქია ({ }); როდესაც ჩვეულებრივი ანონიმური კლასის სინტაქსია {}; კიდევ ერთხელ დააკვირდით ფოტოს. მეთოდის ფრჩხილი იხსნება, იწყება ინსტანცირება, იხსნება ახალი ფრჩხილი, ხდება მეთოდის გადაწერა, იხურება ინსტანციის ფრჩხილი, იხურება მეთოდის ფრჩხილი. საკმაოდ მარტივია. განსაკუთრებული წესები ამ კლას არ გააჩნია, უბრალოდ სინტაქი აქვს განსხვავებული.

                                                    ჩაბუდებული ინტერფეისი
ჩაბუდებული ინტერფეისის არსი მარტივია, ის გამოიყენება მაშინ როცა ლოგიკურად ახლოს მდგომი ინტერფეისების ქონა გინდათ ერთ ადგილზე. ან იდეალურად ავსებენ მატი ოეპრაციები ერთმანეთს. მისი გამოყენების წესებიც მარტივია.

1.   თუ ინტერფეისის ჩაბუდებას ვახდენთ ინტერფეისში მაშინ ჩაბუდებული ინტერფეისი არის ავტომატურად public მოდიფიკატორით, თუ მის ჩაბუდებას ვახდენთ კლასში, მოდიფიკატორი არჩევითია.
2. ჩაბუდებული ინტერფეისი არის სტატიკური ავტომატურად.

ნახეთ მაგალითი:

ინტერფეისის კლასში ჩაბუდებაც შეიძლება და კლასის ინტერფეისში ჩაბუდებაც გავრცელებული პრაკტიკა გახლავთ. ანუ შეზღუდვები არ გაქვთ, სინტაქსი ერთი აქვს და მაგალითებს არ გაჩვენებთ.

ამით ჩაბუდებულ კლასებზე საუბარს მოვრჩით. მე დაგემშვიდობებით. კარგად ბრძანდებოდეთ.