понедельник, 5 июля 2010 г.

Хак №4: Доступ к protected методам

Это перевод Hack #4: Access to protected methods. Автор: Hallvard Vassbotn.

Как мы видели ранее, возможно получить доступ для записи для свойства только для чтения, вернее, к его полю данных. Также вы можете повысить видимость protected-свойств до public или published, чтобы получить к ним доступ, но у нас нет соответствующего синтаксиса, чтобы сделать это для полей или методов. Часто вам бывает просто необходимо вызвать protected-метод какого-то класса - часто это метод в VCL-компоненте или сторонней библиотеке кода или компонентов. Часто это является признаком плохого (недостаточно гибкого) дизайна класса. Автор кода не подумал, что коду уровня приложения может потребоваться доступ к этому методу. Т.е. грубо говоря, у нас есть такой код:
unit Utility;

interface

type
  TUtility = class
  protected
    procedure Useful;
  public
    constructor Create;
    procedure Boring;
  end;
Мы хотим вызвать protected-метод Useful, но всё, к чему мы имеем доступ, - это public-метод Boring. Типичная ситуация, да? ;)

Если вам повезёт, то вы имеете контроль над типом используемых экземпляров, так что вы можете написать своего наследника, который делает метод доступным "чистым способом" (т.е. документировано, не хаком), вроде такого:
type
  TMyUtility = class(TUtility)
  public
    procedure Useful;
  end;

procedure TMyUtility.Useful;
begin
  inherited Useful;
end;
Теперь вы просто можете использовать TMyUtility всюду вместо TUtility - и у вас будет доступен метод Useful. Последний штрих: использовать в точности то же имя класса, например:
unit MyUtility;

interface

uses
  Utility;

type
  TUtility = class(Utility.TUtility)
  public
    procedure Useful;
  end;
Теперь вам нужно только добавить ссылку на модуль MyUtility в список uses после ссылки на модуль Utility.

Однако, иногда у вас нет контроля над типом используемых экземпляров - к примеру, они создаются внутри стороннего кода.

Как знают многие продвинутые Delphi-программисты, вы легко можете получить доступ к protected-членам класса, просто приводя экземпляр к классу-наследнику, объявленному в том же модуле, что и выражение с приведением типов. Например, чтобы вызвать protected-метод Useful экземпляра класса TUtility, вы можете просто написать:
type
  TUtilityEx = class(TUtility)
  end;

procedure Foo(Instance: TUtility);
begin
  // Не компилируется: у вас нет доступа к protected-методу
  Instance.Useful;   
  // Компилируется отлично, вызывает protected-метод
  TUtilityEx (Instance).Useful; 
end;
Способ работы директив видимости Delphi позволяет всему коду, объявленному в том же модуле, иметь полный доступ ко всем членам класса. Это вроде неявной концепции дружественных классов из C++.

Один очень обещающий поставщик компонентов обычно прячет даже действительно полезные (а иногда и необходимые) методы своих компонентов и вспомогательные классы в protected раздел - скорее всего, только в попытке скрыть их сложность от наивных пользователей. Затем во всех своих модулях, которые должны иметь доступ к этим методам, они делают указанный выше хак с объявлением Dummy-класса для своих собственных классов. Ужасно! Этот хак должен использоваться только для получения доступа к чужому коду, а не использоваться для взаимодействия между своими модулями!

Любопытно, что компилятор использует этот локальный хак-класс только для получения доступа к protected-методу. Хотя класс TUtilityEx упоминается в исходном коде, но ничего из этого класса (VMT, RTTI и т.п.) не нужно для скомпилированного кода. Поэтому умный линкёр удаляет лишнюю информацию из исполняемого модуля. В противном случае, этот хак был бы весьма накладен с точки зрения размера кода.

Читать далее.

10 комментариев:

  1. Я перевожу только интересные (для меня) посты. Хак №3 - это включение JIT-оптимизатора для D8 .NET. Мне это не интересно.

    ОтветитьУдалить
  2. atrus-at-lj вы тоже вспомнили историю про свиней в американской школе?

    ОтветитьУдалить
  3. Гы, а я не слышал раньше :))

    ОтветитьУдалить
  4. Анонимный5 июля 2010 г., 22:21

    Эх, вот еслиб можно было, без извращений, к приватным полям достучаться... Вроде TWinControl.FControls

    ОтветитьУдалить
  5. Это достаточно старая фишка. Очень давно читал тоже самое в рунете...

    >> Эх, вот еслиб можно было, без извращений, к приватным полям достучаться...

    И слава Богу, что нельзя! Ведь это - основа Объектно-ориентированной концепции, в которой класс рассматривается как ящик пандоры. То есть то, что используется внутри не уместно для внешнего использования (закрытый ящик), а пытаешься открыть и тут те БАЦ! Куча проблем и надежда вида: - "Ну заработай! Пожалуйста заработай!!!" .

    ОтветитьУдалить
  6. Анонимный6 июля 2010 г., 10:40

    >> И слава Богу, что нельзя!

    Ну мне вот надо сделать простую вещь - узнать, лежат ли на TWinControl, наследники от TControl (если нет, то можно очень сильно ускорить отрисовку). Еслиб FControls было хотя бы в protected, то решение простое: (FControls <> nil), а теперь приходится перебирать все контролы (ControlCount, Controls[]) и тестировать каждый оператором is, что не выглядит быстрым и изящным. И теперь вопрос - сильно ли меня волновали принципы OOП, когда я увидел, что FControls в private? :)

    ОтветитьУдалить
  7. Я не про то, что совсем уж нельзя, просто это не нужно ) Если разработчики скрыли поле - значит они понимают, что Вам оно не нужно. Там ведь тоже не дураки сидят, так что думаю, если оно было бы нужно как открытое - открыли бы. Получить доступ к приват полям можно, о чём свидетельствует тот же 5 хак от Хальварда (можно найти на его блоге, ссылка вверху).

    ОтветитьУдалить
  8. > Один очень обещающий поставщик компонентов обычно прячет даже действительно полезные (а иногда и необходимые) методы своих компонентов и вспомогательные классы в protected раздел

    Многие поставщики компонентов (например TMS Scripter Pro) прячут действительно полезный код в секции private, что делает практически невозможным кастомизацию их классов через написание своих наследников. И подозреваю, что это делается специально.

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.