PerlでCompositeパターン


スタバにて
コード書き書き
梅雨の明け

どもー。
やましもです。

今日はCompositeパターン。
このパターンはLeafとComposite(Leafの集合)の処理の差異がない場合に
適用可能性があるパターンです。
よく言われるのがファイルとフォルダを上げた具体例ですかね。
ファイルの名前を変えるのとフォルダの名前を変えること、
ファイルの削除とフォルダの削除。
こういった共通のオペレーションをユーザーに枝なのか葉はなのかを
意識させずにコーディングしたい場合に適用出来ます。

今回の実装例としては擬似ファイルを扱うことで実装しました。
#擬似ファイル=実際にファイルやフォルダは作成しないが、オブジェクトとして作成する

コードはこの辺。
Github ka-yamashita

ファイル構成はディレクトリとファイルをか使うクラス一式がDirectoryFile.pm
ユーザーコードがComposite.plです。

早速中身を見て行きましょう。
まずはComposite.plから。
[Perl]
use utf8;
#——————————————–
# Name:DirFile.pm
#——————————————–
package DirectoryFile;
use Moose::Role;
requires qw(list remove);
has ‘target’ => (
is => ‘rw’,
required => 1,
);
no Moose::Role;

package File;
my $self = shift;
use Moose;
with ‘DirectoryFile’;
__PACKAGE__->meta->make_immutable();
no Moose;

sub list {
my $self = shift;
print “/” . $self->target() . “\n”;
}
sub remove {
my $self = shift;
print “/” , $self->target() , ” at removed\n”;
}
package Directory;
use Moose;
use MooseX::AttributeHelpers;
with ‘DirectoryFile’;

#再帰的にオブジェクトを持てるようにする
has ‘DirFileObject’ => (
is => ‘rw’,
isa => ‘ArrayRef[DirectoryFile]’,
metaclass => ‘Collection::Array’,
auto_deref => 1,
provides => {
push => ‘add_object’
},
default => sub { [] },
);
__PACKAGE__->meta->make_immutable();

no Moose;

sub list {
my ($self,$parrent) = @_;

#フォルダ階層文字の結合
$parrent .= “/” . $self->target();

#自分自身の印字
print $parrent , “\n”;

#格納されているオブジェクトを全て取得する
for my $obj($self->DirFileObject()){
#ファイルじゃなければ再帰処理
unless($obj->isa(“File”)){
$obj->list($parrent);
}else{
print $parrent , “/” , $obj->target() , “\n”;
}
}
};
sub remove{
my ($self,$parrent) = @_;

#フォルダ階層文字の結合
$parrent .= “/” . $self->target();

#格納されているオブジェクトを全て取得する
for my $obj($self->DirFileObject()){
#ファイルじゃなければ再帰処理
unless($obj->isa(“File”)){
$obj->remove($parrent);
}else{
print $parrent , “/” , $obj->target() , ” at removed\n”;
}
}
#自分自身の印字
print $parrent , ” at removed\n”;
};
[/Perl]

フォルダのコードはフォルダの中にフォルダが更にある可能性があるので、
list,remove共に再帰的な処理が出来るように書いています。
対してファイルの方は単純に表示、削除するだけですね。
フォルダとファイルの関係性を現すアトリビュートとして、DirFileObjectという
アトリビュートを準備しており、
[Perl]
has ‘DirFileObject’ => (
is => ‘rw’,
isa => ‘ArrayRef[DirectoryFile]’,
metaclass => ‘Collection::Array’,
auto_deref => 1,
provides => {
push => ‘add_object’
},
default => sub { [] },
);
[/Perl]
DirectoryFileクラスの配列を格納できるようにしている感じですね。

次にユーザーコードを見て行きましょう。

[Perl]
use utf8;
#——————————————-
# Name:Composite.pl
#——————————————-
use strict;
use warnings;
use Composition::DirectoryFile;

#ファイルAの作成
my $fileA=File->new( target=>’FileA’);

#ディレクトリAの作成
my $dirA=Directory->new(target=>’DirA’);

#ディレクトリAの中にファイルBの作成
$dirA->add_object(File->new( target=>’FileB’));

#ディレクトリBの作成
my $dirB=Directory->new(target=>’DirB’);

#ディレクトリBの中にディレクトリAを格納
$dirB->add_object($dirA);

#オブジェクトリストの印字
print ” ——- get list ——-\n\n”;
$fileA->list;
$dirB->list;

#オブジェクトリストの削除
print “\n—— remove list ——\n\n”;
$fileA->remove();
$dirB->remove();
print “\n”;
[/Perl]

これはファイル構成として

/-FileA
-DirectoryB
|-DirectoryA
|-FileB

を作成し、その後、一覧の表示、削除を行う擬似コードです。
それを実行するとこんな感じですね。

[Shell]
[Composite]$perl Composite.pl
——- get list ——-

/FileA
/DirB
/DirB/DirA
/DirB/DirA/FileB

—— remove list ——

/FileA at removed
/DirB/DirA/FileB at removed
/DirB/DirA at removed
/DirB at removed

[/Shell]

実際にそれなりのアプリとなると再帰関係になることって
割りとあるので結構適用することが多いのかなという感触を受けました。

あと全然関係ないのですがフットサルのスケジュール調整アプリ、
そろそろ商用化に向けて再設計はじめなきゃなーと思いつつ、
ruby学び直す前に早くPerl一区切り付けたい今日このごろ。