Xpoint
   [напомнить пароль]

Межобъектное взаимодействие

Метки: [без меток]
2008-01-16 14:42:39 [обр] Kleo[досье]

Вкратце суть пробемы:
создается система, работоспособность которой обеспечивают множество объектов, которые, естественно, взаимодействуют между собой посредством определенных
интерфейсов. По мере развития возникает ситуация, когда появляются несколько особых объектов, с которым должны иметь достум многие части системы (такие как объекты сессий, конфигурации, прав
доступа и т.д.). Каким образом можно обеспечить доступ к ним?

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

Следующий шаг - это создать анонимный хэш, внутри которого мы будем хранить ссылки на все наши повсеместно используемые объекты. Плюсы этого решения: на вход конструкторов нам уже НЕ нужно передавать большое кол-во параметров, достаточно передать ОДНУ ссылку на этот анонимный хэш. Но так как мы стараемся делать все аккуратно, создается не просто анонимный хэш, а объект, в обязанности которого входит только хранение других объектов. Так же в нем создаем методы добавления объектов в хранилище и метод для их извлечения (все это есть в простых тестовых примерах ниже).

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

В этом случае нам могут помочь замыкания и Typeglob. Таким образом можно в хранитель добавить еще один метод, который через typeglob создаст в требуемом объекте метод доступа к хранителю посредством замыкания.

Плюсы данного решения: можно централизованно создавать объекты, также централизовано создавать в них методы доступа к реестру (хранилищу), а также получаем возможность вообще не передавать хранитель в конструкторы. Решение кажется идеальным, если бы не одна особенность: некоторые объекты при этом уничтожаются раньше чем надо.

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

Потребуется создать четыре файла. Все они очень просты:

  1. test.pl - исполняемый файл
  2. ObjReestr.pm - модуль хранителя объекта
  3. Ajax.pm - будет у нас неким абстрактым модулем ajax, может быть любым другим
  4. Buf.pm - пусть будет у нас еще один некий абстрактный модуль буфера
спустя 31 минуту [обр] Kleo[досье]

test.pl - исполняемый файл:

#!/usr/bin/perl -wl

use strict;
use lib "C:/test/";

use ObjReestr; # загружаем пакет хранителя (реестра объектов)
use Ajax;      # пакет ajax
use Buf;       # пакет буфера

my $buf = Buf->new(1);            # Создаем первый объект буфера
my $objReestr = ObjReestr->new(); # создаем хранитель объектов

# Создаем и добавляем в хранитель объект ajax
# (Передаем в ajax и сохраняем его в виде параметра первый буфер, чтобы проследить заодно и его состояние)
my $ajax = Ajax->new($buf);
$objReestr->addObj($ajax, 'ajax');
#$objReestr->createCallerReestr($ajax);

ObjReestr.pm - модуль хранителя:

package ObjReestr;
use strict;

sub new
{
   my $class  = shift;
   return bless ({}, $class);
}

# Метод добавления объектов в реестр
# На вход подаются объект и его имя, под которым но будет храниться в реестре
sub addObj
{
   my ($self, $obj, $name) = @_;
   $self->{$name} = {'obj'=>$obj};
   return 1;
}

# Метод извлечения объекта из реестра по его имени
sub get
{
   my ($self, $name) = @_;
   return $self->{$name}{'obj'};
}

# Метод, создающий в требуемом объекте метод доступа к хранилищу
# на вход подается объект в котором требуется создать метод доступа
sub createCallerReestr
{
   my ($self, $obj)     = @_;
   my $package  = ref $obj; # Пакет объекта

   my $slot = $package."::objReestr"; # имя создаваемый метода
   no strict "refs";                  # разрешаем символические ссылки
   # Создаем метод доступа к хранилищу
   *{$slot} = sub {
     my $name = $_[1];         # имя объекта в хранилище
     return $self->get($name); # здесь $self - это self метода createCallerReestr
     };
   return 1;
}

sub DESTROY
{
   my $self = shift;
   print 'ObjREESTR DESTROY';
}

1;

Ajax.pm - модуль ajax:

package Ajax;
use strict;

sub new
{
   my $class  = shift;
   my $self = bless ({}, $class);
   $self->{buf_1} = shift;       # запоминаем первый буфер
   $self->{buf_2} = Buf->new(2); # создаем второй буфер
   return $self;
}

sub DESTROY
{
   my $self = shift;
   print 'AJAX DESTROY:';
  # Состояние буферов на момент уничтожения объекта ajax.
  # Теоретически должны быть оба рабочих, т.к. объект ajax содержит ссылки на каждого их них:
   print "    AJAX BUF [index 1]: ". (ref $self->{buf_1} ? 'ok' : 'empty');
   print "    AJAX BUF [index 2]: ". (ref $self->{buf_2} ? 'ok' : 'empty');
}

1;

Buf.pm - модуль буфера:

package Buf;
use strict;

sub new
{
   my $class  = shift;
   my $index = shift;
   return bless ({'index'=>$index}, $class);
}

sub DESTROY
{
   my $self = shift;
   print 'BUF index:'.$self->{index}.' DESTROY';
}

1;
спустя 12 минут [обр] Kleo[досье]

Запускаем исполняемый файл на исполнение (не забудьте поменять путь use lib в третьей строчке)
видим что все ОК, оба наших объекта буфера на месте (AJAX BUF [index 1]: ok и AJAX BUF [index 2]: ok):

ObjREESTR DESTROY
AJAX DESTROY:
    AJAX BUF [index 1]: ok
    AJAX BUF [index 2]: ok
BUF index:2 DESTROY
BUF index:1 DESTROY

Теперь попробуем раскоментировать последнюю строчку в исполняемом файле:

$objReestr->addObj($ajax, 'ajax');
#$objReestr->createCallerReestr($ajax);

убираем комментарий:

$objReestr->addObj($ajax, 'ajax');
$objReestr->createCallerReestr($ajax);

Запускаем скрипт еще раз и видим что объект второго буфера уничтожился раньше чем надо (AJAX BUF [index 2]: empty):

BUF index:2 DESTROY
AJAX DESTROY:
    AJAX BUF [index 1]: ok
    AJAX BUF [index 2]: empty
BUF index:1 DESTROY
ObjREESTR DESTROY

Теперь попробуем изменить в исполняемом файле порядок инициализации модулей:

use ObjReestr; # загружаем пакет хранителя (реестра объектов)
use Ajax;      # пакет ajax
use Buf;       # пакет буфера

меняем на:

use Buf;       # пакет буфера
use Ajax;      # пакет ajax
use ObjReestr; # загружаем пакет хранителя (реестра объектов)

И видим что теперь все в порядке:

ObjREESTR DESTROY
AJAX DESTROY:
    AJAX BUF [index 1]: ok
    AJAX BUF [index 2]: ok
BUF index:1 DESTROY
BUF index:2 DESTROY

Может ли кто-нибудь пояснить почему так происходит? По идее второй объект нашего буфера должен
уничтожаться только после того, как на него никто не будет ссылаться, а т.к. объект ajax содержит ссылки на оба объекта буфера,
то логично было бы предположить что они будут уничтожены не раньше, чем будет уничтожен объект на них ссылающийся. Фактически
же получается иначе.
Очевидно, что на механизм ссылок каким-то образом влияет метод createCallerReestr, но каким образом, понять не могу.

Powered by POEM™ Engine Copyright © 2002-2005