オブジェクト指向

Perlでちょっとしたデータ読み取りの実装で、ファイルでもメモリ上に展開されたデータでも使える汎用readerがほしくなったので、
オブジェクト指向を使って頑張って書いてみました。


仕様

  • 読み取り専用。
  • ファイルと変数にあるデータを、1行(readline)もしくは任意長(read)読み取れる。
  • seekは甘え
  • 返り値で、読み取ったデータを返す
  • うまくいかないときはdieする。

思いつき

  • readerを意識して選択しないように自動的に選んでくれる関数をMyReaderへ作る
    • 引数の値をref関数で判断。'GLOB'もしくは空文字(文字列)なら、ファイルとする。SCALARなら変数データとして扱う。
    • さらに、ファイルのとき、ファイルハンドル(GLOB)を受け取ったらそのまま使い、ファイル名なら読み込んでファイルハンドルを得る。

階層構造

  • 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({});}という感じでもいける。