RSS

Prokurent

Liczba odsłon: 146

Gdy rok te­mu pi­sa­łem o za­przy­jaź­nia­niu klas w ję­zy­ku C++, za­war­łem w moim tekś­cie stwier­dze­nie, że […] nie ma pros­te­go spo­so­bu, by ukryć przed za­przy­jaź­nio­nym pod­pro­gra­mem lub kla­są część funkcjo­nal­noś­ci kla­sy […]. Sposób – i to wca­le nie aż tak skom­pli­ko­wa­ny – jed­nak ist­nie­je.

Wyobraź­my so­bie na­stę­pu­ją­cą, przy­kła­do­wą kla­sę:

class TestClass
{
public:
    inline const std::string & Attribute() const throw() {
        return attribute;
    }
private:
    friend class UserClass;
    std::string attribute;
    std::string really_private;
};

Wyraże­nie friend class User­Class wy­stę­pu­je w niej, gdyż kla­sa User­Class ma mieć wy­łącz­ne pra­wo mo­dy­fi­ko­wa­nia po­la at­tri­bu­te w do­wol­nej chwi­li i w do­wol­ny spo­sób, na przy­kład pod­czas two­rze­nia obiek­tu (ig­no­ru­je­my przy tym na chwi­lę fakt, że lep­szy do te­go był­by kon­struk­tor pa­ra­met­rycz­ny):

class UserClass
{
public:
    static TestClass GenerateObject() {
        TestClass object;
        object.attribute = "We have private access!";
        return object;
    }
};

Niestety, za­przy­jaź­nie­nie kla­sy Test­Class z kla­są User­Class po­wo­du­je, że ta dru­ga do­sta­je nie­ogra­ni­czo­ne pra­wa do­stę­pu do wszyst­kich ele­men­tów kla­sy Test­Class, w tym do po­la really_pri­vate, któ­re­go w ogó­le nie po­trze­bu­je. Nie sta­no­wi to aż ta­kie­go prob­le­mu przy za­ło­że­niu, że za­przy­jaź­nie­nie jest ściś­le okreś­lo­ne, a oby­dwie kla­sy za­pi­su­je ten sam pro­gra­mis­ta, ro­zu­mie­ją­cy za­sa­dę ich dzia­ła­nia i za­kres, w ja­ki moż­na in­ge­ro­wać we wnętrz­noś­ci Test­Class. Gorzej jed­nak, gdy kla­sę User­Class pi­sze ktoś in­ny, kto nie wie, jak wy­ko­rzys­ty­wać really_pri­vate i – ma­jąc do­stęp do te­go po­la – mo­że na­ro­bić du­żo szkód. Jedynym po­cie­sze­niem jest to, że idea za­przy­jaź­nia­nia wy­klu­cza do­wol­ne roz­sze­rza­nie pu­li uprzy­wi­le­jo­wa­nych klas.

Jak za­tem umoż­li­wić do­stęp do po­la pry­wat­ne­go at­tri­bu­te, nie udo­stęp­nia­jąc przy tym really_pri­vate? Wykorzy­stu­je się do te­go wzo­rzec pro­jek­to­wy pro­ku­ren­ta (ang. attorney de­sign pattern). Proku­rent to za­przy­jaź­nio­na kla­sa, któ­ra udo­stęp­nia nie­pub­licz­ny inter­fejs umoż­li­wia­ją­cy mo­dy­fi­ko­wa­nie wy­bra­ne­go po­la. Jedynie kla­sę pro­ku­ren­ta za­przy­jaź­nia się z do­ce­lo­wą, przez co nie ist­nie­je bez­po­śred­nia moż­li­wość zmia­ny ukry­te­go po­la:

class TestClass
{
public:
    inline const std::string & Attribute() const throw() {
        return attribute;
    }
private:
    friend class TestClassAttorney;
    std::string attribute;
    std::string really_private;
};

class TestClassAttorney
{
private:
    friend class UserClass;
    inline static void Attribute(TestClass &target, const std::string &new_attribute) {
        target.attribute = new_attribute;
    }
};

class UserClass
{
public:
    static TestClass GenerateObject() {
        TestClass object;
        TestClassAttorney::Attribute(object, "We have private access!");
        return object;
    }
};

Tak roz­bu­do­wa­ną kon­struk­cję ma sens sto­so­wać tyl­ko wte­dy, gdy chce­my wy­raź­nie za­zna­czyć, któ­re ele­men­ty inter­fej­su pry­wat­ne­go mo­gą być w osta­tecz­no­ści wy­ko­rzys­ty­wa­ne przez za­przy­jaź­nio­ną kla­sę. Może to sta­no­wić ele­ment sa­mo-ogra­ni­cza­nia się jed­ne­go pro­gra­mis­ty, za­po­bie­ga­ją­ce­go błę­dom, lub ogra­ni­cze­nia na­rzu­ca­ne­go in­nym pro­gra­mis­tom reali­zu­ją­cym du­ży pro­jekt. Wzorzec pro­ku­ren­ta na­bie­ra jed­nak o wie­le więk­sze­go sen­su, gdy roz­bu­du­je­my go w spo­sób umoż­li­wia­ją­cy za­przy­jaź­nie­nie do­wol­nej kla­sy z Test­Class przez zwyk­łe dzie­dzi­cze­nie:

class TestClass
{
public:
    inline const std::string & Attribute() const throw() {
        return attribute;
    }
private:
    friend class TestClassAttorney;
    std::string attribute;
    std::string really_private;
};

class TestClassAttorney
{
protected:
    inline static void Attribute(TestClass &target, const std::string &new_attribute) {
        target.attribute = new_attribute;
    }
};

class UserClass : public TestClassAttorney
{
public:
    static TestClass GenerateObject() {
        TestClass object;
        TestClassAttorney::Attribute(object, "We have private access!");
        return object;
    }
};

W ten spo­sób po­mi­ja­my w ra­zie po­trze­by sztyw­ne ogra­ni­cze­nia za­przy­jaź­nia­nia klas, do­pusz­cza­jąc two­rze­nie do­wol­nie sze­ro­kiej pu­li klas za­przy­jaź­nio­nych, ma­ją­cych do­stęp do bar­dzo ogra­ni­czo­ne­go wy­cin­ka inter­fej­su pry­wat­ne­go. Możemy na­wet zde­fi­nio­wać wie­le klas pro­ku­ren­tów dla da­nej kla­sy, z do­wol­ną do­kład­no­ścią ste­ru­jąc za­kre­sem do­stę­pu do we­wnętrz­nych struk­tur kla­sy. Nie mu­si­my się też w tym mo­men­cie aż tak bać in­nych pro­gra­mis­tów, chcą­cych in­ge­ro­wać w spo­sób dzia­ła­nia na­szej kla­sy, gdyż me­to­dy po­śred­ni­czą­ce – w po­wyż­szym przy­kła­dzie bar­dzo pros­te i dzię­ki te­mu nie wpro­wa­dza­ją­ce na­rzu­tu cza­so­we­go – moż­na w ra­zie po­trze­by roz­bu­do­wać o we­ry­fi­ka­cję da­nych w po­sta­ci aser­cji lub peł­no­praw­nych sytu­acji wy­jąt­ko­wych.