Я обсуждал проблему стиля кода с другом. У нас есть серия пакетов, которые реализуют интерфейс, возвращая определенный тип значения через именованную подпрограмму. Например:
package Foo::Type::Bar;
sub generate_foo {
# about 5-100 lines of code
return stuff here;
}
Итак, вы можете пойти:
my $bar_foo = Foo::Type::Bar->generate_foo;
my $baz_foo = Foo::Type::Baz->generate_foo;
У нас их много, и все они находятся в одной иерархии Foo::Type::*.
Я думаю, что в пакетах должно быть четко указано, что они реализуют интерфейс foo_generate, например:
package Foo::Type::Bar;
use base 'Foo::Type';
sub generate_foo {
...
return stuff here;
}
Я думаю, что это хороший стиль кода, гораздо более понятный и понятный для других программистов, изучающих код. Он также позволяет вам проверить Foo::Type::Bar->isa('Foo::Type'), чтобы увидеть, реализует ли он интерфейс (другие части подсистемы полностью OO).
Мой друг не согласен. Он приводит следующие аргументы:
Foo::Type::* четко названы и используются только во внутреннем проекте, поэтому нет никаких сомнений в том, реализует ли данный пакет интерфейс.Foo::Type не добавляет никакого значения, поскольку это будет буквально пустой пакет, используемый только для включения поиска по ->isaОдин из нас «прав»? Чтобы ты делал?
Обновлено: в примерах переименован Foo :: Generator в Foo :: Type





На мой взгляд, это зависит от масштаба и очень субъективно. Чем больше становится система, тем выгоднее накапливать вокруг нее метаданные. Если вы ожидаете, что он будет расти, возможно, эти сценарии использования изменятся, и на их реализацию стоит потратить время. Но perl обычно ориентирован на прагматичное программирование, кодируя только то, что нужно, и ничего более. В вашем случае, зная то немногое, что я знаю, я бы посоветовал оставить все как есть. Если бы это была java, я бы склонился к другому направлению, поскольку java с самого начала намного больше выигрывает от использования интерфейса.
В соответствии с прагматическим духом Perl, я бы добавил базовый класс только в том случае, если он был ценен сам по себе. Добавление набора пустых функций не так уж и весело.
С другой стороны, базовый класс может быть полезен как место для хранения вашего POD. А для определения констант ... У вас есть Foo :: Type.pm? Если вы это сделаете и обсудите там generate_foo, тогда в Generator.pm нет необходимости ...
Да, это призыв к суждению.
Вообще говоря, если у вас есть «пустой» уровень в иерархии наследования, либо в нем должно быть что-то, что вы еще не исключили, либо, если действительно нечего добавить, его не должно быть на все. Я думаю, что это источник дискомфорта вашего друга по поводу класса Foo::Type - нет никаких причин, по которым там присутствует имеет; это просто является, как вы сейчас описываете код.
Уровни выше первого в вашей иерархии классов должны служить для различения нескольких вещей на этом уровне. В противном случае люди будут искать причину существования уровня, даже если его нет: я мог бы предложить фактически полностью исключить средний уровень. Тот факт, что у него есть семантически нулевое имя (Type), указывает на то, что он на самом деле не нужен. Возможно, в вашем представлении о том, что это за объекты и что они должны делать, есть несоответствие с вашим соглашением об именах.
Я могу утверждать, что
Foo::Bar->generate
Foo::Baz->generate
имеет больше смысла, если возвращаемые объекты больше зависят от их принадлежности к Bar или Baz для их описания, чем от их принадлежности к Type.
Действительно хороший способ оценить такие вещи - это сказать себе: «Могу ли я разместить это на CPAN таким образом? Хотел бы я (потенциально), чтобы любой программист Perl в мире увидел это?» Если вас это не устраивает (совершенно пустой класс? Хммм), вам следует сделать шаг назад и пересмотреть свое решение.
Ну, есть «утиная печать», которую используют другие динамические языки. UNIVERSAL::can достигает этого в той степени, в которой другие языки склонны использовать это.
$unknown->doodle() if $unknown->can( 'doodle' );
Название «утка» происходит от идеи, что «если она ходит как утка и говорит как утка ... это утка». Однако мы не наблюдаем, ходит ли он «как утка», мы просто наблюдаем, делает ли он что-то, что кто-то назвал «ходьбой», потому что это то, что мы называем тем же, что и утка. Например, объект Tree Walker не "ходить" как утка!
Вы бы не захотели передать не тот объект определенные вещи.
$path_or_OS_I_dont_know_which->format( "c:", 'y' );
Итак, насколько хорошо работает утиная печать, зависит от ваших ожиданий с другой стороны. К тому же, если контракт более сложный, это может стать беспорядочным. Вы просто умножаете неопределенность - хотя сужаете объекты за счет того, что два класса случайно будут иметь 23 метода с одинаковыми именами.
if ( $duck->can( 'talk' ) && $duck->can( 'walk' )) {
$duck->walk();
$duck->talk();
}
это нормально, но
if ( $cand->can( 'walk' ) && $cand->can( 'talk' ) && ... && $cand->can( 'gargle' )) {
...
}
становится немного глупо. Кроме того, некоторые из этих методов действительно могут быть по желанию, поэтому вам придется перемежать его с:
$cand->snicker() if $cand->can( 'snicker' );
(Конечно, перекрестки немного очистили бы это.)
И это может стать действительно сложным с промежуточными продуктами методов, которые вы должны передать другому методу, который также может быть там.
У меня есть легкий шаблон, который я называю «Совместимый». Фактического пакета с именем this нет, но если класс реализует то, что делает Date, он помещает его в @ISA;
our @ISA = qw<... Date::Compatible ...>;
Таким образом, есть только два isa, которые нужно вызвать - и я все равно инкапсулирую его в подменю is_one:
sub UNIVERSAL::is_one {
my ( $self, @args ) = @_;
foreach my $pkg ( @args ) {
return 1 if $self->isa( $pkg );
}
return 0;
}
sub UNIVERSAL::is_compatible_with {
my ( $self, $class ) = @_;
return $self->is_one( $class, "${class}::Compatible" );
}
По крайней мере, он дает вещам договор для выполнения и для запроса кода для проверки. Реализация все еще может решить, насколько важно выполнить конкретный метод контракта, если она не придирчива.
Я действительно хочу знать, почему ты говоришь о
my $bar_foo = Foo::Type::Bar->generate_foo
скорее, чем
my $bar_foo = Foo::Type::Bar::generate_foo
В первом случае есть тонкая оболочка объектно-ориентированного подхода, которая предполагает, что на карте может быть какое-то объектно-ориентированное (объектно-ориентированное) наследование; но, глядя на возражения вашего коллеги против добавления явного наследования от пустого класса, кажется довольно очевидным, что generate_foo вызывается не-объектно-ориентированным способом и что никакого наследования не ожидается.
Имея это в виду, никто никогда не будет печатать
if (Foo::Type::Bar->isa('Foo::Type')) { ... }
непосредственно перед
my $bar_foo = Foo::Type::Bar::generate_foo;
поскольку они могут довольно быстро определить, есть ли в Foo :: Type :: Bar функцию generate_foo, и определят это при написании своего кода. Похоже, вы ищете какие-то утверждения «мы делаем правильные вещи», хотя на самом деле, если какой-то фрагмент кода ошибается в этом утверждении, он вылетает при первом запуске.
Похоже, вы имеете дело с не-объектно-ориентированным кодом, и в этом случае я бы не стал применять стандартные объектно-ориентированные методы к проблеме. Возможно, посмотрите Статья Джоэла Спольски о том, почему венгерская нотация может быть хорошей, сделанная правильно. Если вы сделаете очевидным, правильный или неправильный код, вам не нужно беспокоиться о том, чтобы держать в руках или обеспечивать соблюдение мало необходимых стандартов.
Я думаю, вам следует перейти на Moose, если вы задаете эти вопросы. Там вы сможете определить свой интерфейс, создав роль или класс с соответствующими абстрактными методами.
Я согласен с вами, что это может добавить ценную информацию. В Java существует концепция «интерфейса маркера», который представляет собой пустой интерфейс, не имеющий методов, который существует только для того, чтобы пометить набор классов как полезный для определенной цели. Самый распространенный пример этого - интерфейс Serializable.
Конечно, то, что это может быть полезно, не означает, что это будет в вашей конкретной ситуации. :)