Как использовать main :: data в модулях? - PullRequest
0 голосов
/ 02 декабря 2011

В скрипте я инициализирую несколько обработчиков и устанавливаю переменные, которые должны быть доступны для функций в отдельных модулях.Какой наилучший способ их использования ($q, $dbh, %conf) в модулях?

Пример псевдомодуля:

package My::Module

sub SomeFunction (
    @data = $dbh->selectrow_array("SELECT * FROM Foo WHERE Bar = ?", undef, $conf{Bar} );
    return $q->p( "@data" );
)
1;

Пример псевдосценария:

use CGI;
use DBI;
use My::Module;

our $q = new CGI;
our $dbh = some_connenction_function($dsn);
our %conf = ( Foo => 1, Bar => 2, Random => some_dynamic_data() );

Я понимаю, что использование main:: пространства имен будет работать, но что будет чище?Или нет?

Ответы [ 3 ]

5 голосов
/ 02 декабря 2011

пакет My :: Module Ваши модули должны быть независимы от контекста. То есть они не должны ожидать, что $dbh является обработчиком базы данных или что они должны возвращать вещи в $q. или эта конфигурация сохраняется в %conf.

Например, что если вы вдруг окажетесь с двумя экземплярами дескрипторов базы данных? Чем ты занимаешься? Я ненавижу, когда модуль требует, чтобы я использовал специфичные для модуля переменные для конфигурации, потому что это означает, что я не могу использовать два разных экземпляра этого модуля.

Итак, у вас есть два варианта:

  • Либо передайте необходимые данные каждый раз.
  • Создайте экземпляр объекта (это правильно, объектно-ориентированное программирование) для хранения необходимой информации.

Давайте рассмотрим первый экземпляр, используя ваш псевдокод:

sub someFunction (
    %param = @_;
    %conf = %{param{conf};

    @data = $param{dbh}->selectrow_array(
       "SELECT * FROM Foo WHERE Bar = ?", undef, $conf{Bar} 
    );
    return $param{q}->p( "@data" );
)
1;

Пример псевдо-скрипта:

use CGI;
use DBI;
use My::Module;

my $q = new CGI;
my $dbh = some_connenction_function($dsn);
my %conf = ( Foo => 1, Bar => 2, Random => some_dynamic_data() );

someFunction (
   q    => $q,
   dbh  => $dbh,
   conf => \%conf,
);

Здесь я использую вызовы параметров. Не так уж плохо. Это? Теперь, если вам нужен другой оператор select, вы можете использовать разные переменные.

Конечно, но что, если вы не хотите постоянно передавать переменные. Итак, вы можете использовать Объектно-ориентированные методы . Теперь расслабься и успокойся. Есть много, много веских причин для использования объектно-ориентированного дизайна:

  • Это может упростить ваше программирование : Это сбивает с толку людей, потому что объектно-ориентированное программирование означает думать о том, как ваша программа будет работать, затем разрабатывать ее, а затем создавать различные объекты. Все, что вы хотите сделать, это код и убрать его с дороги. Дело в том, что если вы думаете о своей программе и разрабатываете ее, она работает лучше, а код - быстрее. Аспект дизайна исключает сложность вашего основного кода и надежно убирает его в небольшие, легко усваиваемые процедуры.
  • Это то, что круто в Perl : Вам придется привыкнуть к объектно-ориентированному Perl, потому что это то, что пишут все остальные. Вы увидите множество операторов типа my $foo = Foo::Bar->new;. Более новые модули Perl имеют только объектно-ориентированные интерфейсы, и вы не сможете их использовать.
  • Птенцы копают это : Эй, я просто хватаюсь за соломинку здесь ...

Давайте посмотрим, как может работать объектно-ориентированный подход. Для начала посмотрим на основную программу:

use CGI;
use DBI;
use My::Module;

my $q = new CGI;
my $dbh = some_connenction_function($dsn);
my %conf = ( Foo => 1, Bar => 2, Random => some_dynamic_data() );

my $mod_handle = My::Module->new (
   q    => $q,
   dbh  => $dbh,
   conf => \%conf,
);

$mod_handle->someFunction;

Выше я создаю object instance, который содержит эти переменные. И, волшебным образом, я изменил ваши Функции на Методы . Метод - это просто функция в вашем Class (он же модуль). Хитрость в том, что мой экземпляр (переменная $mod_handler содержит все необходимые переменные, хранящиеся в хорошем и удобном для вас виде. Синтаксис $mod_hander-> просто передает эту информацию для моих функций Я имею в виду методы.

Итак, как теперь выглядит ваш модуль? Давайте посмотрим на первую часть, где у меня есть Constructor , который является просто функцией, которая создает хранилище для моих переменных, которые мне нужны:

sub new {
   my $class = shift;
   my %param = @_;

   my $self = {};
   bless $self, $class

  $self->{Q} = $q;
  $self->{DBH} = $dbh;
  $self->{CONF} = $conf;

  return $self;
}

Давайте рассмотрим первое, что немного отличается: my $class = shift;. Откуда это? Когда я вызываю функцию с синтаксисом Foo->Bar, я передаю Foo в качестве первого параметра в функции Bar. Таким образом, $class равно My::Module. Это так же, как если бы я назвал вашу функцию следующим образом:

my $mod_handle = My::Module::new("My::Module", %params);

вместо:

my $mod_handle = My::Module->new(%params);

Следующая вещь - это строка my $self = {};. Это создает ссылку на хеш. Если вы не понимаете ссылки, вам следует обратиться к Справочному руководству Марка , которое включено в Perldocs. По сути, ссылка - это область памяти, где хранятся данные. В этом случае у моего хэша нет имени, все, что у меня есть, это ссылка на память, в которой он хранится, и называется $self. В Perl нет ничего особенного в названии new или $self, но это стандарты, которым все в значительной степени следуют.

Команда bless берет мою ссылку $self и объявляет ее типом My::Module. Таким образом, Perl может отслеживать, является ли $mod_handle типом экземпляра , который имеет доступ к этим функциям.

Как видите, ссылка $self содержит все переменные, которые нужны моим функциям. И я удобно передаю это обратно в мою основную программу, где храню ее в $mod_handle.

Теперь давайте посмотрим на мои методы :

sub SomeFunction {
    $self = shift;

    my $dbh = $self->{DBH};
    my $q = $self->{Q};
    my %conf = %{self->{CONF}};

    @data = $dbh->selectrow_array(
       "SELECT * FROM Foo WHERE Bar = ?", undef, $conf{Bar} 
    );
    return $param{q}->p( "@data" );
}

Опять эта строка $self = shift;. Помните, я называю это как:

 $mod_handle->SomeFunction;

То же самое, что называть это:

 My::Module::SomeFunction($mod_handle);

Таким образом, значение $self является ссылкой на хэш, которую я хранил в $mod_handle. И эта ссылка на хеш содержит три значения, которые я всегда передаю этой функции.


Заключение

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

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

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

И я упоминал, что цыплята копали это?

Adium

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

Я сделал это таким образом, чтобы сделать его простым и сделать немного более очевидным, чем я занимался. Однако, как скажет вам любой хороший объектно-ориентированный программист (и второстепенные хаки, как я), вы должны использовать функции setter / getter для установки значений вашего члена.

Функция установки довольно проста. Шаблон выглядит так:

sub My_Method {
   my $self =  shift;
   my $value = shift;

   # Something here to verify $value is valid

   if (defined $value) {
       $self->{VALUE} = $value;
   }
   return $self->{VALUE};
}

Если я вызову $instance->My_Method("This is my value"); в моей программе, для $self->{VALUE} будет установлено значение This is my value. В то же время он возвращает значение $self->{VALUE}.

Теперь, допустим, я все это так:

 my $value = $instance->My_Method;

Мой параметр $value равен undefined , поэтому я не устанавливаю значение $self->{VALUE}. Тем не менее, я все равно возвращаю значение.

Таким образом, я могу использовать тот же метод для установки и получения моего значения.

Давайте посмотрим на мой Конструктор (это причудливое название для этой new функции):

sub new {
   my $class = shift;
   my %param = @_;

   my $self = {};
   bless $self, $class

  $self->{Q} = $q;
  $self->{DBH} = $dbh;
  $self->{CONF} = $conf;

  return $self;
}

Вместо установки ссылки на хеш $self->{} непосредственно в этой программе, хороший дизайн сказал, что я должен был использовать функции получения / установки как это:

sub new {
   my $class = shift;
   my %param = @_;

   my $self = {};
   bless $self, $class

  $self->Q = $q;       #This line changed
  $self->Dbh = $dbh;   #This line changed
  $self->Conf = $conf; #This line changed

  return $self;
}

Теперь мне нужно определить эти три подпрограммы, Q, Dbh и Conf, но теперь мой метод SomeFunction исходит из этого:

sub SomeFunction {
    $self = shift;

    my $dbh = $self->{DBH};
    my $q = $self->{Q};
    my %conf = %{self->{CONF}};

    @data = $dbh->selectrow_array(
       "SELECT * FROM Foo WHERE Bar = ?", undef, $conf{Bar} 
    );
    return $param{q}->p( "@data" );
}

На это:

sub SomeFunction {
    $self = shift;

    my $dbh = $self->Dbh;  #This line changed
    my $q = $self->Q;      #This line changed
    my %conf = %{self->Conf};   #This line changed

    @data = $dbh->selectrow_array(
       "SELECT * FROM Foo WHERE Bar = ?", undef, $conf{Bar} 
    );
    return $param{q}->p( "@data" );
}

Изменения неуловимы, но важны. Теперь моя new функция и моя SomeFunction понятия не имеют, как эти параметры хранятся. Единственное место, которое знает, как они хранятся, - это сама функция получения / установки. Если я изменяю структуру данных класса, мне не нужно ничего изменять, кроме самих функций получения / установки.

Постскриптум

Вот пища для размышлений ...

Если все ваши вызовы SQL находятся в вашей функции My :: Module, почему бы просто не инициализировать $dbh и $q там в первую очередь. Таким образом, вам не нужно включать модуль Use Dbi; в вашу программу. Фактически, ваша программа теперь остается в блаженном неведении, как именно хранятся данные. Вы не знаете, является ли это базой данных SQL, базой данных Mongo или даже какой-то плоской структурой ББли в стиле Беркли.

Я включаю модуль Я давно выполнил работу, где пытался упростить использование нашей базы данных. Этот модуль сделал несколько вещей:

  • Все обработано в базе данных. Инициализация и все ручки. Таким образом, вашей основной программе не нужно было использовать какой-либо модуль, кроме этого.
  • Он также отошел от разработчика, пишущего операторы select. Вместо этого вы определили, какие поля вам нужны, и выяснили, как сделать запрос за вас.
  • Возвращает инкриминаторную функцию , которая используется для извлечения следующей строки из базы данных.

Посмотрите на это. Это не самое лучшее, но вы увидите, как я использую объектно-ориентированный дизайн для устранения всех проблем, которые у вас возникают. Чтобы просмотреть документацию, просто введите perldoc MFX:Cmdata в командной строке.

2 голосов
/ 02 декабря 2011

Одним из более чистых способов является использование Exporter, чтобы сделать символы из других пространств имен доступными в вашем пакете.Если вы просто случайный программист на Perl, я бы не стал беспокоиться об этом, потому что в нем есть некоторая кривая обучения, есть много ошибок, и для игрушечного проекта это делает код еще более сложным.Но это важно для больших проектов и для понимания модулей других людей.

Обычно в пакете есть глобальные переменные (или особенно глобальные функции), которые вы хотите использовать в другом модуле, не квалифицируя его (т.е.добавляя package:: к каждой переменной и вызову функции).Вот один из способов, с помощью которого Exporter может быть использован для этого:

# MyModule.pm
package MyModule;
use strict; use warnings;
use Exporter;
our @EXPORT = qw($variable function);

our $variable = 42; # we want this var to be used somewhere else
sub function { return 19 };  # and want to call this function
...
1;

# my_script.pl
use MyModule;
$variable = 16;   # refers to $MyModule::variable
my $f = function();  # refers to &MyModule::function

Ваша спецификация проблемы задом наперед (не то, что с этим обязательно что-то не так) - вам нужны переменные в основном скрипте и основном пакетебыть видимым внутри другого модуля.Для краткого списка переменных / функций, которые используются много раз, более чистый способ сделать это может быть взломать таблицу символов:

package My::Module;

# required if you  use strict . You do  use strict , don't you?
our ($q, $dbh, %conf);

*q = \$main::q;
*dbh = \$main::dbh;
*conf = \%main::conf;
...

Теперь $My::Module::q и $main::q относятся к тому жепеременная, и вы можете просто использовать $q в пространстве имен main или My::Module.

2 голосов
/ 02 декабря 2011
  1. Использование main :: явно идеально чисто

  2. В качестве альтернативы вы можете передать эти данные в метод конструктора модуля, предполагая, что ваш модуль основан на объектах (или скопируйте его в собственные переменные модуля из main :: в конструкторе).Таким образом, не совсем изящный main :: скрыт от остальной части модуля.

...