Кіріспе

Бағдарламалық жасақтама үлгісі

Келуші үлгісі – алгоритмді объектілер құрылымынан бөлетін бағдарламалық жасақтама үлгісі. Осы бөлінудің арқасында жаңа операцияларды объектілердің қолданыстағы құрылымын өзгертпей-ақ қосуға болады. Бұл объектіге бағытталған бағдарламалау және бағдарламалық жасақтауда ашық/жабық принципін сақтаудың бір жолы. Құрылымында, келуші кластар отбасына жаңа виртуалды функцияларды кластарды өзгертпей қосуға мүмкіндік береді. Оның орнына, виртуалды функцияның барлық қажетті мамандануларын іске асыратын келуші класс жасалады. Келуші объектінің дереккөз нұсқасын кіріс ретінде қабылдайды және қос жіберу арқылы мақсатына жетеді. Суммалық түрлері мен үлгілерді сәйкестендіру мүмкіндігі бар бағдарламалау тілдері келуші үлгісінің көптеген пайдасын жояды, себебі келуші класс объектінің түріне оңай түрлене алады және жаңа объекті түрі анықталған жағдайда компилятор қатесін тудырады.

Қолдану

2D компьютерлік жобалау (CAD) жүйесінің құрылымын қарастырайық. Оның негізгі түрінде шеңбер, сызық және доға сияқты негізгі геометриялық пішіндерді бейнелейтін бірнеше типтер бар. Объектілер қабаттарға бөлінген, ал типтер иерархиясының жоғарғы жағында сурет орналасқан, ол жай ғана қабаттар тізімі және бірнеше қосымша қасиеттерден тұрады. Бұл типтік иерархиядағы негізгі операция – суретті жүйенің түпкілікті файл пішіміне сақтау. Көріп тұрғанымыздай, иерархиядағы барлық типтерге жергілікті сақтау әдістерін қосу қанағаттанарлық көрінеді. Бірақ суреттерді басқа файл пішімдеріне сақтау да қажет. Көптеген файл пішімдеріне сақтау үшін әдістерді үстемелеп қосу, бастапқыдағы таза геометриялық дерек құрылымын тез арада бұзады. Мұны шешудің қарапайым жолы – әр файл пішімі үшін жеке функцияларды жасау. Мұндай сақтау функциясы суретті кіріс ретінде қабылдап, оны қарап шығып, нақты файл пішіміне түрлендіреді. Осылайша, әр формат қосылған сайын функцияларда қайталау жинақталады. Мысалы, растрлық форматта шеңбер пішінін сақтау үшін қолданылатын код, растрлық форматтың нақты түріне қарамастан, өте ұқсас болады және басқа бастапқы пішіндерден ерекшеленеді. Сызықтар мен көпбұрыштар сияқты басқа бастапқы пішіндерге де осыған ұқсас жағдай тән. Нәтижесінде, код объектілерді аралап өтетін үлкен циклға және цикл ішінде объектінің түрін анықтайтын үлкен шешім ағашына айналады. Бұл тәсілдің тағы бір кемшілігі – пішіннің бір немесе бірнеше сақтаушыларда қалып қоюы немесе жаңа бастапқы пішін енгізілгенде, сақтау процедурасы тек бір файл түрі үшін ғана жасалуы мүмкін, ал қалғандары үшін емес, бұл кодты кеңейту және күтіп ұстау қиындықтарына әкеледі. Файлдың нұсқалары көбейген сайын оны күтіп ұстау одан да қиындай түседі. Оның орнына, келуші үлгісін қолдануға болады. Ол логикалық операцияны (мысалы, save(image tree)) бүкіл иерархия бойынша бір сыныпқа (мысалы, Saver) енгізеді, ол ағашты аралап өтуге арналған жалпы әдістерді іске асырады және форматқа қатысты мінез-құлықтарды іске асыру үшін виртуалды көмекші әдістерді (мысалы, save circle, save square және т.б.) сипаттайды. CAD мысалында, мұндай форматқа қатысты мінез-құлықтарды Visitor (мысалы, SaverPNG) класының кіші класы іске асырады. Осылайша, типті тексерулер мен аралап өту қадамдарының барлық қайталануы жойылады. Сонымен қатар, енді компилятор пішіннің қалып қойған жағдайында шағымданады, өйткені ол жалпы базалық аралап өту/сақтау функциясынан күтіледі.

Итерациялық циклдер

Келуші үлгісі Iterator үлгісі сияқты контейнерлік дерек құрылымдары бойынша итерация үшін қолданылуы мүмкін, бірақ мүмкіндіктері шектеулі. Мысалы, каталогтар құрылымы бойынша итерацияны дәстүрлі циклдың орнына функция класы арқылы жүзеге асыруға болады. Бұл, әр элемент үшін келуші функционалдығын іске асыру арқылы каталогтардың мазмұнынан түрлі пайдалы ақпаратты алуға және итерациялық кодты қайта пайдалануға мүмкіндік береді. Бұл үлгі Smalltalk жүйелерінде кеңінен қолданылады және C++ тілінде де кездеседі. Алайда, осы тәсілдің бір кемшілігі – циклдан оңай шығу мүмкін емес, сондай-ақ бір уақытта итерация жасау қиын (яғни, бір айнымалымен екі контейнерді параллель түрде қарау). Соңғысы үшін келушіге осы мүмкіндіктерді қолдау үшін қосымша функционалды жазу қажет болады.

Егжей-тегжейлер

Келуші үлгісі бірыңғай жіберуді қолдайтын бағдарламалау тілін қажет етеді, себебі көптеген объектіге бағытталған тілдер (мысалы, C++, Java, Smalltalk, Objective C, Swift, JavaScript, Python және C#) осы мүмкіндікке ие. Осы шарттар орындалғанда, екі объектіні қарастырайық, олардың әрқайсысы белгілі бір сыныпқа жатады; біреуі элемент, екіншісі – келуші деп аталады. Келуші, элементті аргумент ретінде қабылдайтын visit әдісін жариялайды, әрбір элемент класы үшін. Нақты келушілер келуші класынан туындайды және осы visit әдістерін іске асырады, олардың әрқайсысы объектілер құрылымымен жұмыс істейтін алгоритмнің бір бөлігін іске асырады. Алгоритмнің күйі нақты келуші класымен жергілікті түрде сақталады. Элемент келушіні қабылдау үшін accept әдісін жариялайды, келушіні аргумент ретінде қабылдайды. Элемент класынан туындаған нақты элементтер accept әдісін іске асырады. Ең қарапайым жағдайда, бұл келушінің visit әдісіне шақыру ғана болады. Бала объектілер тізімін сақтайтын композиттік элементтер әдетте олардың үстінен итерация жасайды, әр баланың accept әдісін шақырады. Клиент объектілер құрылымын тікелей немесе жанама түрде жасайды және нақты келушілерді құрады. Visitor үлгісін пайдалана отырып іске асырылатын операцияны орындау қажет болғанда, ол жоғарғы деңгейдегі элементтің accept әдісін шақырады. accept әдісі шақырылғанда, оның іске асырылуы элементтің динамикалық түрі мен келушінің статикалық түріне сүйене отырып таңдалады. Сәйкес visit әдісі шақырылғанда, оның іске асырылуы келушінің динамикалық түрі мен элементтің статикалық түріне сүйене отырып таңдалады, accept әдісінің іске асырылуынан белгілі болғандай, ол элементтің динамикалық түрімен бірдей. (Егер келуші берілген элемент түріне сәйкес аргументті өңдей алмаса, компилятор қатені анықтайды.) Осылайша, visit әдісін іске асыру элементтің және келушінің динамикалық түрлеріне сүйене отырып таңдалады. Бұл тиімді түрде қос жіберуді іске асырады. Объектілік жүйелері тек бір жіберуді ғана емес, бірнеше жіберуді де қолдайтын тілдер үшін, мысалы Common Lisp немесе C# (Dynamic Language Runtime (DLR) арқылы), келуші үлгісін іске асыру едәуір жеңілдетіледі (Dynamic Visitor деп те аталады), барлық қарастырылатын жағдайларды қамту үшін қарапайым функцияны жүктеуге рұқсат беру арқылы. Динамикалық келуші, егер ол тек ашық деректермен жұмыс істесе, ашық/жабық қағидасына (қолданыстағы құрылымдарды өзгертпейді) және бірыңғай жауапкершілік қағидасына (келуші үлгісін жеке компонентте іске асырады) сәйкес келеді. Осылайша, элементтердің графигін өтуге арналған бір алгоритм жазылуы мүмкін, ал элементтермен және келушілермен өзара әрекеттесу арқылы осы өту кезінде әртүрлі операцияларды орындауға болады, олардың динамикалық түрлеріне сәйкес әртүрлі келушілерді ұсыну арқылы.

Java үлгісі

Келесі мысал Java тілінде жазылған және түйіндер ағашының мазмұнын (осы жағдайда автомобильдің құрамдас бөліктерін сипаттайды) қалай басып шығаруға болатынын көрсетеді. Дөңгелек, Қозғалтқыш, Корпус және Автомобиль сияқты әрбір түйіннің түменші класы үшін жеке басып шығару әдістерін жасаудың орнына, бір ғана қонақ класы (CarElementPrintVisitor) қажетті басып шығару амалын орындайды. Түрлі түйіннің түменші кластары дұрыс басып шығару үшін сәл әртүрлі амалдарды қажет ететіндіктен, CarElementPrintVisitor өзінің visit әдісіне берілген аргументтің класына сәйкес амалдарды жібереді. CarElementDoVisitor, басқа файл пішіміне сақтау операциясы сияқты, солайша жұмыс істейді.

Python мысалы

Python классикалық мағынада әдіс жүктемесін қолдамайды (берілген параметрлердің типіне қарай полиморфтық мінез-құлық), сондықтан әртүрлі модель түрлері үшін "келу" әдістері әртүрлі атауларға ие болуы тиіс.

Шығысы

Сол жақ алдыңғы дөңгелекке бару. Оң жақ алдыңғы дөңгелекке бару. Сол жақ артқы дөңгелекке бару. Оң жақ артқы дөңгелекке бару. Көлік корпусына бару. Қозғалтқышқа бару. Көлікке бару. Сол жақ алдыңғы дөңгелегімді тепкілеу. Оң жақ алдыңғы дөңгелегімді тепкілеу. Сол жақ артқы дөңгелегімді тепкілеу. Оң жақ артқы дөңгелегімді тепкілеу. Денеімді жылжыту. Қозғалтқышты іске қосу. Көлігімді іске қосу.