リスト8.7 ,リスト8.8 物理攻撃クラス&武闘家の物理攻撃クラス
Mouse
での継承で override
を使うの初めて。
というか、それ以前でもなかったかも。
親クラスの同名のメソッドを上書きして使いたい時に使うもの、程度の認識です。
今回は差をわかりやすくするために、親クラスである PhysicalAttack
の攻撃力と、それを継承する FighterPhysicalAttack
の攻撃力に極端に差をつけてます。
おかげで、武闘家のダメージが通常 : 21に対してダブル : 12 と逆転してますが、まぁ。
ここクリックして展開
#!/usr/bin/env perl use strict; use warnings; use feature qw/say/; use Function::Parameters; package PhysicalAttack { use Carp qw/croak/; use Mouse; use namespace::autoclean; use constant { MIN => 0 }; method single_attack_damege() { return 1; } method double_attack_damege() { return 2; } __PACKAGE__->meta->make_immutable(); } package FighterPhysicalAttack { use Carp qw/croak/; use Mouse; use namespace::autoclean; use constant { MIN => 0 }; extends 'PhysicalAttack'; override 'single_attack_damege' => sub { return super() + 20; }; override 'double_attack_damege' => sub { return super() + 10; }; __PACKAGE__->meta->make_immutable(); } package main; my $physical_attack = PhysicalAttack->new(); say $physical_attack->single_attack_damege(); # 1 say $physical_attack->double_attack_damege(); # 2 my $fighter_physical_attack = FighterPhysicalAttack->new(); say $fighter_physical_attack->single_attack_damege(); # 21 say $fighter_physical_attack->double_attack_damege(); # 12
で、こちらが親クラスをいじった結果、子クラスの数字がおかしくなっちゃう版。
言語は違えど、同じオブジェクト指向。見事に同様のエラーになりますなぁ。
ここクリックして展開
#!/usr/bin/env perl use strict; use warnings; use feature qw/say/; use Function::Parameters; package PhysicalAttack { use Carp qw/croak/; use Mouse; use namespace::autoclean; use constant { MIN => 0 }; method single_attack_damege() { return 1; } method double_attack_damege() { return $self->single_attack_damege * 2 } __PACKAGE__->meta->make_immutable(); } package FighterPhysicalAttack { use Carp qw/croak/; use Mouse; use namespace::autoclean; use constant { MIN => 0 }; extends 'PhysicalAttack'; override 'single_attack_damege' => sub { return super() + 20; }; override 'double_attack_damege' => sub { return super() + 10; }; __PACKAGE__->meta->make_immutable(); } package main; my $physical_attack = PhysicalAttack->new(); say $physical_attack->single_attack_damege(); # 1 say $physical_attack->double_attack_damege(); # 2 my $fighter_physical_attack = FighterPhysicalAttack->new(); say $fighter_physical_attack->single_attack_damege(); # 21 say $fighter_physical_attack->double_attack_damege(); # 52 !?
8.9 武闘家の物理攻撃クラス(コンポジション版)
というわけで、継承を使わずにやってみます。
子クラスの中で親クラスのインスタンス変数を用意し
my $physical_attack = PhysicalAttack->new();
おぉ、ちゃんと正しい、意図した数値になった。
ここクリックして展開
#!/usr/bin/env perl use strict; use warnings; use feature qw/say/; use Function::Parameters; package PhysicalAttack { use Carp qw/croak/; use Mouse; use namespace::autoclean; use constant { MIN => 0 }; method single_attack_damege() { return 1; } method double_attack_damege() { return $self->single_attack_damege * 2 } __PACKAGE__->meta->make_immutable(); } package FighterPhysicalAttack { use Carp qw/croak/; use Mouse; use namespace::autoclean; use constant { MIN => 0 }; my $physical_attack = PhysicalAttack->new(); method single_attack_damege() { return $physical_attack->single_attack_damege() + 20; } method double_attack_damege() { return $physical_attack->double_attack_damege() + 10 } __PACKAGE__->meta->make_immutable(); } package main; my $physical_attack = PhysicalAttack->new(); say $physical_attack->single_attack_damege(); # 1 say $physical_attack->double_attack_damege(); # 2 my $fighter_physical_attack = FighterPhysicalAttack->new(); say $fighter_physical_attack->single_attack_damege(); # 21 say $fighter_physical_attack->double_attack_damege(); # 12
リスト8.10 基底クラスでの悪しき共通化 〜 リスト8.17
もう、ここはこれに尽きるのではないでしょうか。
ある継承クラスにとっては関係があっても、別の継承クラスにとっては無関係なメソッドが登場し始めると問題です。
あぁ、これは第5章の「共通処理クラス」のところの横断的関心から外れているから、と言うことで良いのだろうか。
サブクラスの都合でスーパークラスを変更してはいけない!
クソコード動画「継承」 pic.twitter.com/wK3mIx6XmE
— ミノ駆動 (@MinoDriven) 2021年1月24日
本文読んだ後でこの動画を見ると沁みるなぁ。
第8章の残りの部分
以降は文章による説明が続くので、簡潔に。
なんでもpublicで密結合
・・・たぶん、Perl でクラスをパブリックにするとかプライベートにするとかないんじゃないかなぁ。
ちょっとググったりした程度では不明でした。
ということで、次。
privateメソッドだらけ
多くの責務を持ってしまっている可能性あり。
別々のクラスに分離しよう。
高凝集の誤解から来る密結合
たとえば、販売価格クラスに対して、販売手数料、配送料、ショッピングポイントなどが含まれているクラス。
支払いについては凝集していると言えるが、別々の責務が入り込んでいる。
「ある概念の値を使って別の概念の値を算出したい場合」は、計算に使う値をコンストラクタの引数として渡す。
販売手数料クラス、配送料クラス、ショッピングポイントクラス、など。
スマートUI
表示と、表示以外の責務は分ける。
巨大データクラス
様々なデータを持つが故に、色々なところで使われ、値の変更がどこかで行われてしまう可能性が高まる。
グローバル変数と同様の弊害を招きます。
トランザクションスクリプトパターン
メソッド内に一連の処理手順がダラダラと長く書き連ねられている構造
(中略)
データを所持するクラス(データクラス)と、データを処理するクラスとで分けている場合に頻繁に実装されます。
・・・あれ、うちの書くコードってほとんどこれなのでは(思い当たる節がある)
手続き型プログラミングとも呼びます。
お?
手続き型プログラムは、開始点のメインルーチンから階層的に分割された数々のサブルーチン及びそのローカル変数と、全てのサブルーチンからアクセス可能な数々のグローバル変数で構成される。複数のサブルーチンからアクセスされるあらゆるデータを、グローバル変数としてまとめてしまう簡素な設計は、小中規模のソフトウェア開発には適したものとされている。
ふむ。
まぁ、単機能スクリプトというか、ソースが1画面で表示終わっちゃうようなものであれば手続き型で、それ以上ならちゃんと構造化したオブジェクト指向でって感じかな。
神クラス
密結合クラスの対処法
- オブジェクト指向設計
- 単一責任の原則
- 責務ごとのクラス分割
- 100 〜 200 行が一つのクラスの目安
- (思い当たる節がある)
- 使えるテクニック
- 早期return
- ストラテジパターン
- インターフェイスで振り分けるやつ
- ファーストクラスコレクション
- クラスの集合をクラスで表す