Апстракција, индексери¶
У овој лекцији:
шта је апстракција у програмирању и зашто је корисна,
шта је индексер и како се користи.
Замислите некога ко жели даљински управљач за телевизор са само 5 дугмади (укључивање/искључивање, појачај, утишај, претходни канал, следећи канал), а добије управљач са преко 40 дугмади. Таквом кориснику телевизора ће можда бити тешко да нађе на даљинском дугме које му је потребно, а можда ће омашком и да обрише све меморисане канале или да пребаци улаз са антене на HDMI прикључак, на који није прикључен никакав уређај и са ког не стиже никакав сигнал. За овог корисника је најудобније да уопште нема могућност да бира улаз са ког долази сигнал, нити могућност да ручно бира те-ве канале. Могућност детаљније контроле телевизора га само збуњује и отежава му употребу даљинског управљача.
Сваки објекат може да буде сложенији него што нас у одређеном контексту интересује. Претпоставимо да водимо евиденцију ученика једне школе. У том контексту нас за сваког ученика интересује име и презиме, одељење у које иде, оцене и изостанци и ништа или врло мало преко тога. Са становишта школске евиденције не интересује нас нпр. надимак ученика, његова телесна маса, број браће и сестара, омиљено јело, место у коме је провео последњи распуст, и сл. Класа Ucenik
, која би нам омогућавала да попуњавамо и пратимо много више од онога што нам је у датом контексту потребно није одговарајућа.
Сакривањем или занемаривањем детаља који нас не интересују добијамо једноставнији модел објеката којима се бавимо. Смањивање сложености сакривањем непотребних детаља називамо апстракција. Апстракција нам омогућава да се не замарамо детаљима који нам нису потребни.
За класе које постоје у стандардној библиотеци језика не морамо да знамо (и често не знамо) како су те класе имплементиране, али то нас не спречава да се њима послужимо.

Написати програм који учитава неколико речи у једном реду текста и одговара на питање да ли су све те речи различите.
Да бисмо добили кратак и врло ефикасан програм који решава задатак, довољно је да знамо да у библиотеци постоји класа HashSet
, која представља скуп вредности задатог типа. Од метода које ова класа има, биће нам довољна само два: метод Add
за убацивање вредности у скуп и метод Count
који даје број елемената скупа.
При решавању неког другог проблема могу да нам затребају још неки (јавни) методи класе HashSet
(нпр. избацивање елемента из скупа, провера да ли је елемент у скупу), али ни у једној примени ове класе нам неће бити потребно да знамо на који начин се унутар класе чувају елементи њом представљеног скупа, нити било који други детаљи о приватним пољима и методима класе. У свакој примени нам је довољно да знамо како се ова класа употребљава, а не морамо да знамо како она ради. Аутори библиотеке могу и да промене имплементацију ове класе (нпр. ако буду откривени неки још ефикаснији поступци од оних који се тренутно користе) а да ми то уопште не приметимо. То је суштина апстракције.
Исти однос какав ми имамо према готовим класама стандардне библиотеке, имају други програмери према класама које ми напишемо за њих. Они желе апстракцију, јер им она омогућава да се не удубљују у све оно што смо ми морали да знамо да бисмо написали класу. Зато треба да настојимо да својим класама осмислимо што једноставнији интерфејс, који је интуитивно јасан и довољан за обављање онога чему је класа намењена.
Примера ради, методе NZD
и Skrati
у класи Razlomak
смо могли да прогласимо и за јавне. Тиме не бисмо угрозили исправно функционисање ове класе, али појава тих метода у интерфејсу класе би могла да изазове недоумице код програмера који користи класу. На пример, корисник класе би могао да се запита да ли и када треба да позове неки од ових метода да би правилно користио класу. Могуће је да би он позивао ове методе тамо где то није неопходно, или би изгубио извесно време анализирајући имплементацију класе Razlomak
, уколико му је она доступна. Тиме што смо методе NZD
и Skrati
оставили као приватне, учинили смо интерфејс класе јаснијим.
Пример - динамички сабирач¶
Замислимо да смо од другог програмера (зваћемо га Жељко) тражили да за нас напише класу по следећем опису:

Написати класу чији објекти могу да се користе као целобројни низови. Прецизније, класа треба да подржи следеће поступке:
формирање објекта који представља низ задате дужине
постављање вредности задатом елементу низа
очитавање вредности задатог елемента низа
Додатно, ова класа треба да омогући свом кориснику да тражи и добије збир елемената задатог сегмента низа.
У једном од претходних примера смо решавали нешто једноставнији задатак. У том примеру је објекат класе Sabirac
био креиран на основу већ формираног низа и није омогућавао очитавање и мењање вредности елемената. Сада тражимо да објекат узима у обзир ажурирање вредности елемената низа и да даје збирове сегмената у складу са новим садржајем низа.
Жељко би могао релативно брзо да дође до оваквог решења:
Прво Жељково решење је једноставно, лако разумљиво и директно, али није нарочито ефикасно. Наиме, можемо да приметимо да је за добијање збира неког сегмента потребно време сразмерно дужини тог сегмента. И поред овог недостатка, корисно је да Жељко одмах постави ову класу на место одакле можемо да је користимо, да бисмо могли да потврдимо да нам интерфејс класе одговара и да смо се разумели око захтева. Одмах после тога, ми можемо да почнемо да пишемо свој део пројекта користећи ову прву верзију класе - решења, а за то време Жељко може да ради на ефикаснијој имплементацији класе. У неком тренутку, Жељко ће да дође до ефикаснијег решења, које може да изгледа овако:
Захваљујући концепту апстракције, ми не морамо да знамо шта је Жељко у међувремену научио или смислио да би класу учинио ефикаснијом, све док интерфејс класе остаје исти. Не морамо чак да знамо ни када је Жељко поставио ново решење, јер измена у имплементацији класе ни на који начин не омета наш рад. Нека смо, на пример, раније написали ових неколико наредби да испробамо класу DinamickiSabirac
:
int n = 5;
DinamickiSabirac ds = new DinamickiSabirac(n);
for (int k = 0; k < n; k++)
ds[k] = k+1;
Console.WriteLine(ds.Zbir(0, 5)); // 1+2+3+4+5 = 15
Console.WriteLine(ds.Zbir(1, 3)); // 2+3+4 = 9
Console.WriteLine(ds.Zbir(2, 2)); // 3+4 = 7
Нема портебе да након Жељкове измене било шта мењамо у овим наредбама. Оне и даље могу да се изврше и да дају исти резултат. Једино што можемо да приметимо после Жељкове промене је да се метод Zbir
сада за дугачке низове извршава знатно брже него раније (време рада метода Zbir
је сада сразмерно логаритму дужине сегмента), а можда и то да је постављање вредности елементима низа сада нешто спорије (сразмерно лограритму дужине низа, уместо да буде константно).