基本的なデザインパターンの紹介
〜初歩的な7種類〜
Singletonパターン
Iteratorパターン
FactoryMethodパターン
Adapterパターン
Template Methodパターン
Bridgeパターン
Strategyパターン
今回紹介するデザインパターン
デザインパターンってなに?
GoF が命名した23個のパターン
1995年に出版された
「オブジェクト指向における再利用のためのデザインパターン」
GoF本には
「こんな問題に直面したら、こんな設計にしましょうね」
という指針を示した本だったため
『よく出会う問題とそのスマートな解決策』
という意味でも使われる事がある
「再利用性の高い柔軟な設計が出来る」
開発者どうしの意思疎通がスムースになる
※パターン名で設計概念についてやりとりできる
Singletonパターン
唯一の存在を保証するためのパターン
■インスタンスへのアクセスを制御
・インスタンスへのアクセス手段が限られるため
クライアントからアクセスを制御出来る
class Singleton
{
//インスタンス保持用
private static $instance = null;
/**
* コンストラクタ
*/
private function __construct () {
}
/**
* インスタンス取得
*/
public static function get () {
if ( is_null ( self::$instance ) ) {
self::$instance = new self;
}
return self::$instance;
}
}
$singleton = Singleton::get();
Iteratorパターン
要素の集まりを保有するオブジェクトの各要素を順番に
アクセスする方法を提供するためのパターン
Aggregate
ConcreteIterator()
Iterator
+ next()
+ hasNext()
ConcreteIterator
+ next()
+ hasNext()
ConcreteAggregate
ConcreteIterator()
create
create
Iterator:要素に順次サクセスするインターフェース
ConcreteIterator:順次アクセス方法・次要素の有無等の手順の詳細処理を定義
Aggregate:「Iterator」を作り出すインターフェースを定める
ConcreteAggregate:「iterator」メソッドで、自身のオブジェクトをコンストラクタ引数に
「ConcreteAggregate」のオブジェクトを返します
hasNext():次の要素が存在するかどうかを返す
next():次の要素をとしだして、走査中の位置を進める
■集約オブジェクトの内部構造を隠蔽
・処理はすべてConcreteIteratorクラス内に閉じこめられるため
クライアントはリストの内部構造を意識する必要がなくなる
■集約オブジェクトの操作方法を複数用意出来る
・異なる実装のConcreteIteratorクラスを用意することで
内容を容易に変更できます。
$staffList = new StaffList();
$staffList->add("江口", 2);
$staffList->add("斎藤", 1);
foreach($staffList as $row){
var_dump($row->name, $row->sex);
}
string(6) "江口"
int(2)
string(6) "斎藤"
int(1)
class StaffList implements Iterator {
private $staffs = array();
private $index = 0;
//初期化
public function __construct () {
$this->index = 0;
}
//最初の要素に巻き戻す
function rewind () {
$this->index = 0;
}
//現在の要素を返す
function current() {
return $this->staffs[$this->index];
}
//現在の要素のキーを返す
function key () {
return $this->index;
}
//次の要素に進む
function next () {
$this->index++;
}
//現在位置が有効かどうかを調べる
function valid () {
return isset ( $this->staffs[$this->index] );
}
//追加
public function add ( $name, $sex ){
$staff = new Staff();
$staff->name = $name;
$staff->sex = $sex;
$this->staffs[$this->index] = $staff;
$this->index++;
}
}
FactoryMethodパターン
オブジェクトの生成方法に工夫・加工を加える事で
より柔軟にオブジェクトを生成することを目的とする
インスタンスの生成をサブクラスに行わせることで
より柔軟に生成するインスタンスを選択する事が可能
Product:オブジェクト生成メソッド(工場)で生成されるオブジェクト(製品)のAPIを定義
ConcreteProduct:Productクラスで定義されたAPIを実装したクラス
Creator:オブジェクト生成メソッドを提供するクラス
ConcreteCreator:Creatorクラスを継承したサブクラス
Creator
FactoryMethod():Product
Product
ConcreteProductConcreteCreator
FactoryMethod():Product
create
create
return new ConcreteProduct();
■オブジェクトの生成処理と使用処理を分離できる
■オブジェクトの利用側と
オブジェクトのクラスの結びつきを低くする
class ReaderFactory
{
/* Readerクラスのインスタンスを生成するAPI */
public function create ( $fileName )
{
$reader = $this->_createReader ( $fileName );
return $reader;
}
/** 生成するReaderサブクラスを選定する */
private function _createReader ( $fileName )
{
if (false !== stripos($fileName, '.xml')) {
return new XMLFileReader($fileName);
} else {
throw new Exception($fileName . ' is not supported.');
}
}
}
interface Reader
{
/** 読み込み */
public function read ();
/** 表示 */
public function display ();
}
class XMLFileReader implements Reader
{
/** コンストラクタ */
public function __construct($fileName)
{
if (!is_readable($fileName)) {
throw new Exception($fileName . 'is not readable.');
}
$this->_fileName = $fileName;
}
/** 読み込み */
public function read()
{
$this->_handler = simplexml_load_file($this->_fileName);
}
/** 表示 */
public function display()
{
foreach ( $this->_handler->channel->item as $item ) {
echo $item->title;
echo PHP_EOL;
}
}
}
Product
ConcreteProduct
Creator
<?xml version="1.0" encoding="utf-8"?>
<rss>
<channel>
<item>
<title>やったぜ!</title>
</item>
</channel>
</rss>
やったぜ!
$fileName = $argv[1];
try{
$factory = new ReaderFactory();
$obj = $factory->create($fileName);
$obj->read();
$obj->display();
}
catch(Exection $e){
echo $e;
}
Adapterパターン
■インタフェースに互換性の無いクラス同士を組み合わせる
→既存のクラスに対して修正を加える事なく
インターフェースを変更することが出来る。
■既存のコードを修正することなく再利用できる
・一枚皮をかぶせるようなクラスを作成し
必要なメソッドをそれに追加して行くため
既存のクラスを一切変更することなく
新しいAPIとして提供することができる
■利用側はアダプタの向こう側にある実装を意識する必要がない
■公開するAPIを制限する
・異なるAPIを結びつける際に、意図的にAPIを制限する
・また、公開されていないメソッドを利用出来るようにする
継承の場合
<<interface>>
Target
+Request()
Adaptee
+SpefificRequest()
Adapter
+Request()
Target:clientが要求するAPIを提供
Adaptee:AdapterクラスによってAPIを変更される側のクラス
Adapter:Adapteeが提供するAPIをTargetが提供するAPIに変換するクラス
継承の場合
interface Persion
{
public function getName();
}
class Employee
{
private $_name;
public function __construct( $name )
{
$this->_name = $name;
}
public function printName()
{
print($this->_name.PHP_EOL);
}
}
class EmployeePersion extends Employee implements Persion
{
public function __construct($name)
{
Employee::__construct($name);
}
public function getName (){
Employee::printName();
}
}
継承の場合
$employee = new EmployeePersion("さいとう");
$employee->getname();
$employee->printName();
さいとう
さいとう
委譲の場合
<<interface>>
Target
+Request()
Adaptee
+SpefificRequest()
Adapter
+Request()
Target:clientが要求するAPIを提供
Adaptee:AdapterクラスによってAPIを変更される側のクラス
Adapter:Adapteeが提供するAPIをTargetが提供するAPIに変換するクラス
委譲の場合
interface Persion
{
public function getName();
}
class Employee
{
private $_name;
public function __construct( $name )
{
$this->_name = $name;
}
public function printName()
{
print($this->_name.PHP_EOL);
}
}
class EmployeePersion implements Persion
{
private $_person;
public function __construct ( $name )
{
$this->_person = new Employee( $name );
}
public function getName (){
$this->_person->printName();
}
}
委譲の場合
$employee = new EmployeePersion("さいとう");
$employee->getname();
さいとう
TemplateMethdパターン
テンプレートの機能を持つパターン
スーパークラスで処理の枠組みを定め
サブクラスでその具体的内容を実装します。
AbstractClass
method1()
method2()
templateMethod()
ConcreteClass
method1()
method2()
AbstractClass:処理の大きな枠組を定義するクラス
ConcreteClass:AbstractClassクラスを継承したサブクラス
■共通な処理をまとめることができる
・サブクラスに共通処理を記載する必要がないため
変更が発生した場合にAbstoractClass側を変更するだ
け
■サブクラスにより
具体的な処理内容を変えることができる
※AbstoractClass
処理内容を持たずに名前だけ定義されたメソッド
abstract class AbstractDisplay {
/** サブクラスに実装を任せる抽象メソッド **/
protected abstract function output ();
/** 抽象クラスで実装しているメソッド **/
final function display (){
$this->output();
}
}
/** サブクラスを実装 **/
class StringDisplay extends AbstractDisplay{
private $string;
function __construct($string){
$this->string = $string;
}
function output (){
echo $this->string."!".PHP_EOL;
}
}
$d = new StringDisplay("やったぜ");
$d->display();
やったぜ
Bridgeパターン
「橋渡し」クラスを用意することによって
クラスを複数の方向に拡張させる
Abstraction
-implementor:implementor
+Operation()
Implementor
+Operationlmp():void
aggrigate
Concretelmplentor
+Operationlmp():void
RefinedAbstraction
+Operationlmp():void
implementor→Operationlmp()
Client
Abstraction:「何をするのか」を実現するクラス群で最上位に位置するクラス
RefineAbstraction:Abstractionクラスで提供される機能を拡張するサブクラス
Implementor:「どうやってするのか」を実現するクラス群での最上位に位置するクラス
ConcreteImplementor:Implementorクラスを継承したサブクラス
■クラス階層の見通しが良くなる
「機能」と「実装」を提供するクラス群を分けているので
クラス階層を理解しやすく、見通しがよくなる
■最終的に作成すべきクラス数を抑えることができる
継承を使った単純な多態性を使った場合と比べると
クラス数を抑えることが出来る
■機能の拡張と実装の切り替えが容易
「機能」と「実装」を分ける事で
お互いに影響することなく、拡張や切り替えが可能
Implementor
class FileDataSource implements DataSource {
private $source_name;
private $handler;
function __construct($source_name) {
$this->source_name = $source_name;
}
function open() {
if (!is_readable($this->source_name)) {
throw new Exception('データソースが見つかりません');
}
$this->handler = fopen($this->source_name, 'r');
if (!$this->handler) {
throw new Exception('データソースのオープンに失敗しました');
}
}
function read() {
$buffer = array();
while (!feof($this->handler)) {
$buffer[] = fgets($this->handler);
}
return join($buffer);
}
function close() {
if (!is_null($this->handler)) {
fclose($this->handler);
}
}
}
ConcreteImplementor
class Listing {
private $data_source;
function __construct($data_source) {
$this->data_source = $data_source;
}
function open() {
$this->data_source->open();
}
function read() {
return $this->data_source->read();
}
function close() {
$this->data_source->close();
}
}
Abstraction
interface DataSource {
public function open();
public function read();
public function close();
}
Implementor
Implementor
class ExtendedListing extends Listing {
function __construct($data_source) {
parent::__construct($data_source);
}
function readWithEncode() {
return htmlspecialchars($this->read(), ENT_QUOTES);
}
}
RefinedAbstraction
aggrigate
apacheのアクセスログ
apacheのアクセスログ
$list1 = new Listing(new FileDataSource('access.log'));
$list2 = new ExtendedListing(new FileDataSource('access.log'));
try {
$list1->open();
$list2->open();
}
catch (Exception $e) {
die($e->getMessage());
}
$data = $list1->read();
echo $data.PHP_EOL;
$data = $list2->readWithEncode();
echo $data.PHP_EOL;
$list1->close();
$list2->close();
Strategyパターン
メソッドの中に溶け込んだ形のアルゴリズムより
柔軟でメンテナンスしやすい設計となる
Strategy
strategyMethod()
Context
strategy
Strategy
strategyMethod()
Strategy
strategyMethod()
■処理毎にまとめることができる
・処理がクラスにまとめられて実装されているため
コードは処理内容に専念することが出来る
※保守性が高まる
■異なる処理を選択するための条件文がなくなる
・1つのクラスやメソッドに異なる処理を記載した場合
if文やswitch文を使って処理を分岐することになる
※コードの可読性・保守性・拡張性が下がる
■異なる処理を動的に切り替えることができる
・クライアントはConcreteStrategyクラスのインスタンスを
Contextオブジェクトに渡すだけで、動的に切り替える
//Strategy interface
interface Strategy {
public function test();
}
// Strategyインスタンスを実行するContext
class Context {
private function __construct(){}
public static function test( Strategy $strategy) {
return $strategy->test();
}
}
//Strategy interfaceの実装
class Test2 implements Strategy {
public function test() {
return 2;
}
}
//Strategy interfaceの実装
class Test1 implements Strategy {
public function test() {
return 1;
}
}
1
2
$val = Context::test( new Test1());
echo "$val n";
$val = Context::test( new Test2());
echo "$val n";
御清聴ありがとうございますm(_ _)m

デザインパターン(初歩的な7パターン)