Инкапсуляция данных в Perl? - PullRequest
9 голосов
/ 24 марта 2011

Привет сообщество Perl на SO.Я использую Perl уже несколько лет, но поскольку я слежу за SO, я понял, что недостаточно знаю Perl.

За последние 4 года я написал довольно большой сценарий и попытался сделать это в стиле OO,Я знаю, что Perl <6 на самом деле не OO. </p>

Так что один момент, который мне не нравится, это то, что у меня нет инкапсуляции данных, что означает отсутствие переменных, которые действительно являются частными для пакета («класс»)или, может быть, я не знаю, как это сделать).

У меня есть что-то вроде этого (только небольшая часть моего сценария)

package TAG;

sub new () {
    my $classname = shift;
    my $self      = {};

    bless( $self, $classname );
    $self->initialize();
    return $self;
}

sub initialize() {
    my $self = shift;

    # Only an example, I have a long list of items in this "class"
    $self->{ID}          = "NA"; 
}

sub setID() {
    ...
}

sub getID() {
    ...
}

В моем основном сценарии я использую еготогда вот так:

my $CurrentItem;
$CurrentItem = new TAG();

$CurrentItem->getID()

но

$CurrentItem->{ID} = "Something";

тоже работает, но я бы предпочел, чтобы это было невозможно.

Есть ли способ получитьЛучшая инкапсуляция данных, которые я использую в «классе», так что я (или другие пользователи) вынуждены использовать методы get и set?

Ответы [ 5 ]

12 голосов
/ 24 марта 2011

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

В этой статье обсуждаются такие параметры, как замыкания, скаляры и хеш ограниченного доступа с использованием Tie :: SecureHash, предпочтение отдается последнему подходу.

В этом блоге утверждается, что в perl бывают случаи, когда необходимо нарушать инкапсуляцию, хотя комментарии приводят к некоторым негативам.

Вы также можете посмотреть в лось для ваших объектов Perl 5. Он написан для поощрения использования инкапсулированных объектов .

6 голосов
/ 24 марта 2011

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

  • Языковая конструкция, которая облегчает связывание данных с методами (или другими функциями), работающими с этими данными.

Хорошая часть статьи - "Сокрытие информации". Perl допускает тип ОО, который скрывает сложность до тех пор, пока вы не смотрите на это слишком усердно. Я использую инкапсуляцию все время в Perl. Есть так много проблем, которые я решаю один раз и использую снова и снова, с мыслью, что до тех пор, пока взаимодействующие классы не «достигнут», поведение должно быть ожидаемым.

Большинство динамических языков достигли многого с манерами , которые более тяжелые языки достигают с помощью безопасной инкапсуляции. Но, тем не менее, Perl позволяет вам присвоить поведение любому типу ссылки , который вы хотите, и объекты Inside-Out , вероятно, так же безопасны в Perl, как и любая другая форма инкапсуляции - хотя и более рутины, но это просто дает вам случай, когда вы выбрали компромисс между безопасностью и деталями для классов, которые в этом нуждаются.

5 голосов
/ 24 марта 2011

Вы можете попробовать Moose (или Mouse или Any :: Moose ).

package TAG;
use Moose;

has ID => (
  reader => 'getID', # this is the only one that is needed
  writer => 'setID',
  predicate => 'hasID',
  clearer => 'clearID',
  isa => 'Str', # this makes setID smarter
  required => 1, # forces it to be included in new
);

has name => (
  is => 'ro', # same as reader => 'name',
  required => 1, # forces it to be included in new
);

# Notice that you don't have to make your own constructor.
# In fact, if you did, you would effectively break Moose
# so don't do that.

Хотя это на самом деле не мешает TAG->{ID} доступу, но делает несколько вещей для вас изнутри.

Что примерно эквивалентно:

package TAG;
use strict;
use warnings;

sub getID{
  my($self) = @_;
  return $self->{ID};
}

sub setID{
  my($self,$new_id) = @_;

  # die if $new_id is anything other than a string
  # ( the one produced by Moose is smarter )
  die if ref $new_id;
  die unless defined $new_id;

  $self->{ID} = $new_id
}

sub hasID{
  my($self) = @_;
  return exists $self->{ID}
}

sub clearID{
  my($self) = @_;
  delete $self->{ID}
}

sub name{
  my($self) = @_;
 return $self->{name}
}

# The constructor provided by Moose is vastly superior
# to the one shown here.
sub new{
  my($class,%opt) = @_;

  my $self = bless {}, $class;

  die unless exists $opt{ID}; # this was the required => 1, from above
  $self->setID($opt{ID});

  die unless exists $opt{name}; # this was the required => 1, from above
  $self->{name} = $opt{name};
}

Существует теория, согласно которой число ошибок в данном фрагменте кода пропорционально количеству строк кода.

Если это так, то я бы сказал, что в версии Moose значительно меньше ошибок.

На самом деле, если бы я реализовал все, что Moose делает для вас, было бы более чем в два раза больше строк кода, чем сейчас.


Если вы действительно нуждаетесь в для предотвращения доступа $TAG->{ID}, вы можете использовать другой метакласс, все еще используя Moose . Не спрашивайте меня, как это сделать, я только недавно начал использовать Moose .

2 голосов
/ 24 марта 2011

Если вы хотите инкапсуляцию, почему вы нарушаете ее в своем конструкторе? Вместо этого, вы должны установить setID метод непосредственно.

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

Одним из самых простых является использование блокировки вашего объекта с помощью lock_hash. Таким образом, любые попытки изменить его внутренности приведут к фатальной ошибке.

Вы также можете использовать наизнанку объекты.

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

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;


my $f = Foo->new();

$f->bar(15);

print Dumper $f;

eval {
    $f->{bar} = 23;
    1;
} or do {
    print "Direct access error: $@";
};

print Dumper $f;

BEGIN {
    package Foo;
    use strict;
    use warnings;

    use overload '%{}' => '_HASH_DEREF';

    sub new {
        return bless {};
    }

    sub bar {
        my $self = shift;
        $self->{bar} = shift if @_;
        return $self->{bar};
    }

    sub _HASH_DEREF {
        my $caller = caller;

        die "Illegal access to object internals"
            unless $caller eq __PACKAGE__;

        return shift;
    }

}

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

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

Объекты Perl позволяют вам делать сумасшедшие вещи, которые вы хотите, на внутренней стороне, для обработки хранилища и / или инкапсуляции.

Если у вас есть команда, которая обманывает и напрямую получает доступ к объектам, то вам нужно решить социальную проблему. Техническое исправление МОЖЕТ помочь. Реальное исправление заключается в изменении поведения вашей команды разработчиков.

2 голосов
/ 24 марта 2011

Самое простое решение в perl - это просто префикс этой переменной с чем-то (то есть "_ID", или "private_ID", или что угодно), а затем не документировать это, потому что это не часть интерфейса.

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

...