Я когда-то читал книгу еврейского писателя Шолом-Алейхема... У него там была занятная строка: "Если нельзя, но очень хочется, то можно" | |
© Штирлиц "Семнадцать мгновений весны" |
Все члены класса обладают одним важным атрибутом – область видимости. Область видимости определяется специальными ключевыми словами private, protected, public, published и automated, которые называются модификаторами доступа. Сегодня я хочу сказать пару слов о модификаторе доступа protected. Члены класса, которые защищены им видны в любом классе являющимся его наследником и в том модуле, где описан класс. Эту область видимости можно сузить, если к модификатору "protected" добавить слово "strict", тогда эти члены класса увидят только его наследники.
Зачем это надо? Например, за protected, а лучше за strict protected можно скрыть абстрактный метод. Это избавит программиста, который будет использовать класс, от желания вызвать этот метод. Но иногда авторы классов делают обычные методы protected. Самое печальное, что таким образом они скрывают много полезного. Зачем? Разумного объяснения этому я пока не слышал. Некоторые говорят, что причина в реализации одного из основных принципов ООП – инкапсуляции. Но инкапсуляция подразумевает под собой скрытие членов класса от посторонних глаз. А о какой инкапсуляции может вестись речь, если protected члены класса можно легко увидеть и использовать?
Давайте посмотрим ситуацию на примере. Например, в модуле uXyz у нас есть класс:
unit uXyz; interface type TXyz = class strict protected procedure StrictProtectedProc; protected procedure ProtectedProc; procedure DoSomething; virtual; abstract; public end; implementation { TXyz } procedure TXyz.ProtectedProc; begin Writeln('ProtectedProc'); end; procedure TXyz.StrictProtectedProc; begin Writeln('StrictProtectedProc'); end; // -------------------------- procedure Test; var xyz: TXyz; begin xyz := TXyz.Create; xyz.ProtectedProc; xyz.Free; end; end.Любая процедура, в модуле uXyz имеет доступ к protected методу ProtectedProc. Так, где тут инкапсуляция? А вот обращение в процедуре Test к методу StrictProtectedProc приведет к ошибке при компиляции:
[dcc32 Error] uXyz.pas(35): E2362 Cannot access protected symbol TXyz.StrictProtectedProcТеперь перенесем процедуру Test в другой модуль:
program TestXyz; {$APPTYPE CONSOLE} uses uXyz in 'uXyz.pas'; procedure Test; var xyz: TXyz; begin xyz := TXyz.Create; xyz.ProtectedProc; xyz.Free; end;Попытка компиляции программы приводит к ошибке:
[dcc32 Error] TestXyz.dpr(13): E2362 Cannot access protected symbol TXyz.ProtectedProcЭто как раз пример того самого случая, когда автор класса спрятал нужный нам метод за модификатором protected. Этим часто грешат некоторые разработчики библиотек для Delphi. Конечно, при наличии исходных кодов библиотеки этот метод спокойно можно перенести в public. А, что делать если исходных кодов нет? Воспользуемся тем, что protected члены класса видят его наследники и объявим класс "пустышку":
type TXyzHack = class(TXyz);через который вызовем спрятанный от нас метод ProtectedProc:
procedure Test; var xyz: TXyzHack; begin xyz := TXyzHack.Create; xyz.ProtectedProc; xyz.Free; end;или так
procedure Test; var xyz: TXyz; begin xyz := TXyz.Create; TXyzHack(xyz).ProtectedProc; xyz.Free; end;Вариант вызова в данном случае зависит от конкретной ситуации или еще кому как нравится.
Попытка вызвать strict protected метод StrictProtectedProc таким же способом приведет к знакомой нам ошибке:
[dcc32 Error] TestXyz.dpr(16): E2362 Cannot access protected symbol TXyz.StrictProtectedProcНо это случай тоже из разряда "если нельзя, но очень хочется, то можно". Немного усовершенствуем наш класс "пустышку" и переопределим в нем метод StrictProtectedProc из которого вызываем его родительскую реализацию:
type TXyzHackEx = class(TXyz) public procedure StrictProtectedProc; end; procedure TXyzHackEx.StrictProtectedProc; begin inherited; end;И спокойно вызовем strict protected метод:
procedure Test; var xyz: TXyz; begin xyz := TXyz.Create; TXyzHackEx(xyz).StrictProtectedProc; xyz.Free; end;Таким образом модификаторы protected и strict protected легко обходятся и их нельзя назвать механизмом реализации инкапсуляции. По моему мнению, инкапсуляцию реализует только модификатор private, а точнее strict private.
В продолжение данной темы предлагаю ознакомиться с моей следующей статьей Доступ к private членам класса. В ней описаны другие способы, которые позволят получить доступ и к protected и к private членам класса.
Не хочу Вас разочароывать, но если пользоваться грязными хаками типа
ОтветитьУдалитьTXyzHackEx(xyz).StrictProtectedProc;
(это и есть нарушение OOP: обрашение к TXyz как к TXyzHackEx коим TXyz не является) то можно получить доступ и к приватныем полям и методам ...
получить доступ к приватным полям и методам используя TXyzHack из статьи не получается :(
ОтветитьУдалить@Max. Почему это грязный хак? Это использование того, что наследники класса видят protected методы. Так, что никакого хака, все на законных основаниях.
ОтветитьУдалить@Анонимный. Завтра напишу, как вызвать приватный метод. Сегодня я уже спасть.
Через новый rtti можно и к приватным получить доступ
ОтветитьУдалитьПриветный метод вызвать нельзя - он же приватный. Разработчики Delphi позаботились о том, чтобы в Delphi соблюдался принцип инкапсуляции
ОтветитьУдалитьХм, мне казалось я объяснил почему это грязный хак, но видимо надо было объяснять подрбнее
ОтветитьУдалитьВозьмем например две перемменые AEdit: TEdit и AGrid: TStringGrid. Надеюсь никто не будет спорить что
AGrid := TStringGrid(AEdit);
это грязный, нарушающий ООП.
После такого приведения по прежнему можно вызывать методы обшего предка (например методы TComponent) но вот что будет при вызове новых методов объявленных в TStrinGrid? В лучшем случае будет како-нибидь Access Violation, в худшем случае код отработает без ошибок, но он может повредить какие-нибудь данные и ваша программа может вылетать уже в других местах...
PS Как сказал Бен Паркер "большая сила требует большой ответственности". Вот статью про большую силу вы написали, а вот про большую ответственность, которая ложиться на разработчика который пытается достучаться до приватных методов и данных указать забыли ...
AGrid := TStringGrid(AEdit);
ОтветитьУдалитьэто грязный, нарушающий ООП.
Согласен. Но вы перегибаете. Я не призываю приводить всё ко всему и беспорядочно вызывать методы. Я показал, как вызвать метод родительского класса. Это не нарушение ООП.
Кто же виноват, что разработчики бездумно прячут нужные методы/свойства от тех, кто работает с их классом? К тому же, этот трюк доступа к protected очень старый и я думаю, что все его знают и без меня.
P.S. вот грязный метод:
машина(велосипед).колесо.накачать - это грязный трюк, но работающий. Главное качать с умом, что бы камера не лопнула ;)