Имплементација класа и објеката

Када се ради у такозваној објектно-оријентисаној парадигми програмирања, користи се објектна метафора за управљање организацијом рачунарских програма. Већина логике о начину представљања и обраде података изражена је унутар декларисаних класа. У овом одељку је приказано да се класе и објекти могу и сами представити искључиво помоћу функција и речника. Сврха имплементације објектног система на овај начин је да илуструје да употреба објектне метафоре не захтева посебан програмски језик. Програми могу бити објектно-оријентисани, чак и у програмским језицима који немају уграђени објектни систем.

Да би се имплементирали објекти, биће напуштен запис, односно нотација преко тачке (која захтева уграђену језичку подршку), али ће бити створени диспечерски речници који се понашају приближно на исти начин као и елементи уграђеног објектног система. Већ је виђено како се имплементира понашање прослеђивања порука кроз деспечерске речнике. Да би се у потпуности имплементирао објектни систем, шаљу се поруке између инстанци, класа и основних класа, а све су заправо ништа друго до речници који садрже атрибуте.

Неће бити имплементиран целокупан Пајтонов објектни систем, који укључује и одлике које нису обрађиване у досадашњем излагању (нпр. мета-класе и статичке методе). Уместо тога, фокус ће бити стављен на кориснички дефинисане класе без вишеструког наслеђивања и без интроспективног понашања (попут враћања класе неке инстанце). Намера ове показне имлементације није да прати тачну спецификацију Пајтоновог система типова. Уместо тога, пројектована је само за имплементацију основних функционалности које омогућавају објектну метафору.

Инстанце

Почнимо инстанцама. Инстанца има именоване атрибуте, као што је стање на рачуну, који се може поставити и очитати, односно вратити то јест преузети. Инстанце се имплементирају помоћу диспечерског речника који одговара на поруке „преузми” и „постави” које се односе на вредности атрибута. Сами атрибути се чувају у локалном речнику под називом атрибути.

Као што је приказано нешто раније у овом поглављу, сами речници су апстрактни типови података. Речници су били имплементирани помоћу низова, низови помоћу парова, а парови помоћу функција. Док се објектни систем имплементира преко речника, треба имати на уму да би се и сами објекти могли подједнако добро имплементирати користећи се искључиво функцијама.

Имплементација инстанце се започиње претпоставком да постоји имплементација класе која може претражити било која имена која нису део инстанце. Класа се прослеђује функцији направиИнстанцу као клс параметар.

>>> def направиИнстанцу(клс):
...     """Враћа нови објекат инстанце која је у ствари диспечерски речник."""
...     def преузмиВредност(име):
...         if име in атрибути:
...             return атрибути[име]
...         else:
...             вредност = клс['преузми'](име)
...             return повежиМетоду(вредност, инстанца)
...     def поставиВредност(име, вредност):
...         атрибути[име] = вредност
...     атрибути = {}
...     инстанца = {'преузми': преузмиВредност, 'постави': поставиВредност}
...     return инстанца

У горњој функцији инстанца је диспечерски речник који одговара на поруке преузми и постави. Порука постави одговара додељивању вредности атрибута у Пајтоновом објектном систему: сви додељени атрибути чувају се директно у локалном речнику атрибута објекта. У преузми, ако се име не појављује у локалном речнику атрибута, онда се тражи у класи. Уколико је вредност коју враћа клс функција, она мора бити везана за ову инстанцу.

Вредности везане методе

Функција преузмиВредност у направиИнстанцу проналази именовани атрибут у својој класи помоћу преузми, а затим позива повежиМетоду. Везивање методе односи се само на вредности функције и ствара вредност везане методе на основу вредности функције уметањем инстанце као првог аргумента:

>>> def повежиМетоду(вредност, инстанца):
...     """Враћа везану методу ако је вредност могуће позвати, а иначе вредност."""
...     if callable(вредност):
...         def метода(*args):
...             return вредност(инстанца, *args)
...         return метода
...     else:
...         return вредност

Када је метода позвана, први параметар self ће овом дефиницијом бити везан на вредност то јест објекат инстанце.

Класе

Класе су такође објекти, како у Пајтоновом уграђеном објектном систему, тако и у систему који се овде имплементира. Због једноставности каже се да саме класе немају класу. (У Пајтону, класе имају класе; готово све класе деле исту класу, која се назива type). Класа може одговорити на поруке презуми и постави, као и на поруку нова:

>>> def направиКласу(атрибути, основнаКласа=None):
...     """Враћа нову класу, која је у ствари диспечерски речник."""
...     def преузмиВредност(име):
...         if име in атрибути:
...             return атрибути[име]
...         elif основнаКласа is not None:
...             return основнаКласа['преузми'](име)
...     def поставиВредност(име, вредност):
...         атрибути[име] = вредност
...     def нова(*args):
...         return иницијализујИнстанцу(клс, *args)
...     клс = {'преузми': преузмиВредност, 'постави': поставиВредност, 'нова': нова}
...     return клс

За разлику од инстанце, функција преузми за класе не поставља упит својој класи када атрибут није пронађен, већ поставља упит својој основној класи. Саме класе не захтевају везивање метода.

Иницијализација

Функција нова у направиКласу позива иницијализујИнстанцу која најпре прави нову инстанцу, а затим позива методу под називом __init__.

>>> def иницијализујИнстанцу(клс, *args):
...     """Враћа нови објекат са клс типом који је иницијализован са args."""
...     инстанца = направиИнстанцу(клс)
...     init = клс['преузми']('__init__')
...     if init:
...         init(инстанца, *args)
...     return инстанца

Ова последња функција довршава објектни систем. Сада имамо инстанце, над којима постави ради локално, али које се за преузми обраћају својим класама. Након што инстанца потражи неко име у својој класи, она се везује за функционалне вредности како би створила методе. Коначно, класе могу преко нова да праве нове инстанце и позивају/примењују њихову конструкторску функцију __init__ одмах након стварања инстанце.

У овом објектном систему једина функција коју би корисник требало да позове је направиКласу. Све остале функционалности су омогућене путем преношења порука. Слично томе, Пајтонов објектни систем позива се преко class наредбе, а све остале његове функционалности су омогућене кроз тачкасте изразе и позиве класама.

Коришћење имплементираних објеката

Вратимо се сада употреби банковног рачуна као примера из претходног одељка. Користећи имплементиран објектни систем, биће створена класа Рачун, поткласа ТекућиРачун и инстанца сваке од њих.

Класа Рачун се прави помоћу функције направиКласуРачун која има структуру сличну оној коју има и наредба класе у Пајтону, али се завршава позивом на направиКласу.

>>> def направиКласуРачун():
...     """Враћа класу Рачун која поседује методе депонуј и подигни."""
...     камата = 0.02
...     def __init__(self, власникРачуна):
...         self['постави']('власник', власникРачуна)
...         self['постави']('стање', 0)
...     def депонуј(self, износ):
...         """Увећај стање на рачуну за износ и врати ново стање на рачуну."""
...         new_balance = self['преузми']('стање') + износ
...         self['постави']('стање', new_balance)
...         return self['преузми']('стање')
...     def подигни(self, износ):
...         """Умањи стање на рачуну за износ и врати ново стање на рачуну."""
...         стање = self['преузми']('стање')
...         if износ > стање:
...             return 'Недовољно средстава на рачуну.'
...         self['постави']('стање', стање - износ)
...         return self['преузми']('стање')
...     return направиКласу(locals())

Завршни позив функцији locals враћа речник са текстуалним кључевима који садржи везе име-вредност у тренутном локалном оквиру.

Класа Рачун најзад се може инстанцирати преко доделе.

>>> Рачун = направиКласуРачун()

Затим се путем поруке нова ствара инстанца рачуна, која захтева и неко име које иде уз новостворени рачун.

>>> владинРачун = Рачун['нова']('Влада')

Након тога, поруке преузми прослеђене инстанци владинРачун враћају њена својства и методе. Рецуним могу се позвати методе за ажурирање стања на рачуну.

>>> владинРачун['преузми']('власник')
'Влада'
>>> владинРачун['преузми']('камата')
0.02
>>> владинРачун['преузми']('депонуј')(20)
20
>>> владинРачун['преузми']('подигни')(5)
15

Као и код Пајтоновог објектног система, постављањем атрибута инстанце не мења се одговарајући атрибут његове класе.

>>> владинРачун['постави']('камата', 0.04)
>>> Рачун['преузми']('камата')
0.02

Наслеђивање

Може се направити поткласа ТекућиРачун преписивањем једног подскупа атрибута класе. У овом случају мења се метода подигни како би се наметнула провизија приликом подизања новца и смањује се каматна стопа.

>>> def направиКласуТекућиРачун():
...     """Враћа класу ТекућиРачун, која намеће провизију 1 приликом подизања новца."""
...     камата = 0.01
...     провизија = 1
...     def подигни(self, износ):
...         накнада = self['преузми']('провизија')
...         return Рачун['преузми']('подигни')(self, износ + накнада)
...     return направиКласу(locals(), Рачун)

У овој имплементацији, функција подигни основне класе Рачун позива се из функције подигни њене поткласе као што се то и иначе ради у Пајтоновом уграђеном објектном систему. И сама поткласа и њене инстанце могу се стварати као и раније.

>>> ТекућиРачун = направиКласуТекућиРачун()
>>> баруновРачун = ТекућиРачун['нова']('Барун')

Депозити се понашају идентично, као и конструкторска функција, односно, подизања новца се одвијају уз наметнуту провизију од 1 динар из специјализоване и прилагођене методе подигни, а камата има нову, нешто нижу вредност у поткласи ТекућиРачун.

>>> баруновРачун['преузми']('камата')
0.01
>>> баруновРачун['преузми']('депонуј')(20)
20
>>> баруновРачун['преузми']('подигни')(5)
14

Објектни систем изграђен на речницима прилично је сличан по имплементацији уграђеном Пајтоновом објектном систему. У Пајтону, инстанца било које кориснички дефинисане класе има посебан атрибут __dict__ који у речнику чува локалне атрибуте инстанце за тај објекат, врло слично као и речник атрибути. Пајтон се ипак разликује због тога што он ипак издваја одређене посебне методе које комуницирају са уграђеним функцијама како би се осигурало да се те функције правилно понашају за аргументе многих различитих типова. Функције које оперишу над различитим типовима предмет су следећег одељка.