Објектно-оријентисано програмирање¶
Објектно-оријентисано програмирање (ООП) је метода организације програма која обједињује многе идеје представљене у овом поглављу. Као и функције у апстракцији података, класе стварају апстракцијске баријере између употребе и имплементације података. Попут диспечерских речника, објекти одговарају на захтеве за одређеним радњама. Као променљиве структуре података, објекти поседују локално стање које није директно доступно из глобалног окружења. Пајтонов објектни систем пружа погодну синтаксу за промовисање употребе ових техника за организацију програма. Велики део ове синтаксе је заједнички и за многе друге објектно-оријентисане програмске језике.
Објектни систем нуди више од пуке погодности. Он омогућава нову метафору пројектовања програма у којима неколико независних агената комуницира унутар рачунара. Сваки објекат спаја локално стање и понашање на начин који апстракује њихове сложености. Објекти међусобно комуницирају, а корисни резултати израчунавају се као последица њихове интеракције. Објекти не само да прослеђују поруке, већ и деле понашање међу осталим објектима истог типа и наслеђују карактеристике и одлике сродних типова.
Парадигма објектно-оријентисаног програмирања има свој речник који пружа подршку објектној метафори. Већ је приказано да су објекти вредности податка које поседују методе и атрибуте, којима се приступа нотацијом преко тачке. Сваки објекат такође има и свој тип, који се назива класа. Да би се створили нови типови података, имплементирају се нове класе.
Објекти и класе¶
Класа служи као шаблон за све објекте чији је тип баш та класа. Сваки објекат је инстанца неке одређене класе. Сви објекти који су до сада коришћени имају уграђене класе, али се такође могу стварати и нове кориснички дефинисане класе. Дефиниција класе наводи атрибуте и методе који су дељени и заједнички за све објекте те класе. Класе и одговарајуће наредбе биће представљене кроз још један осврт на пример банковног рачуна.
Приликом увођења локалног стања банковни рачуни су природно моделирани као променљиве вредности које имају своје стање
. Објекат банковног рачуна треба да има методу подигни
која ажурира стање на рачуну и враћа захтевани износ, ако је доступан. Да би се довршила и заокружила апстракција: банковни рачун би требало да може да врати своје тренутно стање
, да врати име власник
-а рачуна, као и да буде у могућности да се депонуј
-е одређени износ.
Класа Рачун
омогућава стварање више различитих инстанци банковних рачуна. Чин стварања нове инстанце објекта уобичајено се назива инстанцирање класе. Синтакса у Пајтону за инстанцирање класе идентична је синтакси за позивање функције. У овом конкретном случају, позива се Рачун
са аргументом 'Барун'
, који представља име власника рачуна.
>>> р = Рачун('Барун')
Атрибут објекта је пар име-вредност придружен том објекту, којем је могуће приступити нотацијом преко тачке. Атрибути који су посебни за одређени објекат, за разлику од свих објеката класе, називају се атрибути инстанце. Сваки Рачун
има своје стање и име власника рачуна који су примери атрибута инстанце. У широј програмерској заједници, атрибути инстанце такође се често називају и поља, својства или пак променљиве инстанце.
>>> р.депонуј(15)
15
Каже се да се методе позивају на одређеном објекту или над одређеним објектом. Као резултат позива методе подигни
, подизање се одобрава и подигнути износ се скида са стања рачуна или се захтев одбија и метода враћа поруку о грешци.
>>> р.подигни(10) # Метода подигни враћа стање након подизања
5
>>> р.стање # Атрибут стање је промењен
5
>>> р.подигни(10)
'Недовољно средстава на рачуну.'
Као што је илустровано горе, понашање методе може зависити од промене атрибута објекта. Два идентична позива методи подигни
са истим аргументом могу дати различите резултате.
Дефинисање класа¶
Кориснички дефинисане класе праве се помоћу наредбе class
, која се састоји из једне клаузуле. Наредба класе дефинише име класе, а затим укључује и пакет израза за дефинисање атрибута класе:
class <име>:
<пакет>
Када се изврши class
наредба, нова класа се ствара и везује на <име>
у првом оквиру тренутног окружења. Затим се извршава пакет наредби у телу класе. Било која имена повезана унутар <пакет>
одељка у наредби class
, било кроз def
наредбу преко наредбе доделе, стварају или мењају атрибуте класе.
Класе су обично организоване у сврху манипулисања атрибутима инстанце, а то су парови име-вредност придружени свакој инстанци дате класе. Класа одређује атрибуте инстанце својих објеката дефинишући методу за иницијализацију нових објеката. На пример, део иницијализације објекта класе Рачун
је додељивање почетног стања 0.
Одељак <пакет>
у оквиру class
наредбе садржи def
наредбе које дефинишу нове методе за објекте те класе. Метода која иницијализује објекте има посебно име у Пајтону, __init__
(две подвлаке са сваке стране речи „init”), и назива се конструктор те класе.
>>> class Рачун:
... def __init__(self, власникРачуна):
... self.стање = 0
... self.власник = власникРачуна
Метода __init__
за Рачун
има два формална параметра. Први, self
, везан је на новостворени објекат Рачун
. Други параметар, власникРачуна
, везан је за аргумент прослеђен у класи приликом позива за инстанцирање исте.
Конструктор повезује назив атрибута инстанце стање
на 0. Такође, повезује назив атрибута власник
на вредност под именом власникРачуна
. Формални параметар власникРачуна
је локално име у __init__
методи. С друге стране, име власник
које је повезано последњом наредбом доделе ће и даље наставити да постоји зато што се чува као атрибут self
-а користећи се нотацијом преко тачке.
Након што је класа Рачун
дефинисана, може се инстанцирати.
>>> р = Рачун('Барун')
Овај „позив” класи Рачун
ствара нови објекат који је инстанца Рачун
-а, а затим позива конструкторску функцију __init__
са два аргумента: новоствореним објектом и ниском 'Барун'
. По договору, користи се име параметра self
за први аргумент конструктора зато што је везан на објекат који се инстанцира. Ова конвенција је практично усвојена у готово свим Пајтон програмским кодовима.
Сада се може приступити пољима објекта стање
и власник
помоћу нотације преко тачке.
>>> р.стање
0
>>> р.власник
'Барун'
Идентитет¶
Свака нова инстанца рачуна има свој атрибут стање
, чија је вредност независна од других објеката исте класе.
>>> с = Рачун('Влада')
>>> с.стање = 200
>>> [рач.стање for рач in (р, с)]
[0, 200]
Да би се наметнуло ово раздвајање, сваки објекат који је инстанца кориснички дефинисане класе има јединствени идентитет. Идентитет објекта се упоређује помоћу is
и њему комплементарног is not
оператора.
>>> р is р
True
>>> р is not с
True
Упркос томе што су направљени преко истоветних позива, објекти повезани на имена р
и с
нису исти. Као и обично, везивање објекта на ново име користећи се доделом не ствара нови објекат.
>>> т = р
>>> т is р
True
Нови објекти кориснички дефинисаних класа се стварају само када је класа (као што је Рачун
) инстанцирана синтаксом позивног израза.
Методе¶
Методе објекта су такође дефинисане def
наредбом у пакету унутар class
наредбе. У коду испод су и депонуј
и подигни
дефинисане као методе на објектима класе Рачун
.
>>> class Рачун:
... def __init__(self, власникРачуна):
... self.стање = 0
... self.власник = власникРачуна
... def депонуј(self, износ):
... self.стање = self.стање + износ
... return self.стање
... def подигни(self, износ):
... if износ > self.стање:
... return 'Недовољно средстава на рачуну.'
... self.стање = self.стање - износ
... return self.стање
Иако се дефиниције метода не разликују од дефиниција функција по начину на који су декларисане, дефиниције метода имају другачији ефекат када се извршавају. Функцијска вредност која се ствара def
наредбом унутар class
наредбе се везује на декларисано име, али је везана локално унутар класе као атрибут. Та вредност се позива као метода користећи се нотацијом преко тачке из инстанце класе.
Свака дефиниција методе изнова укључује посебан први параметар self
, који је повезан за објекат над којим је метода позвана. На пример, може се рећи да је метода депонуј
позвана над тачно одређеним Рачун
објектом и прослеђује једну вредност аргумента: износ који треба депоновати. Сам објекат је везан на self
, док је аргумент везан за износ
. Све позване методе имају приступ објекту преко параметра self
па тако могу приступити и манипулисати стањем објекта.
Да би се ове методе позвале, поново се користи нотација преко тачке, као што је илустровано у наставку.
>>> владинРачун = Рачун('Влада')
>>> владинРачун.депонуј(100)
100
>>> владинРачун.подигни(90)
10
>>> владинРачун.подигни(90)
'Недовољно средстава на рачуну.'
>>> владинРачун.власник
'Влада'
Када се метода позове путем нотације преко тачке, сам објекат (повезан на владинРачун
у овом конкретном случају) игра двоструку улогу. Прво, одређује шта значи име подигни
, тачније да подигни
није име из тренутног окружења, већ је то име које је локално за класу Рачун
. Друго, везан је за први параметар self
када је метода подигни
позвана.
Прослеђивање порука и тачкасти изрази¶
Методе, које су дефинисане у класама, као и атрибути инстанце, који се обично додељују у конструкторима, основни су елементи објектно-оријентисаног програмирања. Ова два концепта имитирају већи део понашања диспечерског речника у имплементацији преношења порука о вредности података. Објекти примају поруке у нотацији преко тачке, али уместо порука које су заправо произвољне ниске у виду кључева речника, у овом случају поруке су имена, односно називи локални за класу. Објекти такође имају именоване вредности локалних стања (атрибуте инстанце), али том стању се може приступити и њиме манипулисати помоћу нотације преко тачке, без потребе за коришћењем nonlocal
наредби у самој имплементацији.
Главна идеја у преношењу порука била је да вредности података треба да се понашају тако што ће одговарати на поруке које су релевантне за апстрактни тип који представљају. Такозвани тачкасти запис или тачкаста нотација је синтаксна одлика Пајтона која формализује метафору преношења порука. Предност употребе програмског језика са уграђеним објектним системом је у томе што прослеђивање порука може неометано комуницирати са другим језичким елементима, као што су наредбе доделе. Не захтевају се различите поруке да би се „вратила” или „поставила” вредност повезана са именом локалног атрибута; синтакса језика омогућава директно коришћење имена поруке.
Тачкасти изрази¶
Исечак кода владинРачун.депонуј
назива се тачкасти израз. Тачкасти израз састоји се од израза, тачке и имена:
<израз> . <име>
Иако <израз>
може бити било који ваљани израз у Пајтону, <име>
мора бити једноставно име (не и израз који се вреднује у име). Тачкасти израз се интерпретира у вредност атрибута под задатим називом <име>
, за објекат који је вредност у коју се вреднује <израз>
.
Уграђена функција getattr
такође враћа атрибут за објекат по имену. То је ништа друго до функцијски еквивалент нотације преко тачке. Користећи getattr
, могу се потражити атрибути помоћу ниске, као што је то чињено са диспечерским речницима.
>>> getattr(владинРачун, 'стање')
10
Помоћу уграђене функције hasattr
може се такође испитати да ли објекат поседује атрибут под одређеним именом.
>>> hasattr(владинРачун, 'депонуј')
True
Атрибути објекта укључују све његове атрибуте инстанце, заједно са свим атрибутима (укључујући и методе) дефинисане у његовој класи. Методе су атрибути класе који захтевају посебно руковање.
Методе и функције¶
Када се метода позове над објектом, тај објекат се имплицитно прослеђује као први аргумент методе. Другим речима, објекат који је вредност <израз>
-а с леве стране тачке се аутоматски преноси као први аргумент методи именованој с десне стране тачкастог израза. Као резултат, објекат се везује на self
параметар.
Да би се постигло аутоматско самоувезивање, Пајтон разликује функције, које су писане од самог почетка овог уџбеника, и везане методе, које повезују функцију и објекат над којим ће се та метода позивати. Вредност везане методе је већ придружена њеном првом аргументу, односно инстанци над којом је позвана, а која ће бити названа self
када метода буде позвана.
У интерактивном Пајтоновом интерпретатору могу се уочити разлике приликом позива уграђене функције type
са вредностима тачкастих израза као аргументима. Као атрибут класе, метода је само функција, али као атрибут инстанце, она је заправо везана метода.
>>> type(Рачун.депонуј)
<class 'function'>
>>> type(владинРачун.депонуј)
<class 'method'>
Ова два резултата разликују се само у чињеници да је први стандардна двоаргументна функција са параметрима self
и износ
. Друга је метода са једним аргументом, где ће када се метода позове, назив self
бити аутоматски повезан на објекат под именом владинРачун
, док ће параметар износ
бити везан на аргумент прослеђен методи. Обе ове вредности, било вредности функције или вредности везане методе, су придружене истом телу функције депонуј
.
Постоје два начина да се депонуј
позове: као функција и као везана метода. У првом случају, аргумент за формални параметар self
мора се задати експлицитно. У другом случају, параметар self
је везан аутоматски.
>>> Рачун.депонуј(владинРачун, 1001) # Функција депонуј прима два аргумента
1011
>>> владинРачун.депонуј(1000) # Метода депонуј прима један аргумент
2011
Функција getattr
понаша се исто као и запис преко тачке. Наиме, уколико је њен први аргумент објекат, а име је метода дефинисана у класи, тада getattr
враћа вредност везане методе. С друге стране, ако је први аргумент класа, тада getattr
враћа вредност атрибута директно, што је обична функција.
Правила именовања¶
Имена класа се уобичајено пишу користећи такозвану CapitalizedWords
или CapWords конвенцију (која се још назива и CamelCase јер велика слова у средини изгледају налик на грбе). Иако то није случај у овом уџбенику, имена метода треба да следе стандардно правило именовања функција помоћу малих слова раздвојених подвлакама, односно доњим цртама.
У неким случајевима постоје променљиве и методе инстанце које су повезане са одржавањем и доследношћу објекта за које није предвиђено да их корисници виде или користе. Они нису део апстракције коју дефинише класа, већ део имплементације. Пајтонова конвенција налаже да ако име атрибута започиње доњом цртом, то јест подвлаком, њему треба приступити само у методама саме класе, а не и од стране корисника класе.
Атрибути класе¶
Неке вредности атрибута деле се кроз све објекте дате класе. Такви атрибути су повезани са самом класом, а не са било којом појединачном инстанцом те класе. Примера ради, рецимо да банка даје камате на износ стања на рачуну по фиксној каматној стопи. Та каматна стопа се може променити, али је то једна вредност која је заједничка за све рачуне.
Атрибути класе стварају се наредбама доделе унутар тела класе, али изван дефиниције било које методе. У широј програмерској заједници, атрибути класе називају се још и променљиве класе или статичке променљиве. Следећи пример класе прави атрибут класе за Рачун
под именом камата
.
>>> class Рачун:
... камата = 0.03 # Атрибут класе
... def __init__(self, власникРачуна):
... self.стање = 0
... self.власник = власникРачуна
... # Остале методе би биле дефинисане у наставку
Овом атрибуту се и даље може приступити из било које инстанце класе.
>>> владинРачун = Рачун('Влада')
>>> баруновРачун = Рачун('Барун')
>>> владинРачун.камата
0.03
>>> баруновРачун.камата
0.03
Међутим, само један израз доделе вредности атрибуту класе мења вредност тог атрибута за све инстанце дате класе.
>>> Рачун.камата = 0.02
>>> владинРачун.камата
0.02
>>> баруновРачун.камата
0.02
Имена атрибута¶
У објектни систем унето је довољно сложености да се мора одредити како се разрешавају имена одређених атрибута. Уосталом, лако се може десити да постоје атрибут класе и атрибут инстанце са истим именом.
Као што је приказано, тачкасти изрази се састоје од израза, тачке и имена:
<израз> . <име>
Да би се вредновао тачкасти израз:
Вреднује се
<израз>
с леве стране тачке, што даје објекат тачкастог израза.<име>
се тражи међу атрибутима инстанце тог објекта и, ако постоји атрибут под тим именом, враћа се његова вредност.Уколико се
<име>
не појави међу атрибутима инстанце, тада се<име>
тражи у класи и ако се пронађе даје вредност атрибута класе.Та вредност се враћа под условом да није функција, у ком случају се уместо ње враћа везана метода.
У овом поступку вредновања, атрибути инстанце налазе се пре атрибута класе, баш као што локална имена имају приоритет над глобалним унутар неког окружења. Методе дефинисане у класи комбинују се са објектом тачкастог израза како би се створила везана метода током четвртог корака претходног изнетог поступка вредновања. Поступак тражења имена унутар класе има још неке додатне нијансе које ће ускоро испливати, након што буде уведено наслеђивање класа.
Додела атрибутима¶
Све наредбе доделе које садрже тачкасти израз на својој левој страни утичу на атрибуте објекта тог тачкастог израза. Уколико је објекат инстанца, тада додела поставља вредност атрибуту инстанце, а ако је пак објекат класа, тада додела поставља вредност атрибуту класе. Као последица овог правила, додела атрибутима објекта не може утицати на атрибуте његове класе. Примери у наставку илуструју ову разлику.
Уколико се изврши додела вредности именованом атрибуту камата
на инстанци рачуна, ствара се нови атрибут инстанце који има исто име као и већ постојећи атрибут класе.
>>> баруновРачун.камата = 0.04
и та вредност атрибута биће враћена из тачканог израза.
>>> баруновРачун.камата
0.04
Међутим, атрибут класе камата
и даље задржава своју првобитну вредност која се враћа за све остале рачуне.
>>> владинРачун.камата
0.02
Промене атрибута класе камата
утицаће на владинРачун
, али атрибут инстанце баруновРачун
биће неизмењен.
>>> Рачун.камата = 0.05 # промена атрибута класе
>>> владинРачун.камата # мења све инстанце без истоимених атрибута инстанце
0.05
>>> баруновРачун.камата # али постојећи атрибути инстанце остају непромењени
0.04
Наслеђивање¶
Када се ради у оквирима парадигме објектно-оријентисаног програмирања, често се дешава да су различити типови података међусобно повезани. Конкретно, открива се да се сличне класе разликују по неким мањим специфичностима. Две класе могу имати сличне атрибуте, али једна представља посебан случај друге.
На пример, може се пожелети да се имплементира текући рачун, који се мало разликује од обичног рачуна. Текући рачун наплаћује додатан један динар провизије за свако подизање новца са рачуна и има нижу каматну стопу. У наставку ће бити демонстрирано захтевано понашање.
>>> тр = ТекућиРачун('Влада')
>>> тр.камата # Нижа каматна стопа за текуће рачуне
0.01
>>> тр.депонуј(20) # Депозити су исти
20
>>> тр.подигни(5) # подизање умањује стање за додатну провизију
14
Заправо је ТекућиРачун
само посебан и мало специфичнији обичан Рачун
. У терминологији објектно-оријентисаног програмирања, општи рачун ће служити само као основна класа за ТекућиРачун
, док ће ТекућиРачун
бити поткласа Рачун
-а. (Изрази базна класа, надкласа, родитељска класа и суперкласа такође се често користе за основну класу).
Поткласа наслеђује атрибуте своје основне класе, али може и заменити одређене атрибуте, укључујући ту и одређене методе. Приликом наслеђивања наводи се само оно што се разликује између поткласе и основне класе. Све што се у поткласи остави неодређено, аутоматски се претпоставља да се понаша баш као и у основној класи.
Поред тога што је корисно као организационо својство, наслеђивање такође има улогу и у објектној метафори. Наслеђивање треба да представља такозвани је однос међу класама, који је у супротности са такозваним има односом. Наиме, текући рачун је посебна врста рачуна, тако да то што ТекућиРачун
наслеђује Рачун
јесте одговарајућа употреба наслеђивања. С друге стране, банка има низ банковних рачуна којима управља, па ниједан од њих не би требало да наслеђује неког другог. Уместо тога, низ објеката рачуна би природно био изражен као атрибут инстанце објекта банке.
Коришћење наслеђивања¶
Најпре, у наставку је дата потпуна имплементација класе Рачун
, која укључује докниске за класу и њене методе.
>>> class Рачун:
... """Банковни рачун који има ненегативно стање."""
... камата = 0.02
... def __init__(self, власникРачуна):
... self.стање = 0
... self.власник = власникРачуна
... def депонуј(self, износ):
... """Увећај стање на рачуну за износ и врати ново стање рачуна."""
... self.стање = self.стање + износ
... return self.стање
... def подигни(self, износ):
... """Умањи стање на рачуну за износ и врати ново стање рачуна."""
... if износ > self.стање:
... return 'Недовољно средстава на рачуну.'
... self.стање = self.стање - износ
... return self.стање
Потпуна имплементација за ТекућиРачун
приказана је у наставку. Наслеђивање је специфицирано постављањем израза који се вреднује у основну класу унутар заграда одмах након назива класе.
>>> class ТекућиРачун(Рачун):
... """Банковни рачун који наплаћује провизију приликом подизања новца."""
... камата = 0.01
... провизија = 1
... def подигни(self, износ):
... return Рачун.подигни(self, износ + self.провизија)
Овде се уводи атрибут класе провизија
који је специфичан за класу ТекућиРачун
. Атрибуту камата
додељује се нижа вредност. Такође се дефинише нова метода подигни
која ће заменити понашање описано у класи Рачун
. Без додатних наредби и израза у телу класе, сва остала понашања се наслеђују из основне класе Рачун
.
>>> текући = ТекућиРачун('Влада')
>>> текући.депонуј(10)
10
>>> текући.подигни(5)
4
>>> текући.камата
0.01
Израз текући.депонуј
вреднује се у везану методу за депоновање која је дефинисана у класи Рачун
. Када Пајтон разлучи име у тачкастом изразу које није атрибут инстанце, тражи то име унутар класе. Заправо, поступак „тражења” имена у класи покушава да пронађе то име у свакој од основних класа, то јест надкласа у ланцу наслеђивања за ту класу почетног објекта. Овај поступак може се дефинисати рекурзивно. Одређено име се претражује у класи на следећи начин:
Уколико именује атрибут у класи, вратити вредност атрибута.
У супротном, потражити то име у основној класи, ако постоји.
У случају имена депонуј
, Пајтон би прво тражио то име на инстанци, а затим у класи ТекућиРачун
. Најзад, потражио би то име и у класи Рачун
, где је депонуј
и дефинисано. Према усвојеном правилу вредновања за тачкасте изразе, будући да је депонуј
функција која се тражи у класи инстанце текући
, тачкасти израз вреднује се у вредност везане методе. Та метода се позива са аргументом 10, која даље позива методу депонуј
са параметром self
повезаним на објекат текући
и параметром износ
повезаним на 10.
Класа објекта остаје непромењена током читавог овог поступка. Иако је метода депонуј
пронађена у класи Рачун
, депонуј
се позива са self
повезаним на инстанцу класе ТекућиРачун
, а не Рачун
.
Позивање предака¶
Атрибути који су промењени и даље су доступни преко објеката класе. На пример, метода подигни
у класи ТекућиРачун
имплементирана је позивом методе подигни
из класе Рачун
са аргументом који је укључивао и додатак провизија
.
Треба приметити да је позвана self.провизија
, а не еквивалент ТекућиРачун.провизија
. Предност првог позива над другим је та што класа која би потенцијално наслеђивала ТекућиРачун
може променити износ провизије. Уколико је то заиста и случај, требало би да имплементација методе подигни
пронађе ту нову вредност провизије уместо старе вредности.
Сучеља¶
Изузетно је често у објектно-оријентисаним програмима да различите врсте, односно типови објеката имају заједничка имена атрибута. Објектно сучеље је колекција атрибута и услова за те атрибуте. На пример, сви рачуни морају да имају методе депонуј
и подигни
које примају бројевне аргументе, као и атрибут стање
. Класе Рачун
и ТекућиРачун
имплементирају ово сучеље. Наслеђивање посебно промовише и стимулише дељење имена међу класама на овај начин. У неким програмским језицима као што је Јава, имплементације интерфејса, односно сучеља морају бити експлицитно декларисане. У другим програмским језицима као што су Пајтон и Руби, било који објекат са одговарајућим именима имплементира интерфејс то јест сучеље.
Делови програма који користе објекте (насупрот оним деловима где су имплементирани) најробуснији су на будуће промене ако не праве никакве претпоставке о врстама, то јест типовима објекта, већ само о именима њихових атрибута. Односно, они користе апстракцију објекта, уместо да претпостављају било шта о његовој имплементацији.
На пример, рецимо да водимо лутрију и желимо да уплатимо по 500 динара на сваки у низу рачуна. Следећа имплементација не претпоставља ништа о врстама/типовима тих рачуна и стога подједнако добро функционише са било којом врстом/типом објекта која садржи депонуј
методу:
>>> def депонујСвима(добитници, износ=500):
... for рачун in добитници:
... рачун.депонуј(износ)
Функција депонујСвима
горе претпоставља само да сваки рачун задовољава апстракцију објекта рачуна, па ће тако радити са било којим другим класама рачуна које такође имплементирају ово сучеље. Претпостављање тачно одређене класе рачуна крши апстракцијску баријеру објектне апстракције рачун. На пример, следећа имплементација неће нужно радити са неким новим врстама рачуна:
>>> def депонујСвима(добитници, износ=500):
... for рачун in добитници:
... Рачун.депонуј(рачун, износ)
Ова тема биће детаљније обрађена нешто касније у овом поглављу.
Вишеструко наслеђивање¶
Пајтон подржава концепт поткласе која наслеђује атрибуте из више основних класа, језичку особину која се назива вишеструко наслеђивање.
Претпоставимо да имамо ШтедниРачун
који наслеђује Рачун
, али муштеријама наплаћује неку малу накнаду сваки пут када депонују новац.
>>> class ШтедниРачун(Рачун):
... накнада = 2
... def депонуј(self, износ):
... return Рачун.депонуј(self, износ - self.накнада)
Тада, домишљати руководилац долази до генијалне идеје да осмисли такозвани ПромотивниРачун
са најбољим (за банку, наравно) особинама од оба ТекућиРачун
и ШтедниРачун
: провизија за подизање, накнада на депозите и ниска каматна стопа. То је и текући и штедни рачун у једном! „Ако га направимо”, каже руководилац, „и неко се стварно пријави да плаћа све те трошкове, почастићемо га једним динаром.”
>>> class ПромотивниРачун(ТекућиРачун, ШтедниРачун):
... def __init__(self, власникРачуна):
... self.власник = власникРачуна
... self.стање = 1 # "Бесплатан" динар!
Заправо, ова имплементација је завршена и потпуна. И подизање и депоновање новца ће се одвијати уз провизију, односно накнаду, користећи дефиниције функција у класама ТекућиРачун
и ШтедниРачун
, респективно.
>>> најнај = ПромотивниРачун("Влада")
>>> најнај.стање
1
>>> најнај.депонуј(20) # накнада од 2 динара из ШтедниРачун.депонуј
19
>>> најнај.подигни(5) # провизија од 1 динар из ТекућиРачун.подигни
13
Недвосмислене референце исправно су разрешене како се и очекивало:
>>> најнај.накнада
2
>>> најнај.провизија
1
Међутим, шта када је референца двосмислена, као што је референца на методу подигни
која је дефинисана и у Рачун
и у ТекућиРачун
? Доњи дијаграм приказује граф наслеђивања за класу ПромотивниРачун
. Свака стрелица показује од поткласе до основне класе.
За једноставан облик „дијаманта” попут овог, Пајтон разрешава имена слева надесно, а затим нагоре. У овом примеру Пајтон проверава име атрибута у следећим класама, редо(следо)м, све док се не пронађе атрибут са тим именом:
ПромотивниРачун, ТекућиРачун, ШтедниРачун, Рачун, object
Не постоји тачно решење проблема са редоследом наслеђивања, јер постоје случајеви у којима би програмери у поступку наслеђивања волели да дају предност одређеним класама у односу на друге. Међутим, било који програмски језик који подржава вишеструко наслеђивање мора да одабере неки редослед на доследан начин, тако да корисници могу да предвиде понашање својих програма.
Додатна литература¶
Пајтон разлучује и разрешава ово име помоћу рекурзивног алгоритма названог С3-линеаризација суперкласа (енг. C3 superclass linearization) или редослед разрешења метода (енг. Method Resolution Order). Редослед разрешења метода било које класе може се тражити помоћу mro
методе над свим класама.
>>> [класа.__name__ for класа in ПромотивниРачун.mro()]
['ПромотивниРачун', 'ТекућиРачун', 'ШтедниРачун', 'Рачун', 'object']
Прецизан алгоритам проналажења редоследа разрешења метода није тема овог рукописа, али га Пајтонов примарни аутор описује са све референцом на оригинални рад.
Улога објеката¶
Пајтонов систем објеката пројектован је да апстракцију података и прослеђивање порука учини прикладним и флексибилним. Специјализована синтакса класа, метода, наслеђивања и тачкастих израза омогућава формализацију објектне метафоре у програмима што побољшава способност организовања великих и сложених рачунарских програма.
Конкретно, циљ је да објектни систем промовише такозвано раздвајање проблема између различитих аспеката програма. Сваки објекат у програму енкапсулира и управља неким делом програмског стања, а свака наредба класе дефинише функције које имплементирају неки део целокупне програмске логике. Апстракцијске баријере су те које намећу јасне границе између различитих аспеката унутар великих и сложених програма.
Објектно-оријентисано програмирање је посебно погодно за програме који моделирају системе који имају раздвојене, али узајамнодејствујуће делове. На пример, различити корисници комуницирају на друштвеној мрежи, различити ликови међусобно делују једни на друге у видео играма, а различити облици узајамно делују у некој физичкој симулацији. При представљању таквих система, објекти у програму често се природно пресликавају на објекте у систему који се моделира, а класе представљају њихове типове и односе.
С друге стране, класе некада не пружају најбољи механизам и начин за имплементацију одређених апстракција. Функционалне апстракције пружају природнију метафору за представљање односа између улаза и излаза. Не треба се осећати примораним да се сваки делић програмске логике уклопи унутар класа, посебно када је дефинисање независних функција за обраду и манипулисање подацима природније. Функције такође могу наметати раздвајање проблема.
Програмски језици који користе више парадигми као што је то Пајтон омогућавају програмерима да прилагоде организационе парадигме одговарајућим проблемима. Учење да се препозна када треба увести нову класу, за разлику од нове функције, ради поједностављивања или модуларизације програма, важна је вештина пројектовања у софтверском инжењерству која заслужује пажњу.