ობიექტის ინიციალიზაციის პრობლემა OOP აპლიკაციებში PHP-ში. გამოსავლის პოვნა რეესტრის, ქარხნის მეთოდის, სერვისის ლოკატორისა და დამოკიდებულების ინექციის შაბლონების გამოყენებით. OOP შაბლონები მაგალითებითა და აღწერებით ავტორიტეტული რეგისტრო php

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

ეს ისე ხდება, რომ პროგრამისტები ახდენენ წარმატებული გადაწყვეტილებების კონსოლიდაციას დიზაინის შაბლონების სახით. არსებობს უამრავი ლიტერატურა შაბლონებზე. ერიხ გამას, რიჩარდ ჰელმის, რალფ ჯონსონისა და ჯონ ვლასიდესის წიგნი „ოთხთა ბანდა“ „დიზაინის ნიმუშები“ და, ალბათ, მარტინ ფაულერის „საწარმოთა აპლიკაციის არქიტექტურის ნიმუშები“, რა თქმა უნდა, კლასიკად ითვლება. საუკეთესო რამ, რაც წავიკითხე მაგალითებით. PHP-ში - ეს. ისე ხდება, რომ მთელი ეს ლიტერატურა საკმაოდ რთულია იმ ადამიანებისთვის, რომლებმაც ახლახან დაიწყეს OOP-ის დაუფლება. ამიტომ გამიჩნდა იდეა წარმომედგინა ზოგიერთი ნიმუში, რომელიც მე ყველაზე გამოსადეგი ვარ, ძალიან გამარტივებული ფორმით. სიტყვებით, ეს სტატია ჩემი პირველი მცდელობაა ინტერპრეტაციის დიზაინის ნიმუშები KISS სტილში.
დღეს ჩვენ ვისაუბრებთ იმაზე, თუ რა პრობლემები შეიძლება წარმოიშვას ობიექტის ინიციალიზაციისას OOP აპლიკაციაში და როგორ შეგიძლიათ გამოიყენოთ ზოგიერთი პოპულარული დიზაინის ნიმუში ამ პრობლემების გადასაჭრელად.

მაგალითი

თანამედროვე OOP აპლიკაცია მუშაობს ათობით, ასობით და ზოგჯერ ათასობით ობიექტთან. მოდით, უფრო დეტალურად განვიხილოთ, თუ როგორ ხდება ამ ობიექტების ინიციალიზაცია ჩვენს აპლიკაციებში. ობიექტის ინიციალიზაცია ერთადერთი ასპექტია, რომელიც გვაინტერესებს ამ სტატიაში, ამიტომ გადავწყვიტე გამომეტოვებინა ყველა „დამატებითი“ განხორციელება.
ვთქვათ, ჩვენ შევქმენით სუპერ-დუპერ სასარგებლო კლასი, რომელსაც შეუძლია გაგზავნოს GET მოთხოვნა კონკრეტულ URI-ზე და დააბრუნოს HTML სერვერის პასუხიდან. ისე, რომ ჩვენი კლასი არც ისე მარტივი ჩანდეს, ნება მიეცით მანაც შეამოწმოს შედეგი და დაუშვას გამონაკლისი, თუ სერვერი პასუხობს „არასწორად“.

კლასის Grabber ( საჯარო ფუნქცია get($url) (/** აბრუნებს HTML კოდს ან აგდებს გამონაკლისს */) )

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

კლასი HtmlExtractor ( საჯარო ფუნქციის ფილტრი ($html, $selector) (/** აბრუნებს გაფილტრული ელემენტების მასივს */) )

ახლა წარმოიდგინეთ, რომ ჩვენ უნდა მივიღოთ ძიების შედეგები Google-ში მოცემული საკვანძო სიტყვებისთვის. ამისათვის ჩვენ შემოვიყვანთ სხვა კლასს, რომელიც გამოიყენებს Grabber კლასს მოთხოვნის გასაგზავნად და HtmlExtractor კლასს საჭირო შინაარსის ამოსაღებად. ის ასევე შეიცავს URI-ს აგების ლოგიკას, მიღებული HTML-ის გაფილტვრის სელექტორს და მიღებული შედეგების დამუშავებას.

კლასი GoogleFinder ( პირადი $grabber; პირადი $filter; საჯარო ფუნქცია __construct() ( $this->grabber = new Grabber(); $this->filter = new HtmlExtractor(); ) საჯარო ფუნქცია find($searchString) ( /* * აბრუნებს დაფუძნებული შედეგების მასივს */) )

შენიშნეთ, რომ Grabber და HtmlExtractor ობიექტების ინიციალიზაცია არის GoogleFinder კლასის კონსტრუქტორში? მოდით ვიფიქროთ იმაზე, თუ რამდენად კარგია ეს გადაწყვეტილება.
რა თქმა უნდა, კონსტრუქტორში ობიექტების შექმნის მყარი კოდირება არ არის კარგი იდეა. და ამიტომ. პირველ რიგში, ჩვენ ვერ შევძლებთ ადვილად გადავლახოთ Grabber კლასის სატესტო გარემოში, რათა თავიდან ავიცილოთ რეალური მოთხოვნის გაგზავნა. სამართლიანობისთვის, ღირს იმის თქმა, რომ ეს შეიძლება გაკეთდეს Reflection API-ის გამოყენებით. იმათ. ტექნიკური შესაძლებლობა არსებობს, მაგრამ ეს შორს არის ყველაზე მოსახერხებელი და აშკარა გზა.
მეორეც, იგივე პრობლემა წარმოიქმნება, თუ გვსურს ხელახლა გამოვიყენოთ GoogleFinder ლოგიკა სხვა Grabber და HtmlExtractor იმპლემენტაციებთან. დამოკიდებულებების შექმნა მყარი კოდირებულია კლასის კონსტრუქტორში. და საუკეთესო შემთხვევაში, ჩვენ შევძლებთ მემკვიდრეობით მივიღოთ GoogleFinder და გავაუქმოთ მისი კონსტრუქტორი. და მაშინაც კი, მხოლოდ იმ შემთხვევაში, თუ გატაცებისა და ფილტრის თვისებების ფარგლები დაცულია ან საჯარო.
ერთი ბოლო წერტილი, ყოველ ჯერზე, როდესაც ჩვენ ვქმნით ახალ GoogleFinder ობიექტს, მეხსიერებაში შეიქმნება დამოკიდებულების ობიექტების ახალი წყვილი, თუმცა ჩვენ შეგვიძლია საკმაოდ მარტივად გამოვიყენოთ ერთი Grabber ობიექტი და ერთი HtmlExtractor ობიექტი რამდენიმე GoogleFinder ობიექტში.
ვფიქრობ, თქვენ უკვე გესმით, რომ დამოკიდებულების ინიციალიზაცია უნდა გადავიდეს კლასის გარეთ. ჩვენ შეგვიძლია მოვითხოვოთ, რომ უკვე მომზადებული დამოკიდებულებები გადაეცეს GoogleFinder კლასის კონსტრუქტორს.

GoogleFinder კლასი ( პირადი $grabber; პირადი $filter; საჯარო ფუნქცია __construct(Grabber $grabber, HtmlExtractor $filter) ($this->grabber = $grabber; $this->filter = $filter; ) საჯარო ფუნქციის პოვნა ($searchString) ( /** აბრუნებს დაფუძნებული შედეგების მასივს */) )

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

კლასის კონტროლერი ( საჯარო ფუნქციის მოქმედება() ( /* ზოგიერთი მასალა */ $finder = ახალი GoogleFinder(new Grabber(), ახალი HtmlExtractor()); $results = $finder->

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

რეესტრის შაბლონი.

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

$registry = new ArrayObject(); $registry["grabber"] = new Grabber(); $registry["ფილტრი"] = new HtmlExtractor(); $registry["google_finder"] = ახალი GoogleFinder($registry["grabber"], $registry["ფილტრი"]);
საკმარისია ჩვენი ArrayObject კონტროლერს გადავცეთ და პრობლემა მოგვარებულია.

კლასის კონტროლერი ( პირადი $registry; საჯარო ფუნქცია __construct(ArrayObject $registry) ( $this->registry = $registry; ) საჯარო ფუნქციის მოქმედება() ( /* ზოგიერთი მასალა */ $results = $this->registry["google_finder" ]->find("ძებნის სტრიქონი"); /* გააკეთე რამე შედეგებით */ ) )

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

ქარხნის მეთოდი

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

Class Factory ( საჯარო ფუნქცია getGoogleFinder() ( დააბრუნეთ ახალი GoogleFinder($this->getGrabber(), $this->getHtmlExtractor()); ) პირადი ფუნქცია getGrabber() ( დაბრუნება new Grabber(); ) პირადი ფუნქცია getHtmlExtractor() ( დააბრუნეთ ახალი HtmlFiletr(); ) )

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

Class Factory ( პირადი $finder; საჯარო ფუნქცია getGoogleFinder() ( if (null === $this->finder) ( $this->finder = new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor() ); ) დააბრუნეთ $this->finder; ))

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

კლასის კონტროლერი ( კერძო $factory; საჯარო ფუნქცია __construct(Factory $factory) ( $this->factory = $factory; ) საჯარო ფუნქციის მოქმედება() ( /* ზოგიერთი მასალა */ $results = $this->factory->getGoogleFinder( )->find("ძებნის სტრიქონი"); /* გააკეთე რაღაც შედეგები */ ) )

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

სერვისის ლოკატორი

Service Locator-ის შაბლონი საშუალებას გაძლევთ გადაჭრათ ქარხნების ფრაგმენტაციის ნაკლებობა და მართოთ ობიექტების შექმნა ავტომატურად და ცენტრალიზებულად. თუ ამაზე დავფიქრდებით, შეგვიძლია შემოვიტანოთ დამატებითი აბსტრაქციის ფენა, რომელიც პასუხისმგებელია ჩვენს აპლიკაციაში ობიექტების შექმნასა და ამ ობიექტებს შორის ურთიერთობების მართვაზე. იმისათვის, რომ ამ ფენამ შეძლოს ჩვენთვის ობიექტების შექმნა, ჩვენ უნდა მივცეთ მას ცოდნა, თუ როგორ უნდა გააკეთოს ეს.
სერვისის ლოკატორის ნიმუშის პირობები:
  • სერვისი არის მზა ობიექტი, რომლის მიღებაც შესაძლებელია კონტეინერიდან.
  • სერვისის განმარტება – სერვისის ინიციალიზაციის ლოგიკა.
  • კონტეინერი (Service Container) არის ცენტრალური ობიექტი, რომელიც ინახავს ყველა აღწერილობას და შეუძლია შექმნას სერვისები მათზე დაყრდნობით.
ნებისმიერ მოდულს შეუძლია დაარეგისტრიროს თავისი სერვისის აღწერილობები. კონტეინერიდან გარკვეული სერვისის მისაღებად, ჩვენ მოგვიწევს მისი მოთხოვნა გასაღებით. სერვისის ლოკატორის დანერგვის მრავალი ვარიანტი არსებობს; უმარტივეს ვერსიაში შეგვიძლია გამოვიყენოთ ArrayObject როგორც კონტეინერი და დახურვა, როგორც სერვისების აღწერა.

Class ServiceContainer აფართოებს ArrayObject ( საჯარო ფუნქცია get($key) ( if (is_callable($this[$key])) ( return call_user_func($this[$key]); ) გადაყარეთ ახალი \RuntimeException("ვერ ვპოულობ სერვისის განმარტებას ქვემოთ გასაღები [$key]"); ))

შემდეგ განმარტებების რეგისტრაცია ასე გამოიყურება:

$container = new ServiceContainer(); $container["grabber"] = ფუნქცია () ( დაბრუნება new Grabber(); ); $container["html_filter"] = ფუნქცია () ( დააბრუნეთ ახალი HtmlExtractor(); ); $container["google_finder"] = ფუნქცია() გამოიყენე ($container) ( დააბრუნე ახალი GoogleFinder($container->get("grabber"), $container->get("html_filter")); );

და კონტროლერში გამოყენება ასეთია:

კლასის კონტროლერი ( კერძო $container; საჯარო ფუნქცია __construct(ServiceContainer $container) ( $this->container = $container; ) საჯარო ფუნქციის მოქმედება() ( /* ზოგიერთი მასალა */ $results = $this->container->get( "google_finder")->find("ძებნის სტრიქონი"); /* გააკეთე რამე შედეგებით */ ) )

სერვისის კონტეინერი შეიძლება იყოს ძალიან მარტივი, ან ძალიან რთული. მაგალითად, Symfony Service Container გთავაზობთ უამრავ მახასიათებელს: პარამეტრებს, სერვისების ფარგლებს, სერვისების ძიებას ტეგებით, ფსევდონიმებით, პირადი სერვისებით, კონტეინერში ცვლილებების შეტანის შესაძლებლობას ყველა სერვისის დამატების შემდეგ (კომპლიერის პასები) და მრავალი სხვა. DIExtraBundle კიდევ უფრო აფართოებს სტანდარტული განხორციელების შესაძლებლობებს.
მაგრამ დავუბრუნდეთ ჩვენს მაგალითს. როგორც ხედავთ, Service Locator არა მხოლოდ აგვარებს ყველა იგივე პრობლემას, როგორც წინა შაბლონები, არამედ აადვილებს მოდულების გამოყენებას საკუთარი სერვისის განმარტებებით.
გარდა ამისა, ჩარჩოს დონეზე მივიღეთ აბსტრაქციის დამატებითი დონე. კერძოდ, ServiceContainer::get მეთოდის შეცვლით შეგვიძლია, მაგალითად, ობიექტი შევცვალოთ პროქსით. და მარიონეტული ობიექტების გამოყენების ფარგლები შემოიფარგლება მხოლოდ დეველოპერის ფანტაზიით. აქ შეგიძლიათ განახორციელოთ AOP პარადიგმა, LazyLoading და ა.შ.
მაგრამ დეველოპერების უმეტესობა მაინც განიხილავს Service Locator-ს ანტი-ნიმუშად. რადგან, თეორიულად, შეიძლება გვქონდეს იმდენი ე.წ Container Aware კლასები (ანუ კლასები, რომლებიც შეიცავს მითითებას კონტეინერზე). მაგალითად, ჩვენი კონტროლერი, რომლის შიგნითაც შეგვიძლია მივიღოთ ნებისმიერი სერვისი.
ვნახოთ, რატომ არის ეს ცუდი.
პირველი, ხელახლა ტესტირება. იმის ნაცვლად, რომ შექმნათ დაცინვები მხოლოდ ტესტებში გამოყენებული კლასებისთვის, თქვენ მოგიწევთ მთელი კონტეინერის დაცინვა ან ნამდვილი კონტეინერის გამოყენება. პირველი ვარიანტი არ გიხდებათ, რადგან... თქვენ უნდა დაწეროთ ბევრი არასაჭირო კოდი ტესტებში, მეორე იმიტომ, რომ ეს ეწინააღმდეგება ერთეულის ტესტირების პრინციპებს და შეიძლება გამოიწვიოს დამატებითი ხარჯები ტესტების შესანარჩუნებლად.
მეორეც, გაგვიჭირდება რეფაქტირება. ნებისმიერი სერვისის (ან ServiceDefinition) შეცვლით კონტეინერში, ჩვენ იძულებული ვიქნებით შევამოწმოთ ყველა დამოკიდებული სერვისიც. და ეს პრობლემა ვერ მოგვარდება IDE-ის დახმარებით. ასეთი ადგილების პოვნა მთელ აპლიკაციაში არც ისე ადვილი იქნება. გარდა დამოკიდებული სერვისებისა, თქვენ ასევე უნდა შეამოწმოთ ყველა ის ადგილი, სადაც რეფაქტორირებული სერვისი მიიღება კონტეინერიდან.
ისე, მესამე მიზეზი არის ის, რომ კონტეინერიდან სერვისების უკონტროლო ამოღება ადრე თუ გვიან გამოიწვევს კოდის არეულობას და არასაჭირო დაბნეულობას. ამის ახსნა რთულია, უბრალოდ დაგჭირდებათ მეტი და მეტი დრო გაატაროთ იმის გასაგებად, თუ როგორ მუშაობს ესა თუ ის სერვისი, სხვა სიტყვებით რომ ვთქვათ, თქვენ შეგიძლიათ სრულად გაიგოთ რას აკეთებს ან როგორ მუშაობს კლასი მხოლოდ მისი მთლიანი კოდის წაკითხვით.

დამოკიდებულების ინექცია

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

$container["google_finder"] = function() use ($container) ( დაბრუნება ახალი კონტროლერი (Grabber $grabber); );

და მოიშორეთ კონტეინერი კონტროლერში:

კლასის კონტროლერი ( პირადი $finder; საჯარო ფუნქცია __construct(GoogleFinder $finder) ( $this->finder = $finder; ) საჯარო ფუნქციის მოქმედება() ( /* ზოგიერთი მასალა */ $results = $this->finder->find( "ძებნის სტრიქონი"); /* გააკეთე რამე შედეგებით */ ) )

ამ მიდგომას (როდესაც სერვისის კონტეინერზე წვდომა არ არის უზრუნველყოფილი კლიენტის კლასებისთვის) დამოკიდებულების ინექცია ეწოდება. მაგრამ ამ შაბლონს ასევე აქვს როგორც დადებითი, ასევე უარყოფითი მხარეები. სანამ ჩვენ ვიცავთ ერთიანი პასუხისმგებლობის პრინციპს, კოდი ძალიან ლამაზად გამოიყურება. უპირველეს ყოვლისა, ჩვენ გავათავისუფლეთ კონტეინერი კლიენტის კლასებში, რაც მათ კოდს უფრო მკაფიო და მარტივს გავხდით. ჩვენ შეგვიძლია მარტივად შევამოწმოთ კონტროლერი საჭირო დამოკიდებულებების შეცვლით. ჩვენ შეგვიძლია შევქმნათ და შევამოწმოთ თითოეული კლასი სხვებისგან დამოუკიდებლად (კონტროლერების კლასების ჩათვლით) TDD ან BDD მიდგომის გამოყენებით. ტესტების შექმნისას შეგვიძლია კონტეინერიდან აბსტრაქცია და მოგვიანებით დავამატოთ განმარტება, როცა კონკრეტული ინსტანციების გამოყენება გვჭირდება. ეს ყველაფერი ჩვენს კოდს უფრო მარტივს და ნათელს გახდის და ტესტირებას უფრო გამჭვირვალეს გახდის.
მაგრამ აუცილებელია მონეტის მეორე მხარის აღნიშვნა. ფაქტია, რომ კონტროლერები ძალიან სპეციფიკური კლასებია. დავიწყოთ იმით, რომ კონტროლერი, როგორც წესი, შეიცავს მოქმედებების ერთობლიობას, რაც ნიშნავს, რომ ის არღვევს ერთიანი პასუხისმგებლობის პრინციპს. შედეგად, კონტროლერის კლასს შეიძლება ჰქონდეს ბევრად მეტი დამოკიდებულება, ვიდრე საჭიროა კონკრეტული მოქმედების შესასრულებლად. ზარმაცი ინიციალიზაციის გამოყენებით (ობიექტი ინსტანცირდება მისი პირველი გამოყენების დროს, მანამდე კი გამოიყენება მსუბუქი პროქსი) გარკვეულწილად წყვეტს შესრულების პრობლემას. მაგრამ არქიტექტურული თვალსაზრისით, კონტროლერზე მრავალი დამოკიდებულების შექმნა ასევე არ არის მთლად სწორი. გარდა ამისა, კონტროლერების ტესტირება, როგორც წესი, არასაჭირო ოპერაციაა. ყველაფერი, რა თქმა უნდა, დამოკიდებულია იმაზე, თუ როგორ არის ორგანიზებული ტესტირება თქვენს აპლიკაციაში და იმაზე, თუ როგორ გრძნობთ თავს ამის შესახებ.
წინა აბზაციდან მიხვდით, რომ Dependency Injection-ის გამოყენება არქიტექტურულ პრობლემებს მთლიანად არ აღმოფხვრის. ამიტომ, იფიქრეთ იმაზე, თუ რამდენად მოსახერხებელი იქნება თქვენთვის, შეინახოთ თუ არა კონტეინერის ბმული კონტროლერებში. აქ არ არსებობს ერთი სწორი გამოსავალი. ვფიქრობ, ორივე მიდგომა კარგია, სანამ კონტროლერის კოდი მარტივი რჩება. მაგრამ, რა თქმა უნდა, თქვენ არ უნდა შექმნათ Conatiner Aware სერვისები კონტროლერების გარდა.

დასკვნები

ისე, დროა შევაჯამოთ ყველაფერი რაც ითქვა. და ბევრი ითქვა... :)
ასე რომ, ობიექტების შექმნის სამუშაოს სტრუქტურირებისთვის, შეგვიძლია გამოვიყენოთ შემდეგი შაბლონები:
  • რეესტრი: შაბლონს აქვს აშკარა უარყოფითი მხარეები, რომელთაგან ყველაზე ძირითადია ობიექტების შექმნის აუცილებლობა მათ საერთო კონტეინერში მოთავსებამდე. ცხადია, ჩვენ უფრო მეტ პრობლემას მივიღებთ, ვიდრე სარგებელს მისი გამოყენებით. ეს აშკარად არ არის შაბლონის საუკეთესო გამოყენება.
  • ქარხნული მეთოდი: ნიმუშის მთავარი უპირატესობა: ობიექტები იქმნება აშკარად. მთავარი მინუსი: კონტროლერები ან თავად უნდა იზრუნონ ქარხნების შექმნაზე, რაც მთლიანად არ წყვეტს კლასების სახელების მყარი კოდირების პრობლემას, ან ჩარჩო პასუხისმგებელი უნდა იყოს კონტროლერებისთვის ყველა საჭირო ქარხნის მიწოდებაზე, რაც არც ისე აშკარა იქნება. არ არსებობს ობიექტების შექმნის პროცესის ცენტრალიზებული მართვა.
  • სერვისის ლოკატორი: უფრო მოწინავე გზა ობიექტების შექმნის კონტროლისთვის. აბსტრაქციის დამატებითი დონე შეიძლება გამოყენებულ იქნას საერთო ამოცანების ავტომატიზაციისთვის, რომლებიც გვხვდება ობიექტების შექმნისას. Მაგალითად:
    class ServiceContainer აფართოებს ArrayObject ( საჯარო ფუნქცია get($key) ( if (is_callable ($this[$key])) ($obj = call_user_func($this[$key]); if ($obj ინსტანცია RequestAwareInterface) ( $obj- >setRequest($this->get("მოთხოვნა")); ) დააბრუნეთ $obj; ) გადაყარეთ ახალი \RuntimeException("ვერ ვპოულობ სერვისის განმარტებას გასაღების ქვეშ [ $key ]"); ) )
    Service Locator-ის მინუსი არის ის, რომ კლასების საჯარო API წყვეტს ინფორმატიულს. აუცილებელია კლასის მთელი კოდის წაკითხვა იმის გასაგებად, თუ რა სერვისებია გამოყენებული მასში. კლასი, რომელიც შეიცავს კონტეინერზე მითითებას, უფრო რთული შესამოწმებელია.
  • დამოკიდებულების ინექცია: არსებითად ჩვენ შეგვიძლია გამოვიყენოთ იგივე სერვისის კონტეინერი, როგორც წინა შაბლონისთვის. განსხვავება ისაა, თუ როგორ გამოიყენება ეს კონტეინერი. თუ თავიდან ავიცილებთ კლასების კონტეინერზე დამოკიდებულებას, მივიღებთ მკაფიო და მკაფიო კლასის API-ს.
ეს არ არის ყველაფერი, რაც მინდა გითხრათ PHP აპლიკაციებში ობიექტების შექმნის პრობლემის შესახებ. ასევე არსებობს პროტოტიპის ნიმუში, ჩვენ არ განვიხილავთ Reflection API-ს გამოყენებას, ჩვენ გვერდზე დავტოვეთ სერვისების ზარმაცი დატვირთვის პრობლემა და მრავალი სხვა ნიუანსი. სტატია საკმაოდ გრძელი აღმოჩნდა, ამიტომ დავასრულებ :)
მე მინდოდა მეჩვენებინა, რომ დამოკიდებულების ინექცია და სხვა შაბლონები არ არის ისეთი რთული, როგორც ჩვეულებრივ გვჯერა.
თუ ვსაუბრობთ Dependency Injection-ზე, მაშინ, მაგალითად, არსებობს ამ ნიმუშის KISS განხორციელებები

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

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

დროის დასაწყისი

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

მაგრამ არც ისე სწრაფად. იმისათვის, რომ ის იმუშაოს, საჭიროა ცოტა ღრმად ჩასვლა. ჩვენი სისტემა წარმოადგენს იერარქიას და ნებისმიერ იერარქიულ სისტემას აქვს დასაწყისი: სამონტაჟო წერტილი Linux-ში, ლოკალური დისკი Windows-ში, სახელმწიფოს სისტემა, კომპანია, საგანმანათლებლო დაწესებულება და ა.შ. ასეთი სისტემის თითოეული ელემენტი ვიღაცას ექვემდებარება და შეიძლება ჰყავდეს რამდენიმე ქვეშევრდომი, ხოლო მეზობლებისა და მათი ქვეშევრდომების მისამართით ის იყენებს ზემდგომებს ან თავად საწყისს. იერარქიული სისტემის კარგი მაგალითია საგვარეულო ხე: ირჩევა საწყისი წერტილი - რომელიმე წინაპარი - და წავალთ. ჩვენს სისტემაში ასევე გვჭირდება საწყისი წერტილი, საიდანაც გავზრდით ფილიალებს - მოდულებს, დანამატებს და ა.შ. ჩვენ გვჭირდება რაიმე სახის ინტერფეისი, რომლის მეშვეობითაც ჩვენი ყველა მოდული "კომუნიკაციას" მოახდენს. შემდგომი მუშაობისთვის, ჩვენ უნდა გავეცნოთ კონცეფციას ” დიზაინის ნიმუში" და მათი რამდენიმე განხორციელება.

დიზაინის ნიმუშები

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

დიზაინის შაბლონებს ასევე ხშირად უწოდებენ დიზაინის შაბლონებს ან უბრალოდ შაბლონებს (ინგლისური სიტყვიდან ნიმუში, ითარგმნება როგორც "ნიმუში"). შემდგომ სტატიებში, როდესაც ვსაუბრობ შაბლონებზე, ვგულისხმობ დიზაინის ნიმუშებს.

ყველა სახის საშინელი (და არც თუ ისე საშინელი) ნიმუშის სახელების უზარმაზარი სიიდან, ჯერჯერობით მხოლოდ ორი გვაინტერესებს: რეესტრი და სინგლი.

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

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

განმარტებები არ არის სრული ან განზოგადებული, მაგრამ ეს საკმარისია გასაგებად. ჩვენ მაინც არ გვჭირდება ისინი ცალკე. ჩვენ გვაინტერესებს თითოეული ამ შაბლონის შესაძლებლობები, მაგრამ ერთ კლასში: ასეთი ნიმუში ე.წ singleton რეესტრი ან Singleton Registry.

რას მოგვცემს ეს?
  • გარანტირებული გვექნება რეესტრის ერთი ეგზემპლარის არსებობა, რომელშიც ნებისმიერ დროს შეგვიძლია დავამატო ობიექტები და გამოვიყენოთ კოდის ნებისმიერი ადგილიდან;
  • შეუძლებელი იქნება მისი კოპირება და PHP ენის სხვა არასასურველი (ამ შემთხვევაში) მაგიის გამოყენება.

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

კარგი, საკმარისი სიტყვები, მოდით შევქმნათ!

პირველი ხაზები

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

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

ვინაიდან ჩვენ ვიცავთ რაღაცას, რომელიც დაკავშირებულია, ჩვენ დავარქმევთ მუდმივას _PLUGSECURE_:

თუ (!defined("_PLUGSECURE_")) ( die ("მოდულის პირდაპირი გამოძახება აკრძალულია!"); )

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

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

ინტერფეისი StorableObject ( საჯარო სტატიკური ფუნქცია getClassName(); )

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

დადგა დრო ჩვენი მარტოხელა რეესტრის კლასისთვის. ჩვენ დავიწყებთ კლასის და მისი ზოგიერთი ცვლადის გამოცხადებით:

კლასის რეესტრი ახორციელებს StorableObject-ს ( //მოდულის სახელი იკითხება private static $className = "Registry"; //registry instance private static $instance; //ობიექტების მასივი private static $objects = array();

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

საჯარო სტატიკური ფუნქცია singleton() ( if(!isset(self::$instance)) ( $obj = __CLASS__; self::$instance = new $obj; ) დაბრუნების self::$instance; )

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

პირადი ფუნქცია __construct()() პირადი ფუნქცია __clone()() პირადი ფუნქცია __wakeup()() პირადი ფუნქცია __sleep() ()

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

//$object - გზა დაკავშირებული ობიექტისკენ //$key - ობიექტზე წვდომის გასაღები რეგისტრის საჯარო ფუნქციაში addObject($key, $object) ( require_once($object); //ობიექტის შექმნა ობიექტების მასივში self::$objects[ $key] = new $key(self::$instance); ) //ალტერნატიული მეთოდი ჯადოსნური საჯარო ფუნქციის მეშვეობით __set($key, $object) ($this->addObject($key, $ ობიექტი);)

ახლა, ჩვენს რეესტრში ობიექტის დასამატებლად, შეგვიძლია გამოვიყენოთ ორი ტიპის ჩანაწერი (ვთქვათ, რომ ჩვენ უკვე შევქმენით რეესტრის მაგალითი $registry და გვინდა დავამატოთ config.php ფაილი):

$registry->addObject("config", "/core/config.php"); //რეგულარული მეთოდი $registry->config = "/core/config.php"; //PHP ჯადოსნური ფუნქციის საშუალებით __set()

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

რომელი ჩანაწერი გამოიყენოთ, თქვენზეა დამოკიდებული. მე მაგიური მეთოდით ჩაწერა მირჩევნია - ის უფრო "ლამაზია" და მოკლე.

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

//მიიღეთ ობიექტი რეგისტრიდან //$key - გასაღები მასივის საჯარო ფუნქციის getObject($key) ( //შეამოწმეთ არის თუ არა ცვლადი ობიექტი if (is_object(self::$objects[$key])) ( //თუ ასეა, მაშინ ჩვენ ვაბრუნებთ ამ ობიექტს დააბრუნებს self::$objects[$key];) ) //მსგავსი მეთოდი ჯადოსნური საჯარო ფუნქციის მეშვეობით __get($key) ( if (is_object(self::$objects[$ გასაღები])) ( დააბრუნეთ საკუთარი თავი: :$objects[$key]; ) )

როგორც სეტერთან, ობიექტზე წვდომის მისაღებად გვექნება 2 ექვივალენტი ჩანაწერი:

$registry->getObject("config"); //რეგულარული მეთოდი $registry->config; //PHP მაგიური ფუნქციის საშუალებით __get()

ყურადღებიანი მკითხველი მაშინვე დასვამს კითხვას: რატომ __set() ჯადოსნურ ფუნქციაში მე უბრალოდ ვუწოდებ რეგულარულ (არაჯადოსნურ) ობიექტს დამატების ფუნქციას, მაგრამ __get() მიმღებში იგივე ზარის ნაცვლად ვაკოპირებ getObject() ფუნქციის კოდს?მართალი გითხრათ, ამ კითხვაზე საკმარისად ზუსტად ვერ გიპასუხებთ, უბრალოდ ვიტყვი, რომ სხვა მოდულებში __get() მაგიასთან მუშაობისას მქონდა პრობლემები, მაგრამ კოდის „პირველად“ გადაწერისას ასეთი პრობლემები არ არის.

შესაძლოა, ამიტომაც ხშირად ვხედავდი სტატიებში საყვედურებს PHP-ის ჯადოსნური მეთოდების მიმართ და რჩევებს, რომ თავიდან აიცილოთ მათი გამოყენება.

"ყველა მაგიას აქვს ფასი." © Rumplestiltskin

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

$registry = Registry::singleton();

ჩვენ მივიღებთ შეცდომას:

Ფატალური შეცდომა: კლასის რეესტრი შეიცავს 1 აბსტრაქტულ მეთოდს და, შესაბამისად, უნდა გამოცხადდეს აბსტრაქტულად ან დანერგოს დარჩენილი მეთოდები (StorableObject::getClassName) ...

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

საჯარო სტატიკური ფუნქცია getClassName() ( დააბრუნეთ self::$className; )

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

საჯარო ფუნქცია getObjectsList() ( //მაივი, რომელსაც დავაბრუნებთ $names = array(); //მიიღეთ თითოეული ობიექტის სახელი foreach ობიექტების მასივიდან (self::$objects როგორც $obj) ( $names = $ obj->getClassName() ;) //დაამატეთ რეგისტრის მოდულის სახელი მასივს array_push($names, self::getClassName()); //და დააბრუნეთ $names;)

Სულ ეს არის. ეს ასრულებს რეესტრს. შევამოწმოთ მისი ნამუშევარი? შემოწმებისას დაგვჭირდება რაღაცის დაკავშირება - იყოს კონფიგურაციის ფაილი. შექმენით ახალი core/config.php ფაილი და დაამატეთ მინიმალური შინაარსი, რომელსაც ჩვენი რეესტრი მოითხოვს:

//არ ​​დაგავიწყდეთ შეამოწმოთ მუდმივი if (!defined("_PLUGSECURE_")) ( die("მოდულის პირდაპირი გამოძახება აკრძალულია!"); ) class Config ( //მოდულის სახელი, იკითხება კერძო სტატიკური $className = "Config "; საჯარო სტატიკური ფუნქცია getClassName() ( დააბრუნეთ self::$className; ) )

Რაღაც მაგდაგვარი. ახლა მოდით გადავიდეთ თავად გადამოწმებაზე. ჩვენი პროექტის ძირში შექმენით ფაილი index.php და ჩაწერეთ მასში შემდეგი კოდი:

Define ("_PLUGSECURE_", true); //განსაზღვრა მუდმივი ობიექტებზე პირდაპირი წვდომისგან დასაცავად. require_once "/core/registry.php"; //დააკავშირა რეგისტრი $registry = Registry::singleton(); //შექმნა რეგისტრი singleton ინსტანცია $registry->config = "/core/config.php"; //დააკავშირეთ ჩვენი, ჯერჯერობით უსარგებლო, კონფიგურაცია //აჩვენეთ დაკავშირებული მოდულების სახელები echo " დაკავშირებულია"; foreach ($registry->

  • " . $names ."
  • "; }

    ან, თუ მაინც თავს არიდებთ მაგიას, მაშინ მე-5 ხაზი შეიძლება შეიცვალოს ალტერნატიული მეთოდით:

    Define ("_PLUGSECURE_", true); //განსაზღვრა მუდმივი ობიექტებზე პირდაპირი წვდომისგან დასაცავად. require_once "/core/registry.php"; //დააკავშირა რეგისტრი $registry = Registry::singleton(); //შექმნა რეგისტრი singleton ინსტანცია $registry->addObject("config", "/core/config.php"); //დააკავშირეთ ჩვენი, ჯერჯერობით უსარგებლო, კონფიგურაცია //აჩვენეთ დაკავშირებული მოდულების სახელები echo " დაკავშირებულია"; foreach ($registry->getObjectsList() როგორც $names) (echo "

  • " . $names ."
  • "; }

    ახლა გახსენით ბრაუზერი და ჩაწერეთ http://localhost/index.php ან უბრალოდ http://localhost/ მისამართების ზოლში (რელევანტურია, თუ იყენებთ სტანდარტულ ღია სერვერს ან ვებ სერვერის მსგავს პარამეტრებს)

    შედეგად, ჩვენ უნდა ვნახოთ მსგავსი რამ:

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

    დღეს ჩვენ ამაზე შევჩერდებით. შემდეგ სტატიაში ჩვენ დავუბრუნდებით მონაცემთა ბაზას და დავწერთ კლასს MySQL SUDB-თან მუშაობისთვის, დავაკავშირებთ მას რეესტრში და შევამოწმებთ სამუშაოს პრაქტიკაში. Გნახავ!

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

    განვიხილოთ ორივე ვარიანტი თანმიმდევრობით.

    რასაც ჰქვია "სუფთა რეესტრი" ან უბრალოდ რეესტრი არის კლასის იმპლემენტაცია სტატიკური ინტერფეისით. მთავარი განსხვავება სინგლტონის შაბლონისგან არის ის, რომ ის ბლოკავს კლასის მინიმუმ ერთი ინსტანციის შექმნის შესაძლებლობას. ამის გათვალისწინებით, აზრი არ აქვს ჯადოსნური მეთოდების დამალვას __clone() და __wakeup() კერძო ან დაცული მოდიფიკატორის მიღმა.

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

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

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

    • სხვა ტიპის დამოკიდებულების დანერგვა - რეესტრის გასაღებებზე;
    • რეესტრის ორ განსხვავებულ კლავიშს შეიძლება ჰქონდეს მინიშნება იმავე ობიექტზე

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

    მეორე პრობლემა მოგვარებულია Registry::set() მეთოდში შემოწმების შემოღებით:

    საჯარო სტატიკური ფუნქციის ნაკრები ($key, $item) (თუ (!array_key_exists($key, self::$_registry)) ( foreach (self::$_registry როგორც $val) (თუ ($val === $item) ( ჩააგდე ახალი გამონაკლისი ("ერთეული უკვე არსებობს"); ) ) self::$_registry[$key] = $item; ) )

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

    $instance = Singleton::getInstance(); $instance->Foo();

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

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

    _registry[$key] = $object; ) სტატიკური საჯარო ფუნქცია get($key) ( return self::getInstance()->_registry[$key]; ) პირადი ფუნქცია __wakeup() () პირადი ფუნქცია __construct() ( ) პირადი ფუნქცია __clone() ( ) ) ?>

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

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

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

    სინგლტონი

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

    კლასი Singleton ( კერძო სტატიკური $instance = null; პირადი ფუნქცია __construct())( /* ... @return Singleton */ ) // დაცვა ახალი Singleton პირადი ფუნქციის საშუალებით __clone() ( /* ... @return Singleton * / ) // შექმნისგან დაცვა კლონირების პირადი ფუნქციის საშუალებით __wakeup() ( /* ... @return Singleton */ ) // დაცვა შექმნისგან არასერიალიზაციის საჯარო სტატიკური ფუნქციის მეშვეობით getInstance() ( if (is_null(self::$instance ) ) ( self::$instance = ახალი მე; ) დაბრუნება self::$instance; ) )

    რეესტრი (რეესტრი, ჩანაწერების ჟურნალი)

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

    კლასის რეესტრი ( კერძო $registry = array (); საჯარო ფუნქციების ნაკრები ($key, $object) ( $this->registry[$key] = $object; ) საჯარო ფუნქცია get($key) ( დაბრუნება $this->registry [$ გასაღები];))

    სინგლტონის რეესტრი- არ აურიოთ)

    "რეესტრი" ხშირად არის "მარტოხელა", მაგრამ ეს ყოველთვის ასე არ უნდა იყოს. მაგალითად, ჩვენ შეგვიძლია შევქმნათ რამდენიმე ჟურნალი ბუღალტრული აღრიცხვის განყოფილებაში, ერთ თანამშრომელში "A"-დან "M"-მდე, მეორეში "N"-დან "Z"-მდე. თითოეული ასეთი ჟურნალი იქნება „რეესტრი“, მაგრამ არა „ერთი“, რადგან უკვე 2 ჟურნალია.

    კლასი SingletonRegistry ( private static $instance = null; private $registry = array(); პირადი ფუნქცია __construct() ( /* ... @return Singleton */ ) // დაცვა ახალი Singleton პირადი ფუნქციის საშუალებით __clone() (/ * ... @return Singleton */ ) // დაცვა შექმნისგან კლონირების პირადი ფუნქციის მეშვეობით __wakeup() ( /* ... @return Singleton */ ) // დაიცავით შექმნისგან unserialize საჯარო სტატიკური ფუნქციის მეშვეობით getInstance() ( if ( is_null(self::$instance)) (self::$instance = ახალი თვით; ) return self::$instance; ) საჯარო ფუნქციის ნაკრები ($key, $object) ($this->registry[$key] = $ ობიექტი; ) საჯარო ფუნქცია get($key) ( დაბრუნება $this->registry[$key]; ) )

    მულტიტონი ("სინგლების" აუზი) ან სხვა სიტყვებითრეესტრი სინგლტონი ) - არ აურიოთ Singleton Registry-ში

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

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

    კლასის მონაცემთა ბაზა ( კერძო სტატიკური $ინსტანციები = მასივი(); პირადი ფუნქცია __construct() ( ) პირადი ფუნქცია __clone() ( ) საჯარო სტატიკური ფუნქცია getInstance($key) ( if(!array_key_exists($key, self::$instances)) ( self::$instances[$key] = new self(); ) return self::$instances[$key]; ) ) $master = მონაცემთა ბაზა::getInstance("master"); var_dump ($ master); // ობიექტი(მონაცემთა ბაზა)#1 (0) ( ) $logger = მონაცემთა ბაზა::getInstance("logger"); var_dump ($logger); // ობიექტი(მონაცემთა ბაზა)#2 (0) ( ) $masterDupe = მონაცემთა ბაზა::getInstance("მასტერი"); var_dump ($masterDupe); // ობიექტი(მონაცემთა ბაზა)#1 (0) ( ) // ფატალური შეცდომა: გამოძახება კერძო მონაცემთა ბაზაზე::__construct() არასწორი კონტექსტიდან $dbFatalError = new Database(); // PHP ფატალური შეცდომა: გამოძახება პირად მონაცემთა ბაზაზე::__clone() $dbCloneError = კლონი $masterDupe;

    ობიექტის აუზი

    არსებითად ეს ნიმუშია "რეესტრი", რომელიც ინახავს მხოლოდ ობიექტებს, არ არის სტრიქონები, მასივები და ა.შ. მონაცემთა ტიპები.

    ქარხანა

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

    აბსტრაქტული კლასი AnimalAbstract ( დაცული $species; საჯარო ფუნქცია getSpecies() ( return $this->species; ) ) class Cat აფართოებს AnimalAbstract ( დაცული $species = "cat"; ) class Dog extends AnimalAbstract ( protected $species = "dog"; ) კლასი AnimalFactory ( საჯარო სტატიკური ფუნქციის ქარხანა ($animal) ( გადამრთველი ($animal) ( case "cat": $obj = new Cat(); break; case "dog": $obj = new Dog(); break; default : throw new Exception("ცხოველთა ქარხანამ ვერ შექმნა სახეობის ცხოველი "" . $animal . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::factory("cat"); // ობიექტი(კატა)#1 echo $cat->getSpecies(); // კატა $ძაღლი = AnimalFactory::factory("ძაღლი"); // ობიექტი(ძაღლი)#1 echo $dog->getSpecies(); // ძაღლი $hippo = AnimalFactory::factory("hippopotamus"); // ეს გამოიწვევს გამონაკლისს

    თქვენი ყურადღება მინდა გავამახვილო იმ ფაქტზე, რომ ქარხნული მეთოდი ასევე არის ნიმუში, მას ჰქვია Factory მეთოდი.

    მშენებელი (მშენებელი)

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

    კლასის ბოთლი ( საჯარო $სახელი; საჯარო $ლიტრი; ) /** * ყველა შემქმნელმა უნდა */ ინტერფეისი BottleBuilderInterface ( საჯარო ფუნქცია setName(); საჯარო ფუნქცია setLiters(); საჯარო ფუნქცია getResult(); ) კლასი CocaColaBuilder ახორციელებს BottleBuilderInterface ( კერძო $ ბოთლი; საჯარო ფუნქცია __construct() ($this->bottle = new Bottle(); ) საჯარო ფუნქცია setName($value) ($this->bottle->name = $value; ) საჯარო ფუნქცია setLiters($value) ($ this->bottle->liters = $value; ) საჯარო ფუნქცია getResult() ( return $this->bottle; ) ) $juice = new CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $juice->setLiters(2); $juice->getResult();

    Პროტოტიპი

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

    $newJuice = $juice-ის კლონი;

    ზარმაცი ინიციალიზაცია

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

    ადაპტერი ან შეფუთვა (ადაპტერი, შეფუთვა)

    ეს ნიმუში სრულად შეესაბამება მის სახელს. იმისათვის, რომ "საბჭოთა" დანამატი იმუშაოს ევრო სოკეტის საშუალებით, საჭიროა ადაპტერი. სწორედ ამას აკეთებს „ადაპტერი“ – ის ემსახურება როგორც შუალედურ ობიექტს ორ სხვას შორის, რომლებსაც არ შეუძლიათ უშუალოდ ერთმანეთთან მუშაობა. მიუხედავად განმარტებისა, პრაქტიკაში მე მაინც ვხედავ განსხვავებას ადაპტერსა და Wrapper-ს შორის.

    კლასი MyClass ( საჯარო ფუნქციის მეთოდიA() () ) კლასი MyClassWrapper ( საჯარო ფუნქცია __construct())( $this->myClass = new MyClass(); ) საჯარო ფუნქცია __call($name, $არგუმენტები)( Log::info(" თქვენ აპირებთ გამოიძახოთ $name მეთოდი."); დააბრუნეთ call_user_func_array(array($this->myClass, $name), $arguments); ) ) $obj = new MyClassWrapper(); $obj->methodA();

    დამოკიდებულების ინექცია

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

    კლასი AppleJuice () // ეს მეთოდი არის დამოკიდებულების ინექციის ნიმუშის პრიმიტიული განხორციელება და შემდგომში ნახავთ ამ ფუნქციას getBottleJuice()) ($obj = ახალი Ვაშლის წვენი Ვაშლის წვენი)( დაბრუნება $obj; ) ) $bottleJuice = getBottleJuice();

    ახლა წარმოიდგინეთ, რომ ჩვენ აღარ გვინდა ვაშლის წვენი, ჩვენ გვინდა ფორთოხლის წვენი.

    კლასი AppleJuice() კლასი Ფორთოხლის წვენი() // ეს მეთოდი ახორციელებს დამოკიდებულების ინექციის ფუნქციას getBottleJuice())( $obj = ახალი Ფორთოხლის წვენი; // შეამოწმეთ ობიექტი, იმ შემთხვევაში, თუ ლუდი დაგვცურეს (ლუდი წვენი არ არის) if($obj instanceof Ფორთოხლის წვენი)( დააბრუნეთ $obj; ) )

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

    ინტერფეისი Juice () კლასი AppleJuice ახორციელებს Juice () Class OrangeJuice ახორციელებს Juice () ფუნქციას getBottleJuice())( $obj = new OrangeJuice; // შეამოწმეთ ობიექტი, იმ შემთხვევაში, თუ მათ ლუდი დაგვიცურეს (ლუდი წვენი არ არის) if($obj მაგალითი წვენი)( დააბრუნეთ $obj; ) )

    დამოკიდებულების ინვერსია ზოგჯერ აირია დამოკიდებულების ინექციით, მაგრამ არ არის საჭირო მათი აღრევა, რადგან დამოკიდებულების ინვერსია არის პრინციპი და არა ნიმუში.

    სერვისის ლოკატორი

    "სერვისის ლოკატორი" არის "დამოკიდებულების ინექციის" განხორციელების მეთოდი. ის აბრუნებს სხვადასხვა ტიპის ობიექტებს ინიციალიზაციის კოდის მიხედვით. დაე, დავალება იყოს მშენებლის, ქარხნის ან სხვა რამის მიერ შექმნილი ჩვენი წვენის პაკეტის მიტანა, სადაც მყიდველს სურს. ლოკატორს ვეუბნებით „მოგვეცით მიტანის სერვისი“ და ვთხოვთ სერვისს, რომ წვენი მიაწოდოს სასურველ მისამართზე. დღეს არის ერთი სერვისი და ხვალ შეიძლება იყოს მეორე. ჩვენთვის არ აქვს მნიშვნელობა რა კონკრეტული სერვისია, ჩვენთვის მნიშვნელოვანია ვიცოდეთ, რომ ეს სერვისი მიაწვდის იმას, რასაც ჩვენ ვეუბნებით და სად ვეუბნებით. თავის მხრივ, სერვისები ახორციელებენ „მიწოდება<предмет>on<адрес>».

    თუ ვსაუბრობთ რეალურ ცხოვრებაზე, მაშინ, ალბათ, სერვისის ლოკატორის კარგი მაგალითი იქნება PDO PHP გაფართოება, რადგან დღეს ჩვენ ვმუშაობთ MySQL მონაცემთა ბაზასთან, ხვალ კი შეგვიძლია ვიმუშაოთ PostgreSQL-ით. როგორც უკვე მიხვდით, ჩვენი კლასისთვის არ აქვს მნიშვნელობა რომელ მონაცემთა ბაზაში აგზავნის თავის მონაცემებს, მნიშვნელოვანია, რომ მას შეუძლია ამის გაკეთება.

    $db = ახალი PDO(" mysql:dbname=test;host=localhost", $user, $pass); $db = ახალი PDO(" pgsql:dbname=ტესტი ჰოსტი=localhost", $user, $pass);

    განსხვავება დამოკიდებულების ინექციასა და სერვისის ლოკატორს შორის

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

    ვეცდები გითხრათ PHP-ში რეესტრის ნიმუშის ჩემი დანერგვის შესახებ. რეესტრი არის გლობალური ცვლადების OOP ჩანაცვლება, რომელიც შექმნილია მონაცემების შესანახად და სისტემის მოდულებს შორის გადასატანად. შესაბამისად, დაჯილდოებულია სტანდარტული თვისებებით – ჩაწერა, წაკითხვა, წაშლა. აქ არის ტიპიური განხორციელება.

    ისე, ამ გზით მივიღებთ $key = $value მეთოდების სულელურ ჩანაცვლებას - Registry::set($key, $value) $key - Registry::get($key) unset($key) - წაშალე Registry::remove ($key ) უბრალოდ გაურკვეველი ხდება - რატომ არის ეს დამატებითი კოდი. ასე რომ, მოდით ვასწავლოთ ჩვენს კლასს გააკეთოს ის, რაც გლობალურ ცვლადებს არ შეუძლიათ. დავამატოთ წიწაკა.

    getMessage()); ) Amdy_Registry::unlock("ტესტი"); var_dump(Amdy_Registry::get("ტესტი")); ?>

    შაბლონის ტიპურ ამოცანებს, მე დავამატე ცვლადის დაბლოკვის შესაძლებლობა ცვლილებებისგან, ეს ძალიან მოსახერხებელია დიდ პროექტებზე, თქვენ შემთხვევით არ ჩადებთ არაფერს. მაგალითად, მოსახერხებელია მონაცემთა ბაზებთან მუშაობისთვის
    define('DB_DNS', 'mysql:host=localhost;dbname= ’);
    define('DB_USER', ' ’);
    define('DB_PASSWORD', ' ’);
    define('DB_HANDLE');

    Amdy_Regisrtry::set(DB_HANDLE, ახალი PDO(DB_DNS, DB_USER, DB_PASSWORD));
    Amdy_Registry::lock(DB_HANDLE);

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

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