抽象データ型
抽象データ型を自分なりに調べてみたのでまとめてみる。誤っていたらコメントで指摘して欲しいです。
色々調べてみたのですが、以下が一番しっくりきました。今から私の行う説明はほとんど以下に記載されている内容を自分なりに解釈したものです。まだ読まれていない方は先に読んでみてください。
抽象データ型を歴史からひも解く
まず初めに皆さんよく使われると思うのですが、手続き(関数)を使って処理を抽象化した手続き型プログラミングが出てきました。
次にデータの隠蔽をという考えが出てきました。データの隠蔽を行うにあたって、隠蔽するデータを利用するロジックも併せて隠蔽する必要があるので、結果としてモジュールが生まれ、機能の提供側とクライアント側に分けることが出来ました。
この提供側をクラスを利用して型として表現することで複製可能(インスタンス)だったり、同じ型同士で比較可能・交換可能・計算可能などの要素を持たせて、言語やIDEや静的解析ツールの恩恵を受けるようにするのが抽象データ型です。
話は逸れますが、抽象データ型だけでは振る舞いが抽象化できないので、継承を利用してポリモーフィズムを実現することで振る舞いを抽象化することがオブジェクト指向の考え。これをすると修正に対して閉じて拡張に対して開くのでオープンクローズドの原則につながったりする。
抽象データ型の目的
抽象データ型を以下のように説明したが、これを満たしたうえで抽象データ型が行いたい目的を考える。
この提供側をクラスを利用して型として表現することで複製可能(インスタンス)だったり、同じ型同士で比較可能・交換可能・計算可能などの要素を持たせて、言語やIDEや静的解析ツールの恩恵を受けるようにするのが抽象データ型。
抽象データ型の目的としてよく見るのが、ユーザが定義したデータ型とそれに付随するロジックを纏めることでカプセル化を図るというもの。ですがこれは上記の説明のデータ隠蔽でも満たせているため、誤ってはいないが抽象データ型の目的とは異なると思ういます。
私が思うに抽象データ型の目的はカプセル化したモジュールを「型」として扱うことで、より安全にプログラミングを行うことだと考えます。
なんで型として扱うと安全にプログラミングできるかを上手い文章が思いつかなかったので、サンプルコードを書いてみました。
税抜きの抽象データ型
<?php
require_once "./IncludeTaxAmount.php";
define("__TAX_RATE", 0.1);
//税抜き金額
class ExcludTaxAmount {
private int $_exclud_tax_amount;
/**
* 初期化
*
* @param integer $exclud_tax_amount
*/
public function __construct(int $exclud_tax_amount) {
if (!$this->is_valid($exclud_tax_amount)) {
throw new Exception('異常値');
}
$this->_exclud_tax_amount = $exclud_tax_amount;
}
/**
* 値の取りうる範囲
*
* @param [type] $exclud_tax_amount
* @return boolean
*/
private function is_valid($exclud_tax_amount): bool {
return 0 < $exclud_tax_amount;
}
/**
* getterはないほうがいいけど、加算できないから作る
*
* @return integer
*/
public function get_exclud_tax_amount(): int {
return $this->_exclud_tax_amount;
}
/**
* 加算
*
* @param ExcludTaxAmount $exclud_tax_amount
* @return ExcludTaxAmount
*/
public function add_exclud_tax_amount (ExcludTaxAmount $exclud_tax_amount): ExcludTaxAmount {
return new ExcludTaxAmount($this->_exclud_tax_amount + $exclud_tax_amount->get_exclud_tax_amount());
}
/**
* 税抜き→税込み計算
*
* @param ExcludTaxAmount $exclud_tax_amount
* @return IncludeTaxAmount
*/
public function become_include_tax_amount (ExcludTaxAmount $exclud_tax_amount): IncludeTaxAmount{
return new IncludeTaxAmount(round($exclud_tax_amount->get_exclud_tax_amount() * (1 + 0.01 * __TAX_RATE)));
}
}
税込みの抽象データ型
<?php
//税込み金額
class IncludeTaxAmount {
private int $_include_tax_amount;
/**
* 初期化
*
* @param integer $include_tax_amount
*/
public function __construct(int $include_tax_amount) {
if (!$this->is_valid($include_tax_amount)) {
throw new Exception('異常値');
}
$this->_include_tax_amount = $include_tax_amount;
}
/**
* 値の取りうる範囲
*
* @param [type] $include_tax_amount
* @return boolean
*/
private function is_valid($include_tax_amount): bool {
return 0 < $include_tax_amount;
}
/**
* getterはないほうがいいけど、加算できないから作る
*
* @return integer
*/
public function get_include_tax_amount(): int {
return $this->_include_tax_amount;
}
/**
* 加算
*
* @param IncludeTaxAmount $include_tax_amount
* @return IncludeTaxAmount
*/
public function add_include_tax_amount (IncludeTaxAmount $include_tax_amount): IncludeTaxAmount {
return new IncludeTaxAmount($this->_include_tax_amount + $include_tax_amount->get_include_tax_amount());
}
}
クライアント
<?php
require_once "./ExcludTaxAmount.php";
require_once "./IncludeTaxAmount.php";
//なんか金額を扱う処理
//直で入れているが、受け取った値やDBの値を渡すイメージ
$exclud_tax_amount1 = new ExcludTaxAmount(1000);
$exclud_tax_amount2 = new ExcludTaxAmount(2000);
$exclud_tax_amount3 = $exclud_tax_amount1->add_exclud_tax_amount($exclud_tax_amount2);
$includ_tax_amount1 = $exclud_tax_amount1->become_include_tax_amount($exclud_tax_amount1);
$includ_tax_amount2 = $exclud_tax_amount2->become_include_tax_amount($exclud_tax_amount2);
$includ_tax_amount3 = $includ_tax_amount1->add_include_tax_amount($includ_tax_amount2);
echo($exclud_tax_amount1->get_exclud_tax_amount(). "\n");
echo($exclud_tax_amount2->get_exclud_tax_amount(). "\n");
echo($exclud_tax_amount3->get_exclud_tax_amount(). "\n");
echo($includ_tax_amount1->get_include_tax_amount(). "\n");
echo($includ_tax_amount2->get_include_tax_amount(). "\n");
echo($includ_tax_amount3->get_include_tax_amount(). "\n");
税抜きの抽象データ型と税込みの抽象データ型を作成し、クライアント側でそれらを利用した処理を行うという内容になっています。
税抜きの抽象データ型と税込みの抽象データ型を利用することで、本来税抜きを利用しなければいけなかった場合に誤って税込みを利用したり、今現在この値が税込みか税抜きかを考える手間がなくなります。
このように考えることを減らし安全にプログラミングを行うことが抽象データ型の目的なのではと考えます。