Prototypeパターン

Prptotypeパターンは生成に関するデザインパターンの1つです。 通常はnewしてインスタンスを生成しますが、Prototypeでは既存のインスタンスをコピー(clone)して新しいインスタンスを生成します。

メリット
新しいインスタンスを作成する複雑さを、クライアントから隠蔽する。
型のわからないオブジェクトの生成を行うという選択肢を、クライアントに提供する。
環境においてはオブジェクトのコピーの方が新しいオブジェクトを作成するよりも有効である可能性がある(ソースコードの管理等)。

デメリット
コピーを作る事で物事が複雑になる場合がある(これについては追々書いてみたい)。

Head First デザインパターンでモンスターを作るクラス図があったのでそれを参考に中身だけ書いてみたいと思います。
単純にモンスターをコピーできるようにするのと、ゲームを楽しむユーザーがモンスターのレベルとHPを変更できるようにしていきます。

・Monster.php

<?php
abstract class Monster
{
    private int $id; // 1種類につき与えられるモンスターのid
    private string $name;
    protected int $level;
    protected int $hp;
    private string $category;
    private bool $copied; // コピーされたものかどうか

    public function __construct(int $id, string $name, int $level, int $hp, string $category)
    {
        $this->id = $id;
        $this->name = $name;
        $this->level = $level;
        $this->hp = $hp;
        $this->category = $category;
        $this->copied = false;
    }

    abstract protected function __clone();

    public function newInstance(): \Monster
    {
        return clone $this;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getLevel(): int
    {
        return $this->level;
    }

    public function getHp(): int
    {
        return $this->hp;
    }

    public function getCategory(): string
    {
        return $this->category;
    }

    public function getCaller(): string
    {
        return $this->caller;
    }

    protected function copied(bool $copied): void
    {
        $this->copied = $copied;
    }

}

ユーザー側で値を変更できないモンスター
・WellKnownMonster.php

<?php
require_once 'Monster.php';

class WellKnownMonster extends Monster
{

    protected function __clone()
    {
        $this->copied(true);
    }

}

ユーザー側でlevelとhpを変更できるモンスター
・DynamicPlayerGeneratedMonster.php

<?php
require_once 'Monster.php';

class DynamicPlayerGeneratedMonster extends Monster
{
    protected function __clone() {
        $this->copied(true);
    }

    public function setLevel(int $level): void
    {
        $this->level = $level;
    }

    public function setHp(int $hp): void
    {
        $this->hp = $hp;
    }
}

・MonsterRegistry.php

<?php

class MonsterRegistry
{
    private array $registered;

    public function register(Monster $monster): void
    {
        $this->registered[$monster->getId()] = $monster;
    }

    public function getMonster(int $id){
        return $this->registered[$id]->newInstance();
    }
}

・ index.php

<?php
require_once 'MonsterRegistry.php';
require_once 'WellKnownMonster.php';
require_once 'DynamicPlayerGeneratedMonster.php';

$monsterRegistry = new MonsterRegistry();
$monster1 = new WellKnownMonster(1, 'ゴブリン', 4, 12, 'ゴブリン');
$monsterRegistry->register($monster1);
$monsterCopy1 = $monsterRegistry->getMonster($monster1->getId());

var_dump($monster1);
var_dump($monsterCopy1);

$monster2 = new DynamicPlayerGeneratedMonster(37, 'アイスボム', 24, 400, 'ボム');
$monsterRegistry->register($monster2);
$monsterCopy2 = $monsterRegistry->getMonster($monster2->getId());
$monsterCopy2->setLevel(99);
$monsterCopy2->setHp(9999);

var_dump($monster2);
var_dump($monsterCopy2);

コピー元となるモンスターはDBに入れそうなものですが…。 その場合は引っ張ってきてインスタンス化して必要であればコピーしていくといった方法で使うといいかな?って思います。

(参考)
Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本
2005/12/2
Eric Freeman (著), Elisabeth Freeman (著), Kathy Sierra (著), Bert Bates (著), 佐藤 直生 (監訳), 木下 哲也 (翻訳), 有限会社 福龍興業 (翻訳)
13章 付録:残りのパターン

リモートワーク対策ツールメモ

基本的にリモートデスクトップで作業するんだろうなと思いますが、画面共有や情報共有などについてメモっておきます。役に立つかはわかりません。
ファイルの共有はTeamsやSharePointなどで事足りそうですね。

※リモートワーク時に使えるかは未検証です。

※VPN経由(リモートデスクトップで作業中)で使えるかなども未確認です。

画面共有

※画面共有のやり方を説明している記事のリンク

例)
 Teams: 会議中に自分の画面を共有する – Office サポート

 LINE: LINEで画面共有する方法まとめ【iPhone/Android/PC】 | アプリオ

 Discord: 【Discord】画面共有のやり方/音声共有方法も【PC/スマホ対応】 – DigitalNews365

リモートアシスト

nasが壊れたときに某社サポートが使ってたような遠隔操作ツールです。
Google翻訳をチャットツールみたいに使い始めたときは賢いな~と思いました(余談)。

例)
 TeamViwer

共同プログラミング

エディタは各々自由に選んでいるのでちょっと微妙ですが、共同編集機能(プラグイン)というものもあります。
(TeamViwerとか使ってリモートアシストしてしまう方が早いかもしれない)

例)
 Atom: Teletype for Atom

 Eclipse: DocShare_Plugin

 VS Code: Live Share

 PhpStorm: Code With Me

共有ホワイトボード

お絵かきしながら説明したいなんかに便利。とりあえずGoogleのが安牌?
(そういえば知り合いに画面共有しながらペイントとかパワポ(編集画面)で説明しだす猛者おったな)

※この手のツールを使う場合は、ペン型のポインティングデバイス推奨です。

共用ホワイトボードアプリ紹介:
テレワークで使いたいホワイトボードアプリ5選! – スクラムマスダーの日記


Builderパターン

Buliderクラスについてサンプルを書きます。
effective javaに書いているようなBuilderとGoFのBuilderは違うのかな?その辺がよくわかりませんが、GoFの方を書いていきます。目的は一緒だと思います。
effective javaのBuilderは下をご覧ください。

Bulderパターンですが生成に関するパターンで複合構造の構築に良く使われます。
監督者が(director)、指示を出して建築者 (Builder) が構築していくイメージです。

メリット
複雑なオブジェクトの構築方法をカプセル化できること

デメリット
構築がめんどくさ(ry

他にも複数の手順の変化するプロセスで柔軟なオブジェクトを作成できる点(Factoryとは対照的)がメリットとして挙げられますね。思っていたのと違うパラメータを設定している可能性もあるので一長一短ですが。

それでは作っていきます。夕飯を作ります。

パターン1:ステーキ定食(ご飯、ステーキ、オニオンスープ、ポテトサラダ)
パターン2:パン、ハンバーグ、シーザーサラダ

・Builder.php

<?php
interface Builder
{
    public function stapleFood(string $stapleFood):void;
    public function mainDish(string $mainDish):void;
    public function soup(string $soup):void;
    public function salad(string $salad):void;
    public function getResult();
}

・DinnerBuilder.php

<?php
require_once 'Builder.php';

class DinnerBuilder implements Builder
{
    private string $stapleFood;
    private string $mainDish;
    private string $soup;
    private string $salad;

    public function stapleFood(string $stapleFood): void
    {
        $this->stapleFood = $stapleFood;
    }

    public function mainDish(string $mainDish): void
    {
        $this->mainDish = $mainDish;
    }

    public function soup(string $soup): void
    {
        $this->soup = $soup;
    }

    public function salad(string $salad): void
    {
        $this->salad = $salad;
    }

    public function getResult(): dinnerBuilder
    {
        return $this;
    }
}

・Director.php

<?php

require_once 'Builder.php';

class Director
{

    private Builder $builder;

    public function __construct(Builder $builder){
        $this->builder = $builder;
    }

    public function makeSteakSetMeal(): void
    {
        $this->builder->stapleFood("rice");
        $this->builder->mainDish("steak");
        $this->builder->soup("onion soup");
        $this->builder->salad("potato salad");
    }

    public function addStapleFood(string $stapleFood): void
    {
        $this->builder->stapleFood($stapleFood);
    }

    public function addMainDish(string $mainDish): void
    {
        $this->builder->mainDish($mainDish);
    }

    public function addSoup(string $soup): void
    {
        $this->builder->soup($soup);
    }

    public function addSalad(string $salad): void
    {
        $this->builder->salad($salad);
    }
}

・index.php

<?php
require_once 'DinnerBuilder.php';
require_once 'Director.php';

$builder1 = new DinnerBuilder();
$director1 = new Director($builder1);
$director1->makeSteakSetMeal();

var_dump($builder1);

$builder2 = new DinnerBuilder();
$director2 = new Director($builder2);
$director2->addStapleFood("bread");
$director2->addMainDish("hamburg steak");
$director2->addSalad("caesar salad");

var_dump($builder2);

Builderパターンは実はeffective javaで紹介されているような物を良く使っていてあまりこちらで書いていないので他の人がこの書き方で書いているのを見た事があって混乱した覚えがあります。BuilderといってもLombok BuilderやFluent Builderというのもあるみたいです。複雑ですね…

(参考)
Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本
2005/12/2
Eric Freeman (著), Elisabeth Freeman (著), Kathy Sierra (著), Bert Bates (著), 佐藤 直生 (監訳), 木下 哲也 (翻訳), 有限会社 福龍興業 (翻訳)
13章 付録:残りのパターン

Singletonパターン

Singletonパターンについて書いてみます。

Singletonパターンはインスタンスが1つしか存在しない唯一のオブジェクトを生成するために使います。

オブジェクトが1つしか必要ないというケースとは ・スレッドプール ・キャッシュ ・ダイアログボックス ・プリファレンス ・レジストリの設定を処理するオブジェクト ・ロギング用のオブジェクト など。 これらのオブジェクトを二つ以上インスタンス化してしまうとプログラムの誤操作、リソースの使いすぎ、つじつまの合わない結果などの問題が出てきます。

特徴としてクラス外部からインスタンスを作成できないようにするためにコンストラクタをprivateにします。

・Singleton.php

<?php
class Singleton
{
    private static Singleton $singleton;

    private function __construct()
    {
        echo "create singleton";
    }

    public static function getInstance(): \\Singleton
    {
        if (!isset(self::$singleton)) {
            self::$singleton = new Singleton();
        }

        return self::$singleton;
    }
}

・index.php

<?php
require_once 'Singleton.php';

$singleton1 = Singleton::getInstance();
$singleton2 = Singleton::getInstance();

if ($singleton1 === $singleton2) {
    echo "同じインスタンス";
}

// new Singleton();
// Fatal error: Uncaught Error: Call to private

インスタンスをnewしようとするとコンストラクタがプライベートなのでFatal errorとなります。 インスタンスが一つしか存在しないことを保証できます


(参考)
Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本
2005/12/2
Eric Freeman (著), Elisabeth Freeman (著), Kathy Sierra (著), Bert Bates (著), 佐藤 直生 (監訳), 木下 哲也 (翻訳), 有限会社 福龍興業 (翻訳)
5章 Singletonパターン:唯一のオブジェクト

SQLパフォーマンス詳解を読んだ

明けましておめでとうございます。今年もよろしくお願いいたします。

11月の事ですが、SQLパフォーマンス詳解を読みました。
感想などを書いていきたいと思います。

読んでみようと思ったきっかけ
かなり多くの回数呼ばれるプログラムのデバッグや後輩のコードレビューをする中でパフォーマンスを意識したSQLの知識が皆無に等しくこのままでは良くないと思ったため。また先輩から勧められたため。

感想
インデックスをどう活用するかを書いてくれててわかりやすかった。どうすればパフォーマンスが良くなるのかということでググると、「適切なインデックス」を張るということをよく目にするがいつも「どうすれば適切になるんだ」と思っていた。張り過ぎもよくないと聞くので「過ぎ」ってどの程度のことなんだろうと思ったりしていた。インデックスは作成されているけど、sqlの書き方のせいで使えていないとかもある。そういうものがわかってきたので読んで良かったと思います。MySQLのExtra列については今後しっかり勉強したい。

以下まとめ(MySQLのみ) 第4章、5章は省略

第1章 インデックスの内部構造

インデックスの最も重要な目的は、インデックスを張ったデータに対して順序をつけてアクセスできるようにすることです。

writeの処理(insert, update, delete)が走る際に、大量のデータを動かすわけにはいかない。そういう場合の解決策として双方向連結リストでインデックスリーフノードと対応するテーブルのデータを対応させている。

検索ツリー(Bツリー)
データベースがリーフノードを高速に見つけるのに役に立つ。MySQLの場合は、BTREEと表記されている。Bはバランスの略でその名の通り木構造。1~1000というidが入ったノードと1001~2000、2001~3000といったノードがあって20xxというidを検索するとき、1から順に検索しなくて良いので速くなる。1001以上かどうか、2001以上かどうかなどを調べて該当のノードの中を確認する。 ツリーが深さは対数的に増え、リーフノードの増加に対してツリーの深さの増大は非常に遅い。

インデックスによる検索は、以下の3ステップで行われる。

  1. ツリーを走査
  2. リーフノードチェーンをたどる
  3. テーブルからデータを読み出す

UNIQUEなSCANは1しか行わないため高速。

第2章 WHERE句

前提としてSQLを発行する際には実行計画(EXPLAIN)は必ず行う。 MySQLにおける実行計画で最低限注意して見るべき点は下の記事を参考にしています。

https://nippondanji.blogspot.com/2009/03/mysqlexplain.html

複合インデックス定義する際に考えるべき最も重要なのは、そのインデックスを使えるSQL文ができるだけ多くなるように、列の順番を決めることです。

a, b, c, dという4つの列の複合インデックスがあった場合は、a,bを条件に検索するときも使えるので先に定義しておく。cを条件にすることが多い場合は、c, a, b, dなどの順で定義するといい。こうすることで新たなインデックスの作成を減らせる。インデックスの数が多いと更新処理のパフォーマンスに影響する。

WHERE句はインデックスの列の順に合わせて使う。 OR検索やWHERE INなどは場合によっては実行計画が変わるので出来るだけUNIONやJOINをうまく使って出来るだけ使わないようにしたほうがいいかもしれない。データ数が少ないテーブルの場合はそんなに気にしなくていいとは思うが。

パラメータ化クエリはちゃんと使う。SQLインジェクションを防ぐために。残念ながらMySQLはされていないが、SQLによってはパラメータ化クエリを使うことでキャッシュされたSQLが使える。

・範囲検索(大なり、小なり、BETWEEN) なるべく先に狭い範囲になるように検索してから残りを検索する。

・LIKE ワイルドカード(%)を先頭に出来るだけ使わない。 使ったことなかったけどmatchやagainstを使えるなら使う。

カラムはしっかり指定して*で取得するのはやめる。

インデックスは2つ使うより、1つだけ使う方が高速

第3章 パフォーマンスとスケーラビリティ

データ量が多くなるにつれパフォーマンスは悪くなる。

注意深い実行計画の調査結果は、うわべだけのベンチマークよりも信用のおけるものです。完全な負荷テストは意味のあることですが、そのための手間はかかります。

本番のデータとテスト時のデータ、開発時のデータは全く違うものだと思うし、似たようなテストをするにしても大変な手間がかかる。大量データの場合は特に、適切なインデックスが張られているかを注意する。ただそれだけデータがあるということはそれだけ更新処理があると思うので張りすぎにも注意する。

リレーショナルSQLデータベースかリレーショナルでないシステムに関わらず、正しいインデックスを作ることは、クエリの応答時間を短くする唯一の方法です。

第6章 ソートとグルーピング

order by句がインデックスによる順序付けと一致している場合、 データベースは明示的なソート処理を省略できます。

order by句でASK, DESCが混在している場合はインデックスが効かない。ASC,ASCやDESC,DESCだったら効く。混在させる場合はインデックスを定義する必要がある。

第7章 部分的結果

・LIMIT

データベースは、部分結果のみを取得することを事前に知っている場合のみ、部分結果のみ、部分結果向けにクエリを最適化できます。

実際に事前に知れているかどうかは実行計画を確認する。ただMySQLの場合は、Extra列に「using filesort」がなければ部分結果の取得が行われている。

・ページングについて

ページングの際は、並べ替えの順序は確定的である必要があります。

ページングの際はoffsetを使うのが一般的だがそのテーブルにリアルタイムで行が挿入される場合は、挿入されたデータが最初に来る場合があるので、ずれてしまう。

第8章 挿入、削除、更新

インデックスが作られるとデータベースはインデックスを自動的にメンテナンスする。 簡単にいうとinsertするとインデックスのノードも更新するため処理が重くなる。 インデックスを張りすぎると良くないのはこのため。insertが圧倒的に多いテーブルにはインデックスは出来るだけ少なくする必要がある。

インデックスは注意深くかつ慎重に使い、かつ、可能な限り冗長なインデックスは使わないようにしましょう。これは、delete文やupdate文を使う際にも同じ事が言えます。

deleteやupdateにも実行計画はあります。