Perlでちょっとしたデータ読み取りの実装で、ファイルでもメモリ上に展開されたデータでも使える汎用readerがほしくなったので、
オブジェクト指向を使って頑張って書いてみました。
仕様
- 読み取り専用。
- ファイルと変数にあるデータを、1行(readline)もしくは任意長(read)読み取れる。
- seekは甘え
- 返り値で、読み取ったデータを返す
- うまくいかないときはdieする。
思いつき
階層構造
- MyReaderを使うプログラム(main.pl)
- MyReader.pm
- Data.pm
- File.pm
MyReader.pm
package MyReader; sub new{ my $class = shift; my $self = {}; # メンバ変数(無し) return bless($self,$class); } ### メンバ関数(一応定義していない事を示しておく) sub readline{ die "Unimplemented 'readline'"; } sub read{ die "Unimplemented 'readline'"; } ### セレクタ use MyReader::Data; use MyReader::File; sub Choose{ my $data = shift; if(not defined $data){ die "Not selected data"; } my $ref = ref $data; # data if($ref eq "SCALAR"){ return new MyReader::Data($data); } # file if($ref eq 'GLOB' || !$ref){ return new MyReader::File($data); } # not support die "Not supported data"; return undef; } 1;
MyReader/Data.pm
(10/21 追記)
コードが間違ってました。修正しておきます。
package MyReader::Data; use MyReader; use base qw(MyReader); sub new{ my $class = shift; my $self = new MyReader; $self->{offset} = 0; $self->{data} = $_[0]; # read only!! return bless($self,$class); } sub readline{ my $self = shift; my $pos = index(${$self->{data}}, "\n", $self->{offset}); my $ret=''; if($pos == -1){ $ret = substr(${$self->{data}}, $self->{offset}); }else{ $ret = substr(${$self->{data}}, $self->{offset}, $pos+1 - $self->{offset}); } $self->{offset} += length($ret); return $ret ? $ret : undef; } sub read{ my $self = shift; my $size = shift; my $ret = substr(${$self->{data}}, $self->{offset}, $size); $self->{offset} += length($ret); return $ret; } 1;
MyReader/File.pm
package MyReader::File; use MyReader; use base qw(MyReader); sub new{ my $class = shift; my $arg = shift; my $self = new MyReader; $self->{fh} = undef; if(!ref $arg){ if(-f $arg){ die "Not found this file."; }elsif(!open($self->{fh}, "<", "$arg")){ die "Can't open this file."; } }elsif(ref $arg eq 'GLOB'){ $self->{fh} = $arg; } return bless($self,$class); } sub readline{ my $self = shift; return readline($self->{fh}); } sub read{ my $self = shift; my $size = shift; read(my $ret, $size, $self->{fh}); return $ret; } 1;
main.pl
use strict; use MyReader; my $data = <<'__EOD__'; あいうえお かきくけこ __EOD__ my $reader = MyReader::Choose(\$data); while(my $line = $reader->readline()){ print "$line"; } $reader = MyReader::Choose(\*DATA); while(my $line = $reader->readline()){ print "$line"; } __DATA__ さしすせそ たちつてと
あいうえお
かきくけこ
さしすせそ
たちつてと
めも
- しっかりReaderを選ぶ必要があるが、使うだけなら同じように使える。
- MyReader::Choose関数はMyReader::Data.pmとMyReader::File.pmに依存した処理なので、他に種類の追加があった場合はここも変更しないといけない。なので、こういった関数は作るべきではない。作るならmain.plか。
- PerlでのJavaのinterface/abstractやC++の純粋仮想関数みたいな定義の仕方がわからなかったので、処理を定義していない関数をスーパクラスに作ってる。
- なので、種類を追加した場合、read/readlineが定義されてないのに呼び出すと、スーパクラスのread/readlineが呼び出されるのでdieされるが、呼び出さなければそのまま動いてしまう。
- blessの第2引数は省略でき、最小限なコンストラクタはsub new{bless({});}という感じでもいける。