Садржај

Енкапсулација

У овој лекцији:

  • Зашто подаци треба да буду приватни,

  • Шта је конструктор и како може да се напише.

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

Примером који следи бавићемо се у неколико наврата. За почетак, пример нам даје мотивацију за увођење енкапсулације, а касније ћемо да га искористимо за илустровање још неких концепата, као и за упознавање неких особина језика које нам омогућавају да те концепте спроведемо.

Пример - разломци

Нека је потребно обезбедити поређења и основне рачунске операције над рационалним бројевима, у којима се подаци и резултати задају у облику разломка. Логична идеја је да напишемо класу Razlomak, чији објекти ће да представљају рационалне бројеве. Желимо да имамо на располагању и методе Equals и CompareTo, који постоје у многим класама стандардне библиотеке. Уобичајено понашање ових метода је следеће:

  • метод x.Equals(y) враћа логичку вредност, и то true ако су x и y једнаки, а false у супротном

  • метод x.CompareTo(y) враћа целобројну вредност, и то негативну ако је x мањи од y, нула ако су једнаки, а позитивну ако је x већи од y

па овакво понашање очекујемо и када су x и y објекти класе Razlomak.

Знамо да су разломци \(r_1 = {p_1 \over q_1}\) и \(r_2 = {p_2 \over q_2}\) једнаки ако имају једнаке бројиоце (\(p_1 = p_2\)) и једнаке имениоце (\(q_1 = q_2\)). Међутим, ови разломци могу да буду једнаки и када \(p_1 \neq q_1, p_2 \neq q_2\). На пример, \({1 \over 2} = {3 \over 6}\) мада \(1 \neq 3, 2 \neq 6\). Такође, \({-1 \over 2} = {1 \over -2}\) мада \(-1 \neq 1, 2 \neq -2\).

Провера једнакости би била једноставнија када бисмо могли да наметнемо додатне услове на облик у коме се памти разломак. На пример, када бисмо за сваки разломак \(p \over q\) били сигурни да је нескратив и да важи \(q > 0\), за проверу једнакости би било довоољно да проверимо \(p_1 = p_2\) и \(q_1 = q_2\). Осим тога, било би једноставније да се напише и метод CompareTo (избегли бисмо поређење разломака са негативним имениоцима), метод за приказ вредности разломка (минус се не пише испред имениоца), а вероватно и неки други методи.

Сада се поставља питање како да обезбедимо да увек важе услови које желимо да додатно наметнемо (да је разломак нескратив и да је именилац позитиван). Свакако треба спречити корисника класе да директно задаје или мења вредност бројиоца и имениоца било ког разломка. То значи да бројилац и именилац треба да буду приватна поља класе.

Ово јесте добар почетак, али он отвара нека нова питања. Ако напишемо

public class Razlomak
{
    private int a, b; // razlomak je oblika a/b gde je b > 0
    ...
}

Razlomak r = new Razlomak() { a = 2, b = 3 };

добићемо већ поменуте синтаксне грешке које се односе на последњу линију кода

Ове грешке нас упозоравају да не можемо да доделимо почетну вредност разломку на овај начин, јер су поља a и b недоступна. Да бисмо могли да задајемо почетне вредности разломцима, потребан нам је конструктор објеката класе Razlomak, коме ћемо као параметре да проследимо вредности бројиоца и имениоца.

Конструктор је специјалан метод класе, који увек има исто име као и сама класа, а који као резултат враћа нови објекат те класе. Ако класа у својој дефиницији нема написан конструктор, компајлер јој аутоматски додељује такозвани подразумевани конструктор без параметара. Захваљујући томе, и пре него што научимо шта је конструктор (и пре него што напишемо неки), можемо да пишемо

Razlomak r = new Razlomak();

Ово је позив подразумеваног конструктора класе Razlomak, који је јаван ако постоји (а постоји ако нисмо написали свој, експлицитан конструктор). Подразумевани конструктор увек враћа објекат у коме су сва поља иницијализована на подразумеване вредности (0 за бројеве и карактере, false за логичке вредности, празан стринг са стрингове, null за референциране типове).

У случају да су поља a и b јавна (public), можемо да им доделимо вредности након позивања подразумеваног конструктора

Razlomak r = new Razlomak();
r.a = 2;
r.b = 3;

што је функционално равноправно са раније коришћеним записом

Razlomak r = new Razlomak() { a = 2, b = 3 };

Експлицитан конструктор

У нашем случају, пошто смо одлучили да поља a и b буду приватна, од подразумеваног конструктора нема много користи јер немамо начина да накнадно променимо вредности a и b. Зато нам је овде потребан експлицитан конструктор:

public class Razlomak
{
    private int a, b; // razlomak je oblika a/b gde je b > 0

    public Razlomak(int p, int q)
    {
        if (q == 0)
        {
            throw new Exception("Imenilac razlomka je 0");
        }

        if (q < 0)
        {
            p = -p;
            q = -q;
        }

        a = p;
        b = q;
        Skrati(ref a, ref b);
    }
    //...
}

Пошто намеравамо да користимо написани конструктор ван класе, он је означен као јаван (public). Приметимо да се при писању конструктора не наводи тип враћене вредности, јер се подразумева да је исти као и назив метода, односно класе.

public Razlomak(int p, int q)
{
    ...
}

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

Пошто сваки цео број \(n\) може да се посматра као разломак \(n \over 1\), можемо да напишемо и конструктор који има само један целобројни параметрар. Тај параметар представља бројилац будућег разломка, чији именилац је 1. У овом случају потребни услови већ важе (разломак је нескратив, а именилац је позитиван), па је конструктор сасвим једноставан:

public Razlomak(int n)
{
    a = n; b = 1;
}

Методи Equals и CompareTo сада могу да се напишу знатно једноставније него у случају када не би важили наметнути услови:

public bool Equals(Razlomak r)
{
    return a == r.a && b == r.b;
}

public int CompareTo(Razlomak r)
{
    return a * r.b - r.a * b;
}

Следећи пример садржи све делове класе Razlomak, које смо до сада разматрали и написали. Програм можете да га копирате у своје радно окружење и испробате.

Напомена: Да смо изоставили дефиницију метода Equals, програм би и даље могао да се изврши, али би се понашао другачије.

Када у нашој класи не би био дефинисан метод Equals, користио би се подразумевани истоимени метод који пореди било какве објекте (а не само разломке). Тај метод ради тако што само провери да ли су једнаке адресе објеката који су му прослеђени као параметри. То значи да би резултат извршавања тог, подразумеваног метода Equals за два различита објекта увек био false, јер су адресе тих објеката различите (садржаји објеката се не би ни поредили).

За приватне чланове класе, као што су бројилац a и именилац b у нашој класи Razlomak, кажемо да су енкапсулирана (стављена у капсулу) и могу да се користе само унутар те капсуле, тј. класе. Као што смо већ рекли, корисник класе нема начина да приступи приватним деловима класе, па ни да им мења вредности, мада би њему то можда и било згодно у неким ситуацијама.

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

Увођење и одржавање интерних услова које треба да испуњавају сви објекти дате класе је један од важних разлога због којих нам је енкапсулација потребна. У случају класе Razlomak, то је кључни разлог за енкапсулирање интерних података.

Да бисмо и у наставку рада могли да се ослонимо на важење наметнутих услова унутар класе, водићемо рачуна да сви методи које будемо додали у класу Razlomak одржавају наметнуте услове у постојећим објектима, као и да их успостављају при стварању нових објеката.

(Created using Swinx, RunestoneComponents and PetljaDoc)
© 2022 Petlja
A- A+