はじめに
エンジニアを1年やって何となく使ってたオブジェクト思考をしっかり理解したい!
そう思い基礎から勉強し直したので備忘録として残します。
サンプルコードはPHPを使いますが他のオブジェクト思考言語にも応用できるかと思います。
注意点としては、コードを乗せていますが全部理解する必要はなく、「このメソッドでこういう事をしてるのか」
ぐらいのざっくり理解で問題ないです!
できる限りシンプルに解説しますが言葉足らずや間違いがあればご指摘お願いします
では行きましょう!
なぜオブジェクト思考は理解しづらいのか
それはズバリ「何が良いか分からないから」です。
わざわざ抽象的な概念であるオブジェクト思考を使わずともプログラムは作れるしなんかやたらファイルも多くなって逆によく分からない!
そう思う初学者の人もいると思います。(実際私も同じ事を考えてました)
オブジェクト思考のメリットはたくさんありますが、最も良さを感じられるのが「拡張性」だと思ってます。
保守性とか再利用性とかって実務で働いたりしないと実感しにくい部分でもあるので初学者でもオブジェクト思考の良さを実感できる拡張性にフォーカスを当てて解説しようと思います。
今回の記事を見てなんとなくでも良いのでオブジェクト思考のメリットを感じて頂ければ幸いです。
実際のコードの違い
プレイヤー同士が1~10のカードを引いて数字の大きい方が勝利というシンプルなプログラムを例とします。
オブジェクト思考を使わないコード
メインプログラムが全ての責務を負っている状態
function makePlayer()
{
$name1 = 'プレイヤー1';
$name2 = 'プレイヤー2';
$player1 = [
'name' => $name1,
'score' => 0
];
$player2 = [
'name' => $name2,
'score' => 0
];
return [$player1, $player2];
}
function draw($players)
{
$cards = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// プレイヤー1がランダムで1枚引く
$arrKey1 = array_rand($cards, 1);
$score1 = $cards[$arrKey1];
$players[0]['score'] = $score1;
// プレイヤー2がランダムで1枚引く
$arrKey2 = array_rand($cards, 1);
$score2 = $cards[$arrKey2];
$players[1]['score'] = $score2;
return $players;
}
function juge($playerScoreArr)
{
// 勝敗判定
if ($playerScoreArr[0]['score'] > $playerScoreArr[1]['score']) {
echo '勝者は' . $playerScoreArr[0]['name'] . 'です';
} elseif ($playerScoreArr[1]['score'] > $playerScoreArr[0]['score']) {
echo '勝者は' . $playerScoreArr[1]['name'] . 'です';
} elseif ($playerScoreArr[0]['score'] === $playerScoreArr[1]['score']) {
echo '引き分けです';
}
}
// 関数呼び出し
$players = makePlayer();
$playerScoreArr = draw($players);
juge($playerScoreArr);
着目点はここだけでOKです
// 関数呼び出し
$players = makePlayer();
$playerScoreArr = draw($players);
juge($playerScoreArr);
オブジェクト思考を使ったコード
メインプログラム(Gameクラスのstart関数)が各アクターに責務を委譲してる状態。
実際にはインターフェースで処理をまとめたり、アクターをさらに分割した方が良いと思いますが今回は割愛
class Card
{
private array $cards = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
public function draw()
{
$arrKey = array_rand($this->cards, 1);
return $this->cards[$arrKey];
}
}
class player
{
private int $drawCard = 0;
public function draw(Card $card)
{
$this->drawCard = $card->draw();
}
public function getDrawCard()
{
return $this->drawCard;
}
}
class TwoPlayerRule
{
private $player1 = '';
private $player2 = '';
public function __construct()
{
$this->player1 = new Player();
$this->player2 = new Player();
}
public function draw()
{
$card1 = new Card();
$card2 = new Card();
$this->player1->draw($card1);
$this->player2->draw($card2);
}
public function juge()
{
$score1 = $this->player1->getDrawCard();
$score2 = $this->player2->getDrawCard();
if ($score1 > $score2) {
echo 'プレイヤー1の勝利です';
exit;
} elseif ($score2 > $score1) {
echo 'プレイヤー2の勝利です';
} elseif ($score1 === $score2) {
echo '引き分けです';
}
}
}
class Game
{
private int $playerNum = 0;
public function __construct(int $playerNum)
{
$this->playerNum = $playerNum;
}
public function start()
{
$rule = $this->getRule();
$rule->draw();
$rule->juge();
}
public function getRule()
{
$rule = '';
if ($this->playerNum === 2) {
$rule = new TwoPlayerRule();
}
return $rule;
}
}
// Gameクラスの引数には参加人数を入力
$game = new Game(2);
$game->start();
ちなみにメインプログラムはここだけ↓
①ルールを取得
②カードを引く
③勝敗を判定
public function start()
{
$rule = $this->getRule();
$rule->draw();
$rule->juge();
}
多分コード数が多くてオブジェクト思考を使った方が分かりにくい!
そう思った人もいると思いますが、オブジェクト思考の強みは拡張性の高さです。
ここから機能追加をしていきます
プレイヤー数を追加
オブジェクト思考を使わないコード
判定ロジックが少々複雑なのと処理の内容は重要ではないので今回は割愛させて頂きました
<?php
function makePlayer()
{
$name1 = 'プレイヤー1';
$name2 = 'プレイヤー2';
$name3 = 'プレイヤー3';
$player1 = [
'name' => $name1,
'score' => 0
];
$player2 = [
'name' => $name2,
'score' => 0
];
$player3 = [
'name' => $name3,
'score' => 0
];
return [$player1, $player2, $player3];
}
function draw($players)
{
$cards = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// プレイヤー1がランダムで1枚引く
$arrKey1 = array_rand($cards, 1);
$score1 = $cards[$arrKey1];
$players[0]['score'] = $score1;
// プレイヤー2がランダムで1枚引く
$arrKey2 = array_rand($cards, 1);
$score2 = $cards[$arrKey2];
$players[1]['score'] = $score2;
// プレイヤー3がランダムで1枚引く
$arrKey3 = array_rand($cards, 1);
$score3 = $cards[$arrKey3];
$players[2]['score'] = $score3;
return $players;
}
function juge($playerScoreArr)
{
// 勝敗判定
if ($playerScoreArr[0]['score'] > $playerScoreArr[1]['score'] && $playerScoreArr[0]['score'] > $playerScoreArr[2]['score']) {
echo '勝者は' . $playerScoreArr[0]['name'] . 'です';
} elseif ($playerScoreArr[1]['score'] > $playerScoreArr[0]['score'] && $playerScoreArr[1]['score'] > $playerScoreArr[2]['score']) {
echo '勝者は' . $playerScoreArr[1]['name'] . 'です';
} elseif ($playerScoreArr[2]['score'] > $playerScoreArr[1]['score'] && $playerScoreArr[2]['score'] > $playerScoreArr[0]['score']) {
echo '勝者は' . $playerScoreArr[2]['name'] . 'です';
} elseif () {
// 以下割愛
}
}
$players = makePlayer();
$playerScoreArr = draw($players);
juge($playerScoreArr);
オブジェクト思考を使ったコード
こちらもロジックは省略しますね
class ThreePlayerRule
{
// 追加
private $player3 = '';
public function draw()
{
// 追加
// 省略
}
public function juge()
{
// 追加
// 省略
}
両者の違い
この二つの大きな違いは既存プログラムを変更してるのか
という点です。
(正確にはGameクラスのgetRuleメソッドの分岐処理は必要ですが、ほぼ修正なし)
オブジェクト思考を使用してるプログラムは仕様変更がある度にメインプログラムを変更してるのに対してオブジェクト思考を使ったコードはThreePlayerRuleクラスというクラスを新規に追加しただけで既存のコードには触れていません。
このように一つの仕様変更に対して新しくコードを追加するだけで対応できるオブジェクト思考プログラミングはシステムの拡張性が高いと言われる理由です。
現実世界で例えると自転車のタイヤが壊れたら自転車本体を1から作り直すのが非オブジェクト思考
でタイヤのみを交換するのがオブジェクト思考
というイメージです。
非オブジェクト思考のコードの問題点
問題点はいくつかありますが、重要なのはプログラム同士が密に結合してる事です。
人数を増やしたい場合makePlayerメソッドを修正する必要があり、drawメソッドも修正が必要になり、さらにはjugeメソッドも修正する必要があります。
プログラムが大きくなればなるほど一つの追加機能で他の多くの箇所を修正しなければいけなくなります。
さらに一つの修正で他のプログラムでバグが起こりやすくなり、あっという間に技術的負債の完成といった感じになるのです。
既存コードをできる限り修正せずに開発ができるようしっかり設計をすることが重要になってきますね!
まとめ
- オブジェクト思考はシステムの拡張性が高い
- オブジェクト思考は既存コードの修正を最小限にしてくれる
- メインプログラムのコードがシンプルになり可読性が上がる
最後に
私自身エンジニアとしては1年程ですがオブジェクト思考をほとんど使わずに無理やりif文などで実装してる案件に数回アサインした経験があるのですが、1つのファイルで何千行というファイルを見たときは絶望しましたし、そういった状態になると修正するのが難しくなりどんどん技術的負債となってしまします。数日間ソースを読み解くだけみたいな日々で本当に苦しかったです笑
この記事を見て少しでもオブジェクト思考に興味を持って、開発してくれる方が増えると嬉しいです!