Perl - вызов конструктора подкласса из суперкласса (ОО) - PullRequest
8 голосов
/ 05 мая 2010

Это может оказаться смущающе глупым вопросом, но лучше, чем потенциально создать смущающе глупый код. :-) Это действительно вопрос дизайна ОО.

Допустим, у меня есть объектный класс 'Foos', представляющий набор динамических элементов конфигурации, которые получаются путем запроса команды на диске 'mycrazyfoos -getconfig'. Скажем, есть две категории поведения, которые я хочу, чтобы объекты 'Foos' имели:

  • Существующие: во-первых, запрашивайте те, которые существуют в выводе команды, о котором я только что упомянул (/ usr / bin / mycrazyfoos -getconfig`. Вносите изменения в существующие с помощью команд оболочки.

  • Создание новых, которые не существуют; новые 'crazyfoos', использующие сложный набор /usr/bin/mycrazyfoos команд и параметров. Здесь я на самом деле не просто запрашиваю, а на самом деле выполняю кучу команд system (). Влиять на изменения.

Вот моя структура классов:

Foos.pm

пакет Foos, который имеет новый конструктор ($ hashref -> {name => 'myfooname',), который принимает 'crazyfoo NAME' и затем запрашивает существование этого NAME, чтобы увидеть, существует ли оно уже (путем выделения и запуск команды mycrazyfoos выше). Если этот crazyfoo уже существует, верните объект Foos :: Existing. Любые изменения в этом объекте требуют обстрела, запуска команд и получения подтверждения того, что все в порядке.

Если это путь, то конструктор new () должен иметь тест, чтобы увидеть, какой конструктор подкласса использовать (если это даже имеет смысл в этом контексте). Вот подклассы:

Foos / Existing.pm

Как упоминалось выше, это для случая, когда объект Foos уже существует.

Foos / Pending.pm

Это объект, который будет создан, если, как указано выше, 'crazyfoo NAME' на самом деле не существует. В этом случае вышеописанный конструктор new () будет проверен на наличие дополнительных параметров, и он будет продолжен, и при вызове с помощью -> create () будет выложен с помощью system () и создаст новый объект ... возможно, возвращая ' Существующий 'один ...

ИЛИ

Набирая это, я понимаю, что, возможно, лучше иметь один:

(альтернативное расположение)

класс Foos, который имеет

-> new (), который принимает только имя

-> create (), который принимает дополнительные параметры создания

-> delete (), -> change () и другие параметры, которые влияют на существующие; это нужно будет просто динамически проверять.

Итак, мы здесь, два основных направления в этом направлении. Мне любопытно, что было бы более разумным путем.

Ответы [ 4 ]

4 голосов
/ 07 мая 2010

Для суперкласса вообще плохо знать о своих подклассах, принцип, который распространяется на конструкцию. [1] Если вам нужно решить во время выполнения, какой тип объекта создать (и вы это делаете), создайте четвертый класс, чтобы иметь именно эту работу. Это один из видов "фабрики".

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

Так как же это предложение [3]: сделать Foos::Exists и Foos::Pending два отдельных и несвязанных класса и предоставить (в Foos) метод, который возвращает соответствующий. Не называй это new; вы не делаете новый Foos.

Если вы хотите объединить интерфейсов, чтобы клиенты не знали, о каком виде они говорят, тогда мы можем поговорить о подклассах (или, что еще лучше, о делегировании лениво созданным и -обновлено Foos::Handle).

[1]: Объяснение, почему это так, является предметом, достаточно здоровенным для книги [2], но краткий ответ заключается в том, что он создает цикл зависимости между подклассом (который по определению зависит от его суперкласса) и суперклассом (который зависит от своего подкласса из-за плохого проектного решения).
[2]: Лакос, Джон. (1996). Крупномасштабный дизайн программного обеспечения C ++ . Addison-Wesley.
[3]: Не рекомендуется, так как я не могу достаточно хорошо разобраться с вашими требованиями, чтобы быть уверенным, что я не стреляю в рыбу в темном океане.

4 голосов
/ 05 мая 2010

Как правило, ошибка (по дизайну, а не по синтаксису) в методе new - возвращать что-либо, кроме нового объекта. Если вы хотите иногда возвращать существующий объект, вызывайте этот метод как-нибудь еще, например, new_from_cache().

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

package Foos;
use strict;
use warnings;

sub new
{
    my ($class, %args) = @_;

    if ($args{name})
    {
        # handle the name => value option
    }

    if ($args{some_other_option})
    {
        # ...
    }

    my $this = {
        # fill in any fields you need...
    };

    return bless $this, $class;
}

sub new_from_cache
{
    my ($class, %args) = @_;

    # check if the object already exists...

    # if not, create a new object
    return $class->new(%args);
}

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

2 голосов
/ 06 мая 2010

Это также фабричный шаблон (плохо в Perl), если конструктор объекта вернет экземпляр, благословленный более чем в одном пакете.

Я бы создал что-то подобное. Если существует names, то для is_created устанавливается значение 1, в противном случае устанавливается значение 0 .. Я бы слил ::Pending и ::Existing вместе, и если объект не был создан, просто поместите это default для _object, проверка происходит лениво. Кроме того, Foo-> delete () и Foo-> change () будут привязаны к экземпляру в _object.

package Foo;
use Moose;

has 'name' => ( is => 'ro', isa => 'Str', required => 1 );
has 'is_created' => (
    is => 'ro'
    , isa => 'Bool'
    , init_arg => undef
    , default => sub {
        stuff_if_exists ? 1 : 0
    }
);

has '_object' => (
    isa => 'Object'
    , is => 'ro'
    , lazy => 1
    , init_arg => undef
    , default => sub {
        my $self = shift;
        $self->is_created
            ? Foo->new
            : Bar->new
    }
    , handles => [qw/delete change/]
);
1 голос
/ 08 мая 2010

Интересные ответы! Я перевариваю это, пробуя разные вещи в коде.

Ну, у меня есть другой вариант того же вопроса - тот же вопрос, заметьте, просто другая проблема того же класса: проблема создания подкласса!

На этот раз:

Этот код представляет собой интерфейс для командной строки, который имеет ряд различных сложных параметров. Я говорил вам о /usr/bin/mycrazyfoos раньше, верно? Что ж, если я скажу вам, что этот двоичный файл изменяется в зависимости от версии, а иногда он полностью меняет свои базовые параметры. И что этот класс мы пишем, он должен быть в состоянии объяснить все эти вещи. Цель (или, возможно, идея) состоит в том, чтобы сделать: (возможно, названный ИЗ класса Foos, который мы обсуждали выше):

Foos :: Commandline, которая имеет в качестве подклассов различные версии базовой команды '/ usr / bin / mycrazyfoos'.

Пример:

 my $fcommandobj = new Foos::Commandline;
 my @raw_output_list = $fcommandobj->getlist();
 my $result_dance    = $fcommandobj->dance();

где 'getlist' и 'dance' зависят от версии. Я думал об этом:

 package Foos::Commandline;

 new (
    #Figure out some clever way to decide what version user has
    # (automagically)

    # And call appropriate subclass? Wait, you all are telling me this is bad OO:

    # if v1.0.1 (new Foos::Commandline::v1.0.1.....
    # else if v1.2 (new Foos::Commandline::v1.2....
    #etc

 }

тогда

 package Foos::Commandline::v1.0.1;

 sub getlist ( eval... system ("/usr/bin/mycrazyfoos", "-getlistbaby"
  # etc etc

и (разные файлы .pm в поддиректории Foos / Commandline)

 package Foos::Commandline::v1.2;

 sub getlist ( eval...  system ("/usr/bin/mycrazyfoos", "-getlistohyeahrightheh"
  #etc

Имеет смысл? Я выразил в коде то, что я хотел бы сделать, но это просто нехорошо, особенно в свете того, что обсуждалось в ответах выше. Что действительно правильно, так это то, что должен быть общий интерфейс / суперкласс для командной строки ... и что разные версии должны иметь возможность переопределять его. Правильно? Был бы признателен за предложение или два на этом. Gracias.

...