/
InsideOut.pod
2529 lines (1832 loc) · 74.8 KB
/
InsideOut.pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
=encoding euc-jp
=head1 名前
Object::InsideOut - インサイドアウトオブジェクト包括的支援モジュール
=head1 バージョン
この文書はObject::InsideOutバージョン1.52について記述しています。
=head1 概要
package My::Class; {
use Object::InsideOut;
# get+set兼用アクセサを持つ数値フィールド
my @data :Field('Accessor' => 'data', 'Type' => 'NUMERIC');
# 'DATA' (または'data'等) を->new()の必須引数とする
my %init_args :InitArgs = (
'DATA' => {
'Regex' => qr/^data$/i,
'Mandatory' => 1,
'Type' => 'NUMERIC',
},
);
# クラス固有の引数を->new()の一部として処理する
sub init :Init
{
my ($self, $args) = @_;
$self->set(\@data, $args->{'DATA'});
}
}
package My::Class::Sub; {
use Object::InsideOut qw(My::Class);
# 標準的なアクセサ'get_X'と'set_X'を持つリスト型フィールド
my @info :Field('Standard' => 'info', 'Type' => 'LIST');
# 'INFO'を->new()のリスト型オプション引数とする
# 値は自動的に@info配列に追加される
# [ 'empty' ] をデフォルトとする
my %init_args :InitArgs = (
'INFO' => {
'Type' => 'LIST',
'Field' => \@info,
'Default' => 'empty',
},
);
}
package Foo; {
use Object::InsideOut;
# オブジェクトを格納する兼用アクセサと
# オブジェクト生成時の自動パラメータ処理
my @foo :Field('All' => 'foo', 'Type' => 'My::Class');
}
package main;
my $obj = My::Class::Sub->new('Data' => 69);
my $info = $obj->get_info(); # [ 'empty' ]
my $data = $obj->data(); # 69
$obj->data(42);
$data = $obj->data(); # 42
$obj = My::Class::Sub->new('INFO' => 'help', 'DATA' => 86);
$data = $obj->data(); # 86
$info = $obj->get_info(); # [ 'help' ]
$obj->set_info(qw(foo bar baz));
$info = $obj->get_info(); # [ 'foo', 'bar', 'baz' ]
my $obj2 = Foo->new('foo' => $obj);
$obj2->foo()->data(); # 86
=head1 説明
このモジュールは、インサイドアウトオブジェクト (inside-out object) モデルを
使ったクラスを実装するための包括的な支援を提供します。
このモジュールはインサイドアウトオブジェクトを、無名スカラーリファレンスとして
実装します。スカラーはオブジェクトID (通常は連続した番号) を持ち、
そのリファレンスはクラスにblessされます。
Perl 5.8.3以降では、IDがI<偶発的に>変更される事を防ぐため、
スカラーリファレンスはB<読み取り専用>に設定されます。
オブジェクトデータ (つまりフィールド群) は、クラスパッケージ内の
配列またはハッシュに格納されます。
配列の場合はオブジェクトIDがインデックスとなり、
ハッシュの場合はオブジェクトIDがキーとなります。
インサイドアウトオブジェクトモデルの、I<blessされたハッシュ>に基づく
オブジェクトモデルに対する長所を詳細に絶賛することは他に譲ります。
L</"参考文献">に示すリンクを見てください。
インサイドアウトオブジェクトを簡単に説明すると、I<blessされたハッシュ>に基づく
オブジェクトに対して以下の長所があります:
=over
=item * カプセル化
オブジェクトデータはクラスのコードに囲まれており、クラスで定義された
インターフェースを介してのみアクセスできます。
=item * フィールド名の衝突回避
I<blessされたハッシュ>に基づくクラスを継承した場合、別々のクラスが
同じフィールド名 (つまりハッシュのキー) を使うと衝突を引き起こしてしまいます。
インサイドアウトオブジェクトは個々のクラスパッケージにオブジェクトデータを
格納し、オブジェクト自身はデータを持たないため、この問題に影響されません。
=item * コンパイル時フィールド名チェック
I<blessされたハッシュ> に基づくクラスを使う上での良くある間違いは、
フィールド名を書き間違えることです:
$obj->{'coment'} = 'Say what?'; # 'coment'ではなく、'comment'とするべき
ハッシュのキーはコンパイル時にチェックされないため、通常この様な誤りは
実行時まで明らかになりません。
インサイドアウトオブジェクトでは、フィールドデータへのアクセスに
I<文字列>ハッシュキーは用いません。
フィールド名とデータインデックス (つまり $$self) は Perl
コンパイラによりチェックされるため、タイプミスはS<C<perl -c>>を使って
簡単に発見できます。
$coment[$$self] = $value; # コンパイル時エラーが発生する
# または、ハッシュでフィールドを実装した場合
$comment{$$slef} = $value; # この場合もコンパイル時エラーが発生する
=back
このモジュールは他のインサイドアウトオブジェクトモジュールが持つ
全ての機能を提供する上、次の利点があります:
=over
=item * スピード
Object::InsideOutオブジェクトのデータ取得と設定のスピードは
I<blessされたハッシュ>に基づくオブジェクトと比べ、
データ格納に配列を使う場合で40%、データ格納にハッシュを使う場合でも
数%高速です。
=item * スレッド
Object::InsideOutはスレッドセーフです。L<threads::shared>を使う事で、
スレッド間での完全なオブジェクト共有をサポートします。
=item * 柔軟性
オブジェクトIDの定義方法、アクセサの命名方法、パラメータ名マッチング、
その他諸々をコントロールできます。
=item * 実行時サポート
実行時にロードされるクラス (つまり、 S<C<eval { require ...; };>> を使うこと)
をサポートします。同様に、 L<mod_perl>内での利用もサポートします。
その上、実行時にオブジェクトフィールドを動的に生成することもサポートします。
=item * Perl 5.6と5.8
Perl v5.6.0からv5.6.2、v5.8.0からv5.8.8、
およびv5.9.4でテストしています。
=item * 例外オブジェクト
Object::InsideOutは、L<Exception::Class>を使い、
オブジェクト指向と互換性のある作法でエラー処理します。
=item * オブジェクトのシリアル化
Object::InsideOutはオブジェクトのダンプとリロードのサポートを内蔵しています。
これは自動的、またはクラスが提供するサブルーチンによって遂行されます。
L<Storable>を用いたシリアル化もサポートしています。
=item * 外部クラスの継承
Object::InsideOutは外部 (つまりObject::InsideOutではない) クラスからの
継承を認めています。従って、他のPerlクラスからサブクラスを作り、
そのオブジェクトから親クラスのメソッドにアクセスすることができます。
=back
=head2 クラス宣言
このモジュールを使うために、あなたのクラスは S<C<use Object::InsideOut;>>
から始めて下さい:
package My::Class; {
use Object::InsideOut;
...
}
ベースクラスからサブクラスを派生する場合は、
親クラスの名前をObject::InsideOutに渡してください:
package My::Sub; {
use Object::InsideOut qw(My::Parent);
...
}
多重継承もサポートしています:
package My::Project; {
use Object::InsideOut qw(My::Class Another::Class);
...
}
Object::InsideOutは、C<base>プラグマの代わりに働きます:
親モジュール (群) をロードし、それらのC<import>関数を呼び、サブクラスの
@ISA配列をセットアップします。
そのため、あなた自身で S<C<use base ...>> としたり、
C<@ISA>配列をセットアップするべきではありません。
親クラスがパラメータを受け取る場合
(例えば、L<Exporter|/"C<Exporter>と共に使う">
によってエクスポートされるシンボル) は、
親クラス名の次を配列リファレンスとして、その中に列挙します:
package My::Project; {
use Object::InsideOut 'My::Class' => [ 'param1', 'param2' ],
'Another::Class' => [ 'param' ];
...
}
=head2 フィールド宣言
オブジェクトデータフィールドはクラスパッケージ内の複数の配列により構成し、
オブジェクトのIDを配列インデックスとして、これらの配列にデータを格納します。
オブジェクトフィールドとする配列は、後ろにC<:Field>属性を付けて宣言します:
my @info :Field;
オブジェクトデータフィールドは、ハッシュでも構いません:
my %data :Field;
ただし、配列へのアクセスはハッシュへのアクセスに比べて40%早いため、
配列を使う事にこだわるべきです。 (ハッシュが必要となる可能性がある
場合に関して、L</"オブジェクトID">を参照してください。)
(I<Field>という語は大文字小文字どちらでも構いませんが、慣例により、
全ての文字を小文字にするべきではありません。)
=head2 オブジェクト生成
オブジェクトはC<-E<gt>new()>メソッドを使って生成します。
C<-E<gt>new()>メソッドは、Object::InsideOutによって各クラスに
エクスポートされています:
my $obj = My::Class->new();
クラスは (通常) 自分自身のC<-E<gt>new()>メソッドを実装しません。
クラス固有の初期化作業は、C<:Init>ラベルを付けたメソッドで処理します
(L</"オブジェクトの初期化">を参照してください)。
パラメータは、 S<C<key =E<gt> value>> ペアとハッシュリファレンスの両方、
またはいずれかの組み合わせで渡します:
my $obj = My::Class->new('param1' => 'value1');
# または
my $obj = My::Class->new({'param1' => 'value1'});
# または、次の様にしても良い
my $obj = My::Class->new(
'param_X' => 'value_X',
'param_Y' => 'value_Y',
{
'param_A' => 'value_A',
'param_B' => 'value_B',
},
{
'param_Q' => 'value_Q',
},
);
更に、特定のクラスに渡すパラメータをハッシュリファレンスで分離することが
できます。
my $obj = My::Class->new(
'foo' => 'bar',
'My::Class' => { 'param' => 'value' },
'Parent::Class' => { 'data' => 'info' },
);
上記の例では、両方のクラスの初期化メソッドが
S<C<'foo' =E<gt> 'bar'>> を受け取ります。
C<My::Class>の初期化メソッドは S<C<'param' =E<gt> 'value'>> も受け取り、
C<Parent::Class>の初期化メソッドは S<C<'data' =E<gt> 'info'>> も受け取ります。
この仕掛けでは、特定のクラスに対するパラメータは、
それよりも高いレベルで指定された一般的なパラメータをオーバーライドします:
my $obj = My::Class->new(
'default' => 'bar',
'Parent::Class' => { 'default' => 'baz' },
);
C<My::Class>は S<C<'default' =E<gt> 'bar'>> を受け取り、
C<Parent::Class>は S<C<'default' =E<gt> 'baz'>> を受け取ります。
C<new>はオブジェクトに対して呼び出す事もでき、そのオブジェクトのクラスに
対して呼び出したのと同様に動作します (つまり、C<$obj-E<gt>new()>は
C<ref($obj)-E<gt>new()>と同じです)。
注意: Object::InsideOut自身からオブジェクトを生成することはできません。
# これは誤り
# my $obj = Object::InsideOut->new();
この点において、Object::InsideOutは自身のオブジェクトを作るクラスではなく、
プラグマの様に機能します。
=head2 オブジェクトのクローン
オブジェクトのコピーはC<-E<gt>clone()>メソッドで生成できます。
C<-E<gt>clone()>メソッドは、Object::InsideOutによって各クラスに
エクスポートされています:
my $obj2 = $obj->clone();
C<-E<gt>clone()>を引数無しで呼ぶと、オブジェクトのI<浅い>コピーが生成されます。
これは、オブジェクトが記憶している複雑なデータ構造 (つまり、配列、ハッシュ、
スカラーリファレンス) が、そのクローンと共有されることを意味します。
C<-E<gt>clone()>に真の引数を与えて呼ぶと:
my $obj2 = $obj->clone(1);
オブジェクトのI<深い>コピーを生成し、内部で保持している配列、ハッシュ、
スカラーリファレンス等のI<複製>が、新しく生成されたクローンに格納されます。
I<深い>クローンは、フィールドレベルでも制御できます。
更なる詳細はL</"フィールドのクローン">を参照してください。
クローンでは、内部で保持しているオブジェクトをクローンしないことに
注意してください。例えばC<$foo>がC<$bar>へのリファレンスを保持している場合、
C<$foo>のクローンもまたC<$bar>へのリファレンスを保持します;
C<$bar>のクローンではありません。このような振る舞いが必要な場合は、
L<:Replicate|/"オブジェクトの複製">
サブルーチンを使って提供しなければなりません。
=head2 オブジェクトの初期化
オブジェクトの初期化は、C<:InitArgs>ラベルを付けたハッシュ
(L<次の節|/"オブジェクト初期化引数の指定">で詳しく説明します)
とC<:Init>ラベルを付けたサブルーチンを組み合わせて行います。
C<:InitArgs>ラベルを付けたハッシュによって、C<-E<gt>new()>メソッドに渡された
引数リストから抽出するパラメータを指定します。抽出されたパラメータは、
C<:Init>ラベルを付けたサブルーチンで処理するために送られます:
package My::Class; {
my @my_field :Field;
my %init_args :InitArgs = (
'MY_PARAM' => qr/MY_PARAM/i,
);
sub _init :Init
{
my ($self, $args) = @_;
if (exists($args->{'MY_PARAM'})) {
$self->set(\@my_field, $args->{'MY_PARAM'});
}
}
}
package main;
my $obj = My::Class->new('my_param' => 'data');
(I<InitArgs>およびI<Init>という語は大文字小文字どちらでも構いませんが、
慣例により、全ての文字を小文字にするべきではありません。)
C<:Init>ラベルを付けたサブルーチンは2つの引数を受け取ります:
新たに生成され初期化が必要なオブジェクト (つまりC<$self>) と、
C<:InitArgs>での指定にマッチしたパラメータを含むハッシュリファレンスです。
サブルーチンで処理されるデータは、オブジェクトのID (つまりC<$$self>) を使って、
クラスのフィールド配列 (またはハッシュ) に直接代入しても構いません:
$my_field[$$self] = $args->{'MY_PARAM'};
しかし、L<-E<gt>set()|/"データの設定">メソッドを使うこと強く推奨します:
$self->set(\@my_field, $args->{'MY_PARAM'});
このメソッドはデータを、L<threads::shared>を使うアプリケーションで必要となる
共有フォーマットへ変換します。
=head2 オブジェクト初期化引数の指定
C<-E<gt>new()>メソッドで処理されるパラメータは、C<:InitArgs>属性の
ラベルを付けたハッシュで指定します。
最も単純なパラメータ指定は、タグだけの指定です:
my %init_args :InitArgs = (
'DATA' => '',
);
このケースでは、C<-E<gt>new()>メソッドに渡した引数に、
keyが C<DATA>と正確に一致する S<C<key =E<gt> value>> ペアがあった場合、
C<:Init>ラベルを付けたサブルーチンに渡されるハッシュリファレンスに、
S<C<'DATA' =E<gt> value>> が含まれます。
=over
=item パラメータ名マッチング
パラメータ名の正確な一致を求める代わりに、正規表現を使うこともできます:
my %init_args :InitArgs = (
'Param' => qr/^PARA?M$/i,
);
この場合、引数のkeyは次のいずれかで構いません:
PARAM, PARM, Param, Parm, param, parm, 等。
マッチが見つかると、 S<C<'Param' =E<gt> value>> がC<:Init>サブルーチンに
渡されます。引数で渡したオリジナルのkeyの代わりに、C<:InitArgs>ハッシュの
keyが使われることに注意してください。これはC<:Init>サブルーチンの中でkey
のパターンマッチをせずに済ませるためです。
(後述する)付加的なパラメータ指定を使う場合は指定構文が変わります。
そして、正規表現はハッシュリファレンスの内側に移動します:
my %init_args :InitArgs = (
'Param' => {
'Regex' => qr/^PARA?M$/i,
},
);
=item 必須パラメータ
必須 (Mandatory) パラメータは次のように宣言します:
my %init_args :InitArgs = (
# 正確な一致を要求する必須パラメータ
'INFO' => {
'Mandatory' => 1,
},
# パターンマッチを使った必須パラメータ
'input' => {
'Regex' => qr/^in(?:put)?$/i,
'Mandatory' => 1,
},
);
C<new>の引数リストに必須パラメータが無い場合、エラーが生成されます。
=item デフォルト値
オプションパラメータに対して、デフォルト値を指定できます:
my %init_args :InitArgs = (
'LEVEL' => {
'Regex' => qr/^lev(?:el)?|lvl$/i,
'Default' => 3,
},
);
=item 型チェック
パラメータの型を指定することもできます:
my %init_args :InitArgs = (
'LEVEL' => {
'Regex' => qr/^lev(?:el)?|lvl$/i,
'Default' => 3,
'Type' => 'Numeric',
},
);
指定可能な型は次の通りです:
=over
=item Numeric
(数値)
C<Num>またはC<Number>と指定する事もできます。これは
L<Scalar::Util::looks_like_number()|Scalar::Util/"looks_like_number EXPR">
を使って入力値をチェックします。
=item List
(リスト) この型では、単一の値または配列リファレンスが許されます
(単一の値の場合は配列リファレンス内に配置し直されます)。
=item クラス名
パラメータの型は、指定したクラスまたはそのサブクラスでなければなりません
(つまり、型チェックはC<-E<gt>isa()>を使って行われます)。
例えばC<My::Class>と指定します。
=item その他のリファレンス型
パラメータの型は、指定した (L<ref()|perlfunc/"ref EXPR">関数が返す)
リファレンス型でなければなりません。例えばC<CODE>と指定します。
=back
最初の2つの型は、大文字小文字を区別しません (例 'NUMERIC', 'Numeric',
'numeric', 等); 後ろの2つは大文字小文字を区別します。
C<Type>キーワードは、独自の型チェックを提供するコードリファレンスと
ペアにすることもできます。コードリファレンスは無名サブルーチン、または
(公開アクセスできる) サブルーチンリファレンスのいずれかです。
イニシャライザのコードリファレンス実行結果はブール値で返さなければなりません。
package My::Class; {
use Object::InsideOut;
# イニシャライザの型チェックに使用するサブルーチンを 'Private' にする事はできない
sub is_int {
my $arg = $_[0];
return (Scalar::Util::looks_like_number($arg) &&
(int($arg) == $arg));
}
my @level :Field;
my @comment :Field;
my %init_args :InitArgs = (
'LEVEL' => {
'Field' => \@level,
# 名前のあるサブルーチンを使った型チェック
'Type' => \&is_int,
},
'COMMENT' => {
'Field' => \@comment,
# 無名サブルーチンを使った型チェック
'Type' => sub { $_[0] ne '' }
},
);
}
=item 自動処理
フィールド配列/ハッシュにパラメータ値を直接代入する自動処理を指定できます。
この場合、C<:Init>サブルーチンにパラメータは渡されません:
my @hosts :Field;
my %init_args :InitArgs = (
'HOSTS' => {
# 'host' または 'hosts' を許す - 大文字小文字は区別しない
'Regex' => qr/^hosts?$/i,
# 必須パラメータ
'Mandatory' => 1,
# 単一の値または配列リファレンスを許す
'Type' => 'List',
# パラメータを@hostsへ自動的に代入する
'Field' => \@hosts,
},
);
上記の例では、hostパラメータが見つかった場合は自動的にC<@hosts>配列に
代入され、 S<C<'HOSTS' =E<gt> value>> ペアはC<:Init>サブルーチンには
B<渡されません>。実際、全てのパラメータについてフィールドを指定すると、
C<:Init>サブルーチンを持つ必要さえ無くなります!
あなたの全ての作業を引き受けてくれるでしょう。
パラメータの自動処理を指定する第2の方法は、L</"フィールド宣言">
部で行うものです:
my @data :Field('Arg' => 'data');
これは、次と等価です:
my @data :Field;
my %init_args :InitArgs = (
'data' => {
'Field' => \@data,
},
);
この方法では、C<-E<gt>new()>呼び出しで使うパラメータ名が、
C<'Arg'>キーワードで指定したものと正確に一致しなければなりません。
更に、このパラメータは任意(つまり、S<C<'Mandatory' =E<gt> 0>>)であり、
デフォルト値を持たず、パラメータ前処理 (後述) の対象とすることができません。
(これらの機能が必要なパラメータについては、C<:InitArgs>ハッシュを
使わなければなりません。) フィールドのI<set>アクセサに指定した、あらゆる
L<型チェック|/"アクセサの型チェック">がパラメータに対して適用されます。
望むならば、C<'Arg'>の代わりにC<'InitArg>を使用することができます。
=item パラメータ前処理
前述したあらゆるパラメータ処理が行われるよりも前に呼び出されるサブルーチンを、
パラメータに対して指定できます:
package My::Class; {
use Object::InsideOut;
my @data :Field;
my %init_args :InitArgs = (
'DATA' => {
'Preprocess' => \&my_preproc,
'Field' => \@data,
'Type' => 'Numeric',
'Default' => 99,
},
);
sub my_preproc
{
my ($class, $param, $spec, $obj, $value) = @_;
# パラメータの前処理を行う
...
# 結果を返す
return ...;
}
}
上述の様に、パラメータの前処理サブルーチンには5つの引数が渡されます:
=over
=item * パラメータと関連づけられたクラスの名前
上述の例では、C<My::Class>です。
=item * パラメータ名
上述の例では、C<DATA>です。
=item * パラメータ指定ハッシュのリファレンス
C<:InitArgs>ハッシュのC<DATA>キーとペアになったハッシュリファレンス。
=item * 初期化対象のオブジェクト
=item * パラメータの値
C<-E<gt>new()>メソッドの引数リストの中で、パラメータに割り当てられた値。
C<-E<gt>new()>の引数にパラメータが無い場合は、C<undef>が渡される。
=back
前処理サブルーチンの戻り値が、パラメータに設定されます。
前処理サブルーチンが、C<外部>から渡された引数の中でどの種類のデータを
使用できるのかということに注意してください。例えばパラメータ処理の順番は
指定されないため、前処理サブルーチンは他のパラメータが設定されているか
どうかに頼ることができません。
このような処理はC<:Init>サブルーチンで行う必要があります。
しかし、クラス階層で上位のクラスによって設定されたデータは、使用可能です。
(初期化対象のオブジェクトが引数で渡されるのはこのためです。)
パラメータ前処理では次のような事ができます:
=over
=item * 与えられた値のオーバーライド (またはC<undef>を返すことによる値の削除)
=item * 動的に決定したデフォルト値の提供
=back
=back
(上述した例において、I<Regex>はI<Regexp>または単にI<Re>、
I<Default>はI<Defaults>またはI<Def>、I<Preprocess>はI<Preproc>または
I<Pre>と指定しても構いません。これらおよび他の指定キーも、
大文字小文字を区別しません。)
=head2 オブジェクトの初期化前処理
時には、オブジェクト初期化の一部としてサブクラスから親クラスに
パラメータを送る必要があるかもしれません。
サブクラスにC<:PreInit>ラベルを付けたサブルーチンを提供することで、
この作業を遂行できます。これらのサブルーチンが見つかった場合、
クラス階層の下から上に向かう順番で
(つまり、子クラスが最初に) 呼び出されます。
このサブルーチンには2つの引数が渡されます: 新しく生成された
(初期化されていない)オブジェクト (つまりC<$self>) と、
C<-E<gt>new()>メソッド呼び出し時に与えられた全引数および
他のC<:PreInit>サブルーチンで追加された引数を含むハッシュリファレンスです。
ハッシュリファレンスはC<-E<gt>new()>に与えられたものと必ずしも同じとは限らず、
1つのハッシュリファレンスにI<平坦化>されるでしょう。
例を示します:
my $obj = My::Class->new(
'param_X' => 'value_X',
{
'param_A' => 'value_A',
'param_B' => 'value_B',
},
'My::Class' => { 'param' => 'value' },
);
という呼び出しにより、
{
'param_X' => 'value_X',
'param_A' => 'value_A',
'param_B' => 'value_B',
'My::Class' => { 'param' => 'value' }
}
というハッシュリファレンスがC<:PreInit>サブルーチンに渡されます。
目的上必要ならば、C<:PreInit>サブルーチンでハッシュリファレンスのパラメータを
追加、変更、削除しても構いません。
=head2 データの取得
クラスコード内では、オブジェクトのフィールド配列 (ハッシュ) から
オブジェクトIDを使って直接データを取得できます:
$data = $field[$$self];
# または
$data = $field{$$self};
=head2 データの設定
Object::InsideOutは各クラスに自動的にC<-E<gt>set()>
メソッドをエクスポートします。
L<threads::shared>を使ったアプリケーションでクラスが使用される可能性が
ある場合、クラスコード内でオブジェクトのフィールド配列/ハッシュにデータを
設定する際は、 (あなたのクラスコードをスレッドセーフにするために)
このメソッドを使用してください。
前述のように、オブジェクトIDを使ってオブジェクトのフィールド配列 (ハッシュ) へ
直接データを設定できます:
$field[$$self] = $data;
# または
$field{$$self} = $data;
しかし、スレッド間でデータ共有する (つまり、C<threads::shared>を使う)
アプリケーションでは、C<$data>をフィールド配列 (ハッシュ) に格納できるように
するため、共有データに変換する必要があります。
C<-E<gt>set()>メソッドはあなたに代わってこれらの作業を行います。
C<-E<gt>set()>メソッドには2つの引数を与えてください: オブジェクトフィールド
配列/ハッシュへのリファレンスと、それに格納するデータ (スカラー) です:
$self->set(\@field, $data);
# または
$self->set(\%field, $data);
整理すると、C<-E<gt>set()>メソッドはクラスコード内のみで利用可能であり、
アプリケーションコードでは使えません。
オブジェクトメソッド内で、オブジェクトフィールド配列/ハッシュに
データを設定する際に、このメソッドを使ってください。
メソッド名が衝突する場合は、完全修飾名を使ってC<-E<gt>set()>を呼び出せます:
$self->Object::InsideOut::set(\@field, $data);
=head2 アクセサの自動生成
L</"フィールド宣言">のオプションとして、
アクセサメソッドの自動生成を指定できます。
=over
=item アクセサの命名
次の指定により、I<標準的な名前> (つまり、I<get_>およびI<set_>
という接頭辞が付いた名前) のアクセサメソッドペアを生成できます:
my @data :Field('Standard' => 'data');
上記の指定の結果、Object::InsideOutはC<-E<gt>get_data()>
およびC<-E<gt>set_data()>という名前のアクセサメソッドを自動的に生成します。
(キーワードC<Standard>は大文字小文字を区別せず、C<Std>と省略できます。)
また、I<get>とI<set>のアクセサを別々に指定することもできます:
my @name :Field('Get' => 'name', 'Set' => 'change_name');
# または
my @name :Field('Get' => 'get_name');
# または
my @name :Field('Set' => 'new_name');
この場合、アクセサの名前は正確に指定しなければなりません
(つまり、与えられた名前に接頭辞は追加されません)。
(キーワードC<Get>とC<Set>は大文字小文字を区別しません。)
次の指定により、I<get/set>兼用アクセサを自動生成できます:
my @comment :Field('Accessor' => 'comment');
このアクセサは次のように使えます:
# 新しいコメントを設定する
$obj->comment("I have no comment, today.");
# 現在のコメントを取得する
my $cmt = $obj->comment();
(C<Accessor>キーワードは大文字小文字を区別せず、C<Acc>と短縮できます。
また、C<get_set>、C<Combined>、C<Combo>、C<Mutator>と指定することもできます。)
=item All-in-One
アクセサの命名とL<自動パラメータ処理|/"自動処理">
を一度に行うことができます:
my @data :Field('All' => 'data');
これは次のコードに対するI<簡略構文>です:
my @data :Field('Acc' => 'data', 'Arg' => 'data');
これは翻訳すると、次と等価です:
my @data :Field('Acc' => 'data');
my %init_args :InitArgs = (
'data' => {
'Field' => \@data,
},
);
I<標準的な>アクセサをお望みなら、次のようにしてください:
my @data :Field('Std_All' => 'data');
=item I<Set>アクセサの返値
デフォルトでは、自動生成したI<set>動作を行うメソッドは、設定された値
(つまりI<新しい>値) を返します。
C<Return>キーワードにより、I<set>アクセサの返値を指定できます。
例えば、デフォルトの動作を明示できます:
my @data :Field('Set' => 'set_data', 'Return' => 'New');
または、アクセサがI<old> (古い=以前の) 値
(設定されていなかった場合はC<undef>) を返すことを指定できます:
my @data :Field('Set' => 'set_data', 'Return' => 'Old');
もしくは、オブジェクト自身を返すようにします:
my @data :Field('Set' => 'set_data', 'Return' => 'Object');
(C<Return>は C<Ret>と短縮できます; C<Previous>、C<Prev>、C<Prior>はC<Old>
と同じ意味です; C<Object>はC<Obj>と短縮でき、C<Self>も同じ意味です。
これらは全て大文字小文字を区別しません。)
=item メソッド連鎖
フィールドがオブジェクトを格納するのに使われる場合は、
メソッド連鎖が使われることが明らかに分かるケースです:
格納されたオブジェクトに対するメソッドは、I<get>アクセサ呼び出しに
連鎖することができ、オブジェクトを探索します:
$obj->get_stored_object()->stored_object_method()
I<set>アクセサに対して、その返値 (前述) に基づいて連鎖することができます。
I<新しい>値を返すI<set>アクセサの例を示します:
$obj->set_stored_object($stored_obj)->stored_object_method()
I<set_stored_object()>呼び出しは、新しいオブジェクトを格納し、
更にそのオブジェクトを返すので、I<stored_object_method()>呼び出しは
格納した/返されたオブジェクトを介して起動されます。
I<古い>値を返すI<set>アクセサについても同様に働きます。
ただしこの場合は、以前格納した (そして今返された) オブジェクトに対して
連鎖メソッドが起動されます。
L<Want>モジュール (バージョン 0.12 以降) が利用可能ならば、このモジュールは
オブジェクトを格納しない/返さないI<set>アクセサへのメソッド連鎖についても、
I<正しいこと>を行おうと試みます。
この場合は、I<set>アクセサを起動したオブジェクトが、連鎖メソッドの起動にも
使われます (まるでI<set>アクセサが S<C<'Return' =E<gt> 'Object'>>
と共に宣言されたかの様に):
$obj->set_data('data')->do_something();
ただし、I<get>アクセサや、I<兼用>アクセサを引数無しで起動した
(つまりI<get>アクセサとして使われた) 時には、
この特殊な処理が適用されないという事に注意してください。
メソッド連鎖を成功させるには、これらはオブジェクトを返さなければなりません。
=item アクセサの型チェック
オプションとして、I<set/兼用>アクセサに型チェック
コードを追加する事をObject::InsideOutに指示できます:
my @level :Field('Accessor' => 'level', 'Type' => 'Numeric');
指定可能な型は次の通りです:
=over
=item Numeric
(数値)
C<Num>またはC<Number>と指定する事もできます。これは
L<Scalar::Util::looks_like_number()|Scalar::Util/"looks_like_number EXPR">
を使って入力値をチェックします。
=item List または Array
引数は、複数の値または1つの配列リファレンスでなければなりません
(複数の値の場合は、配列リファレンス内に配置し直されます)。
=item Array_ref
(配列リファレンス)
引数は、1つの配列リファレンスでなければなりません。
C<Arrayref>と指定することもできます。
=item Hash
(ハッシュ)
引数は、複数の S<C<key =E<gt> value>> ペアまたは1つのハッシュリファレンスで
なければなりません
(複数のペアの場合は、ハッシュリファレンス内に配置し直されます)。
=item Hash_ref
(ハッシュリファレンス)
引数は、1つのハッシュリファレンスでなければなりません。
C<Hashref>と指定することもできます。
=item クラス名
引数の型は、指定したクラスまたはそのサブクラスでなければなりません
(つまり、型チェックはC<-E<gt>isa()>を使って行われます)。
例えばC<My::Class>と指定します。
=item その他のリファレンス型
引数の型は、指定した (L<ref()|perlfunc/"ref EXPR">関数が返す) リファレンス型
でなければなりません。例えばC<CODE>と指定します。
=back
最後の2つ以外の型は、大文字小文字を区別しません
('NUMERIC'、'Numeric'、'numeric'等)。
C<Type>キーワードは、独自の型チェック機能を提供するコードリファレンスと
ペアにすることもできます。コードリファレンスは、無名サブルーチン、または
完全修飾したサブルーチンのリファレンスのいずれかです。
入力した引数に対してコードリファレンスを実行した結果は、
ブール値で返さなければなりません。
package My::Class; {
use Object::InsideOut;
# アクセサの型チェックに使用するサブルーチンは 'Private' にする事ができる
sub positive :Private {
return (Scalar::Util::looks_like_number($_[0]) &&
($_[0] > 0));
}
# コードリファレンスは無名サブルーチンである
# (これは引数がスカラーであることをチェックする)
my @data :Field('Accessor' => 'data', 'Type' => sub { !ref($_[0]) } );
# サブルーチンの完全修飾名を使ったコードリファレンス
my @num :Field('Accessor' => 'num', 'Type' => \&My::Class::positive);
}
C<Type>キーワードだけを指定したり、C<Get>キーワードだけとの組み合わせで
使った場合はエラーになることに注意してください。
Perlパーサの制約により、C<:Field>属性の途中で改行はできません:
# これは動きません
# my @level :Field('Get' => 'level',
# 'Set' => 'set_level',
# 'Type' => 'Num');
# 全てを1行に書かなければなりません
my @level :Field('Get' =>'level', 'Set' => 'set_level', 'Type' => 'Num');
=item :lvalueアクセサ
L<perlsub/"Lvalue subroutines">に記されているとおり、C<:lvalue>サブルーチンは
変更可能な値を返します。
この変更可能な値は例えば、代入文や置換演算子の左辺として使うことができます
(これ故C<LVALUE> (左辺値) と呼ばれます)。
Perl 5.8.0以降では、Object::InsideOutはC<LVALUE>コンテキストで
使われた場合にオブジェクトのフィールドに値を設定する、
C<:lvalue>アクセサの生成をサポートしています。
package Contact; {
use Object::InsideOut;