Logo

10 декабря 2014 г.

Этюд 7. Обертывание свойства-переменной

Одно из противоречий, с которым приходится сталкиваться в практическом ООП, — публичность переменных в качестве полей классов. Несмотря на то, что многие языки позволяют описать поля-переменные, это считается дурным тоном, поскольку переменная не застрахована от случайного затирания и не может быть перекрыта в потомках, если понадобится.

Традиционное решение в виде свойств с «геттерами» и «сеттерами» является паллиативом и лишь фиксирует исторически сложившуюся неполноценность ООП, добавленного в устоявшиеся языки без пересмотра старых концепций.

На Delphi

Для Delphi характерны обе проблемы полей-переменных: и утечек не избежать, и в потомках не перекрыть. Штатное решение их в Delphi — свойства, реализованные виртуальными методами с ручным освобождением объектов, где требуется:
type
  TContainedObject = class
    // some object
  end;

  TContainer = class
  private
    FHoldingObject: TContainedObject;
  protected
    function GetHoldingObject: TContainedObject; virtual;
    procedure SetHoldingObject(Value: TContainedObject); virtual;
  public
    property HoldingObject: TContainedObject read GetHoldingObject
      write SetHoldingObject;
  end;

  TBlockableContainer = class(TContainer)
  private
    FUpdateCount: Integer;
  protected
    function GetHoldingObject: TContainedObject; override;
    procedure SetHoldingObject(Value: TContainedObject); override;
  public
    procedure BeginUpdate;
    procedure EndUpdate;
  end;

{ TContainer }

function TContainer.GetHoldingObject: TContainedObject;
begin
  Result := FHoldingObject;
end;

procedure TContainer.SetHoldingObject(Value: TContainedObject);
begin
  FHoldingObject.Free;
  FHoldingObject := Value;
end;

{ TBlockableContainer }

procedure TBlockableContainer.BeginUpdate;
begin
  Inc(FUpdateCount);
end;

procedure TBlockableContainer.EndUpdate;
begin
  Dec(FUpdateCount);
end;

function TBlockableContainer.GetHoldingObject: TContainedObject;
begin
  if FUpdateCount = 0 then
    Result := inherited GetHoldingObject
  else
    Result := nil;
end;

procedure TBlockableContainer.SetHoldingObject(Value: TContainedObject);
begin
  BeginUpdate;
  try
    inherited;
  finally
    EndUpdate;
  end;
end;
Ручной работы предостаточно. Всего каких-то два с половиной класса, а объем кода уже — ого-ого! К сожалению, Delphi-программистам платят на за число строк...

На Канторе

Аналогичный код на Канторе куда немногословней:
public class ContainedObject of
  // some object
end;

public class Container of
  public ref var ContainedObject HoldingObject; // var-свойство
end;

public class BlockableContainer from Container of
  var Core:Integer updateCount;

  public BeginUpdate of
    updateCount++;
  end;
  public EndUpdate of
    updateCount--;
  end;

  public ref ContainedObject HoldingObject = // out-свойство
    ?updateCount{[0, inherited], null};

  public HoldingObject(ref ContainedObject value) of // in-свойство
    BeginUpdate;
    do
      inherited;
    finally
      EndUpdate;
    end;
  end;
end;
Причин краткости две, по числу решаемых проблем:
  1. Трактовка var-свойств как пары in-свойства и out-свойства, перекрываемых по отдельности.
  2. Корректное присваивание объектов без появления висячих ссылок и утечек памяти.
Код, соответствующий TContainer.GetHoldingObject и TContainer.SetHoldingObject в примере на Delphi, компилятор Кантора сгенерирует сам. В этом ему поможет модификатор ref, включающий механизм подсчета равноправных ссылок на объект, если это потребуется. Механизм полностью контролируется компилятором и скрыт от глаз программиста.

Комментариев нет :

Отправить комментарий

Выбрав в выпадающем списке пункт «Имя/URL», можно оставить комментарий от своего имени без предварительной регистрации.