はじめに
エンジニアになりたての頃、なにも考えずにコーディングをしていたところ、ベテランエンジニアの友達から「現場で役立つシステム設計の原則」という本を勧められ読んでみました。
将来のことを考えたシステム設計のことなど考えたこともなかった私には目からウロコの内容ばかり書いてありました。
なので今回は、学んだことの備忘録と、少しだけオブジェクト指向について触れる記事になります。
※記事内の言語はPHPで書いています
今回参照した書籍
「現場で役立つシステム設計の原則」(増田亨さん著)
初学者の方にもおすすめです。
この記事の目的
オブジェクト指向は、正直ネットで調べても意味がわからず、初心者の私にはさっぱりでした。
難しい概念ばかり出てくるし、これだけやればOKみたいな単純なものでもないし、概念や思想だという記事まで出てきてより混乱してしまいました。
この難解さが、私のような初心者が「オブジェクト指向」を怖がる原因になってしまいます。
が、しかし今回は、「現場で役立つシステム設計の原則」を読んでみて、オブジェクト指向に少しでも触れられたことにとても感動したので、おさらいと、これから先にも書く予定のオブジェクト指向の記事の走りだしになります。
また、この記事では表層も表層しかさらっていないので、もしオブジェクト指向のことが少しでも気になった方は、ぜひ「現場で役立つシステム設計の原則」を読んでみてください。
解釈違いやアドバイス等あれば教えていただけると幸いです。
オブジェクト指向ってそもそも何?
プログラミングをしていると、以下のような問題に直面することがあると思います。
「コードがめちゃくちゃで、どこに何が書いてあるかすぐに見つけられない!」
「さっきバグ直したと思ったのになんで別のところでエラー出てるの??」
「重複したコードがどんどん増えていってる。。。」
みたいなことありますよね。そんな実装を続けていたら、1年後...2年後...大変なことになっています。
こういった問題は、特にプロジェクトが大規模化し、複雑になるにつれて頻繁に発生する事象です。このような状況が続くと、コードの保守が困難になり、新しい機能を追加するのも一苦労です。
オブジェクト指向は、こうした最悪な事態を避けるために考え出された手法です。
オブジェクト指向は理論ではありません。開発の現場で工夫されてきた設計ノウハウです。現場ですぐに役に立つ実践的なやり方と考え方です。
引用:「現場で役立つシステム設計の原則」
オブジェクト指向実践の基本的な考え方
従来の開発は、まず全体を定義して、それを分割、さらに細かく詳細を決定していってから開発を行う手法が通常ですが、オブジェクト指向は、個々の部品ごとに作り、それを組み合わせながら全体を作り上げていきます。
※従来の開発のイメージとオブジェクト指向のイメージ画像
従来の開発:全体の構成を決定してから分割
各部品の一つ一つが、対象業務(ドメイン)の関心事を表現するものになります。
そして、全体を構築する上では、部品同士の関係性も明確にしなければいけません。
例えば、全国のキャンプ場を検索して予約できるサイトのことを考えます、思いつく対象業務は下記のようなものがあるとします。
- 顧客がキャンプ場を検索、予約をする
- サイト管理者が空き状況を確認する
- サイト管理者が予約を受理する
- 予約日に顧客がキャンプ場を利用をしたことを確認する。
- 顧客に支払請求をする
- 支払確認をする
など
これらの対象業務を、プログラムで表現するために、業務の関心事を部品として設計し、かつ関係性を考えてみます。
各部品
- 顧客情報
- 商品(キャンプ場)
- 予約受付
- 利用確認
- 空き状況
- 支払請求
- 支払い確認
これらの関係性は下記のようになるはずです。
このイメージの各部品は「パッケージ」と呼ばれ、矢印は各パッケージ間の参照関係を表しています。
「予約受付」の際は、「顧客情報」と「商品(キャンプ場)」と「空き状況」を参照する、といったような感じです。
パッケージは、さらに細かい部品で構築されており、これらパッケージの中の部品はオブジェクトというもので出来ています。
例)商品(キャンプ場)パッケージの中身のオブジェクト
- 名前
- 住所
- 地図
- 説明
- 設備
- 料金
- 利用可能期間
- レビュー
- 評価
- 連絡先情報
など
オブジェクトの数はあらかじめ決められるものでなく、必要な業務の関心ごとが増えるたびにオブジェクトも増えていきます。
しかし、増えすぎた場合、パッケージが肥大化してしまいます。
その場合は、いくつかのオブジェクトを切り出して、さらに新しいパッケージにします。
順序が逆になってしまいましたが、
オブジェクト指向の開発として進む順番は下記のようになります。
細かい部品(オブジェクト)を作り → 増えたらパッケージ単位に切り出す。
という繰り返しで全体を構成していきます。
ちなみに、先程イメージ画像で示した例は、本来のキャンプ場予約システムの業務知識とはかけ離れている可能性があります。
これを改善するためには、コミュニケーション力がとても重要になってきます。
精度が高く質のいい部品を作るためには、専門的な知識は必須な要素です。
たまたま、キャンプ場を運営していたことがある人でもない限りは、その分野の専門家から情報を得なければなりません。
そのためには、その業務の専門家から適切な情報を得て、それを元にプログラムに落とし込める部品作りを、メンバーと議論しながら行っていくことが大切になってきます。
オブジェクト指向は、プログラミングスキルだけに限らない、総合力が求められる手法です。
オブジェクト指向のメリット
オブジェクト指向を実践することで得られる利点には以下のようなものがあります。
-
コードの可読性が向上
コードが論理的にまとまっているため、他の開発者がコードを読みやすくなります。 -
コードの再利用性が向上
一度作成したオブジェクトを他のプロジェクトでも簡単に再利用できます。 -
メンテナンスが容易
オブジェクトの内部の実装が外部に影響を与えないため、コードの変更やバグ修正が容易になります。 -
拡張性が高い
新しい機能を追加する際、既存のコードを変更せずにクラスを拡張することで対応できます。これにより、システム全体の安定性が保たれます。
大規模な開発になればなるほど、これらの内容が改善されることで、より質の良いサービスが作れます
オブジェクト指向のデメリット
オブジェクト指向には多くの利点がありますが、もちろんデメリットも存在します。
以下にそのデメリットを詳しく説明します。
-
学習難易度が高い
オブジェクト指向プログラミングの概念や設計パターンは、初心者にとって難解です。基本的な概念を理解して、実践するには、一定の時間と試行錯誤の時間が必要です。 -
総合的な技術力が求められる
オブジェクト指向を実践するエンジニアには、設計力、プログラミング力、さらには会話力など、多岐にわたる技術力が必要です。特に、大規模なシステムや複雑な業務要件を扱う際には、高度な設計スキルが求められます。 -
逆に複雑になりすぎるリスク
柔軟性や拡張性を重視するあまり、設計が複雑になりすぎることがあります。オブジェクト指向では、クラスの抽象化というものを行ったりしますが、これらを過剰に乱用することで、逆に複雑になりすぎるリスクがあります。
オブジェクト指向プログラミングには多くのメリットがありますが、その利点を最大限に引き出すためには、高度なスキルと経験が求められます。また、適切なバランスを取らないと、逆にシステムの複雑化やパフォーマンス低下を招く可能性もあります。オブジェクト指向のメリットとデメリットを理解し、適切な設計と実装を心がけることが重要です。
オブジェクト指向"的"な実装にチャレンジ
少しだけオブジェクト指向"的な"実装を試行錯誤してみるために、
オブジェクトを作って、パッケージングしてみる過程をやってみます。
※ポリモーフィズムは実装していなかったりなどオブジェクト指向を100%実践しているものではありません。
オブジェクト単位を見つける
まずは、細かい部品であるオブジェクトを見つけていきます。
キャンプ場の検索予約サイトなので、まずは商品であるキャンプ場の情報は必ず必要です。
キャンプ場の情報に必要なオブジェクトはさっき列挙した中から、いくつかピックアップしたものだけ書いてみます
ピックアップしたキャンプ場情報のオブジェクト
- キャンプ場名
- キャンプ場の料金
これらをコードで表現してみます。
クラスで表現してみる
コードで表現するときはクラスで表現します。
先程のオブジェクトをそれぞれクラスにしてみます。
<?php
// キャンプ場名クラス
class CampSiteName {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
// 料金クラス
class Price {
private $price;
public function __construct($price) {
$this->price = $price;
}
public function getPrice() {
return $this->price;
}
}
それぞれのクラスを上記のように作りました。
しかし、このままだと、これらのクラスを使う方のクラスに「判断/加工/計算」のロジックを書いてしまうことになります。オブジェクト指向的な実装にするためには、単純なgetterの使用は避け、ロジックの置き場所にします。
getterを無くしてクラスにロジックを書いてみる
// キャンプ場名クラス
class CampSiteName {
private $name;
public function __construct($name) {
$this->setName($name);
}
// 名前のバリデーションとフォーマットを行うメソッド
private function setName($name) {
if (empty($name)) {
throw new InvalidArgumentException("名前は空であってはなりません。");
}
if (strlen($name) < 10) {
throw new InvalidArgumentException("名前は10文字未満である必要があります。");
}
$this->name = $name;
}
// 名前の一部を変更するメソッド
public function changeNamePart($oldPart, $newPart) {
$this->name = str_replace($oldPart, $newPart, $this->name);
}
// 名前を表示するメソッド
public function display() {
echo "キャンプ場の名前: " . $this->name . "\n";
}
}
// 料金クラス
class Price {
private $price;
public function __construct($price) {
$this->validatePrice($price);
$this->price = $price;
}
// 料金のバリデーションを行うメソッド
private function validatePrice($price) {
if (!is_numeric($price) || $price < 0) {
throw new InvalidArgumentException("料金は0以上の数値でなければなりません。");
}
}
// 割引後の料金を計算するメソッド
public function calculateDiscountedPrice($discountRate) {
if ($discountRate < 0 || $discountRate > 1) {
throw new InvalidArgumentException("割引率は0から1の間でなければなりません。");
}
return $this->price * (1 - $discountRate);
}
// 料金を表示するメソッド
public function display() {
echo "料金: " . $this->price . "\n";
}
}
パッケージングしてみる
先程の二つのクラスは、キャンプ場情報のパッケージに入るものです。
なので、Campsiteクラスにパッケージングしてみます。
// キャンプ場クラス
class CampSite {
private $name;
private $price;
public function __construct(CampSiteName $name, Price $price) {
$this->name = $name;
$this->price = $price;
}
// キャンプ場の情報を表示するメソッド
public function displayInfo() {
$this->name->display();
$this->price->display();
}
// 割引後の料金を表示するメソッド
public function displayDiscountedPrice($discountRate) {
$discountedPrice = $this->price->calculateDiscountedPrice($discountRate);
echo "割引後の料金 (" . ($discountRate * 100) . "% 割引): " . $discountedPrice . "\n";
}
}
書いたクラスを使ってみる
実際にこれらクラスを使ってみます。
$campSiteName = new CampSiteName("那須キャンプ場");
$price = new Price(5000);
$campSite = new CampSite($campSiteName, $price);
$campSite->displayInfo(); // キャンプ場の情報を表示
$campSite->displayDiscountedPrice(0.2); // 割引後の料金を表示
// 名前の一部を変更する
$newCampSiteName = $campSiteName->changeNamePart("那須", "朝霧");
$newCampSite = new CampSite($newCampSiteName, $price);
$newCampSite->displayInfo();
結果は以下です。
キャンプ場の名前: 那須キャンプ場
料金: 5000
割引後の料金 (20% 割引): 4000
キャンプ場の名前: 朝霧キャンプ場
料金: 5000
以上までで、実装チャレンジ終わりです。
まとめ
今回は、「現場で役立つシステム設計の原則」を読んだことを受けて、オブジェクト指向のことを書いてみました。
本来であれば、「ポリモーフィズム」などオブジェクト指向を実践する上で大事な概念などはここに記述していません。
これから先、オブジェクト指向を使った開発があるかはわかりませんが、システム設計を行う上でとても大切な考え方なのでこれからも勉強は続けていこうと思います。
また、こういった考え方は僕のような初学者にこそ今のうちから知っておくべきことな気がします。
参照
書籍:現場で役立つシステム設計の原則