kakke18’s blog

ゆるふわ学生エンジニア

Singleton-たった一つのインスタンス-

Java言語で学ぶデザインパターン入門 の第5章を参考にしました。

Singleton Methodパターンとは

本書には以下のように記載されています。

インスタンスが1個しか存在しないことを保証するパターン

つまり、生成するインスタンスの数を1つに制限するデザインパターンです。

実装

こちらを参考にしてください。

一般的な例

public class Singleton {
    // (1)
    private static Singleton singlton = new Singleton();
    // (2)
    private Singleton() {
        System.out.println("インスタンスを生成しました。");
    }
    // (3)
    public static Singleton getInstance() {
        return singlton;
    }
}

Singletonパターンを適用するために以下のようなルールがあります。

  • (1) 自身のクラス変数として、自身のインスタンスを保持
  • (2) 外部から生成されないように、コンストラクタはprivate
  • (3) 唯一のインスタンスを得るためのstaticメソッドを保持

Singleton Methodパターンを使うメリット

本書には以下のように記載されています。

インスタンスが1つしかないという保証があれば、その前提条件の下でプログラミングできることになります

インスタンスが1個しか無い事を保証して何が良くなるのか正直よくわかりません...

まとめ

Singletonパターンとは、生成するインスタンスの数を1つに制限するデザインパターン

参考文献

Java言語で学ぶデザインパターン入門

Factory Method-インスタンス作成をサブクラスにまかせる-

Java言語で学ぶデザインパターン入門 の第4章を参考にしました。

Factory Methodパターンとは

本書には以下のように記載されています。

インスタンス生成のための枠組みをスーパークラス側で定め、実装はサブクラスが行う

つまり、前回の記事のTemplate Methodパターンの処理の枠組みにおいて、 インスタンス生成を組み込んだものです。 'factory'は、日本語で'工場'という意味があります。 スーパークラスが'工場'の役割を果たし、そこからサブクラスが生成されるというイメージです。

Factory Methodパターンの登場人物

実装

こちらを参考にしてください。

Factory Methodパターンを使う場合

大まかな処理は以下のような形になります。

Factory factory = new IDCardFactory();
Product card1 = factory.create("結城浩");
Product card2 = factory.create("とむら");
Product card3 = factory.create("佐藤花子");
card1.use();
card2.use();
card3.use();

Factory Methodパターンを使わない場合

これと同等の処理を素直にJavaで書くとこうなると思います。 この変更を行うには、IDCardコンストラクタをpublicに変更する必要があります。

Product card1 = new IDCard("結城浩");
Product card2 = new IDCard("とむら");
Product card3 = new IDCard("佐藤花子");
card1.use();
card2.use();
card3.use();

Factory Methodパターンを使うメリット

上の2つを見比べると「Factory Methodパターン使わない方が楽なんじゃね...」となるかもしれません。 確かにこの処理を動かすためだけには、Factory Methodパターンを使わない方が楽だと思います。 しかし、Factory Methodには、mainクラスを具体的なクラス名による束縛から解放する というメリットがあります。

Factory Methodパターンの特徴は、Mainクラスにおいて、new演算子によるインスタンス生成を行わずに メソッド呼び出しに置き換えていることです。 これにより、Mainクラスは具体的なクラス(IDCardクラス)を知らずともインスタンス生成をすることが可能となります。

まとめ

インスタンス生成役とインスタンスの中身の実装役に分けているのがFactory Methodパターン。

Solidityでスマートコントラクトの構築を学習できる、CryptoZombiesはこのデザインパターンを用いてますね。

Templete Method-具体的な処理をサブクラスにまかせる-

Java言語で学ぶデザインパターン入門 の第3章を参考にしました。

Templete Methodパターンとは

本書には以下のように記載されています。

スーパークラスで処理の枠組みを定め、サブクラスでその具体的内容を定める。

Templeteには、「雛形」という意味があります。 Templete Methodパターンとは、この「雛形」を利用したデザインパターンです。

Templete Methodパターンの登場人物

  • AbstractClass(抽象クラス役)
    • Templete Methodで使っている抽象メソッドを宣言
    • Templete Methodを実装
  • ConcreteClass(具象クラス役)
    • AbstractClassで宣言された抽象メソッドを実装

実装

こちらを参考にしてください。

  • AbstractClassであるAbstractDisplayクラスのdisplayメソッド(これがTemplete Method)において、以下のようなロジックを実装している。
    • openメソッドを呼ぶ
    • printメソッドを5回呼ぶ
    • closeメソッドを呼ぶ
  • ConcreteClassであるCharDisplayクラスやStringDisplayクラスにおいて、上記の3つのメソッドを実装している。

Templete Methodパターンを使うことのメリット

  • ロジックと実装を切り離すことができる。
  • ロジックを共通化することができる。

まとめ

抽象クラスはインスタンスを作ることができません。 さらに、抽象メソッドにはメソッドの本体が書かれていないので、具体的な処理はわかりません。 しかし、Template Methodパターンのように抽象メソッドを使って、 Templete Methodのアルゴリズムを書くことはできます。 このように責務を分けることによって、オブジェクト指向らしさが出るのではないかなと思ってます。

Adapter-皮をかぶせて再利用-

Java言語で学ぶデザインパターン入門 の第2章を参考にしました。

Adapterパターンとは

本書には以下のように記載されています。

「既に提供されているもの」と「必要なもの」の間のズレを埋めるようなデザインパターン

Adapterの元である「adapt」という単語には、「適合させる」という意味があります。 Adapterという言葉は、身近では、「ACアダプター」という言葉でよく耳にすると思います。 これは、コンセントに流れている交流(既に提供されているもの)を直流(必要なもの)に変換しています。 これがAdapterの役割です。

Adapterパターンの登場人物

登場人物 役割 ノートPCの例
Target
(対象)
今必要となっているメソッドを定める 直流12ボルト
Client
(依頼者)
Targetのメソッドを使うもの ノートPC
Adaptee
(適合される側)
既に用意されたメソッドを持っている 交流100ボルト
Adapter
(変換)
Adapteeのメソッドを使って、
Targetのメソッドを満たす
ACアダプター

実装

Adapterパターンでは、継承による実装とインスタンスによる実装の2種類があります。 詳しくは、こちらを参考にしてください。

この実装では、Bannerクラスを「既に提供されているもの」、Printインターフェースが「必要なもの」とし、 Printインターフェースの実装であり、Bannerクラスを継承したPrintBannerクラスがアダプターの役割を担っています。 (継承による実装の場合)

なぜAdapterパターンを使うのか?

このような実装のどこが良いのでしょうか? それは、既に存在しているクラスを再利用できることです。 今回の例では、Bannerクラスもこのために実装しましたが、 Bannerクラスが十分にテストされ、バグが少なく、これまでにも実際に使われてきた実績があると仮定したなら、 そのままの形でBannerクラスを再利用したいですよね。

実装のために既存クラス(Bannerクラス)を修正してしまうと、動作テストが済んでいる既存クラスをもう一度テストしなければなりません。 Adapterパターンでは、既存クラスをラップするようなクラス(PrintBannerクラス)を作るので、 もしバグが見つかっても、既存クラスはバグなく動くことが保証されているので、ラップしたクラスのみを検証すれば良いということになります。

まとめ

クラスの再利用はとても大事

Iterator -1つ1つ数え上げる-

Java言語で学ぶデザインパターン入門 の第1章を参考にしました。

Iteratorパターンとは

本書には、以下のように記載されています。

何かがたくさん集まっているときに、それを順番に指し示していき、全体をスキャンする処理を行うもの。

簡単に言うと、for文を一般化したような感じですね。 Iterateとは、繰り返すという意味、Iteratorとは、反復子という意味です。 特徴として、数え上げの仕組みがAggregate役の外に置かれています(詳細は後述)。

Iteratorパターンの登場人物

  • Iterator(反復子):順番にスキャンする
  • Aggregate(集合体):Iterator役を作り出す
    f:id:kakke18:20190409013326p:plain
    Iteratorパターンのクラス図

実装

こちらを参考にしてください。

なぜIteratorパターンを使うのか?

以下のコードはIteratorパターンを使ったMainクラスです。

public class Main {
    public static void main(String[] args) {
        BookShelf bookShelf = new BookShelf(4);
        bookShelf.appendBook(new Book("Around the World in 80 Days"));
        bookShelf.appendBook(new Book("Bible"));
        bookShelf.appendBook(new Book("Cinderella"));
        bookShelf.appendBook(new Book("Daddy-Long-Legs"));
        Iterator it = bookShelf.iterator();
        while (it.hasNext()) {
            Book book = (Book)it.next();
            System.out.println(book.getName());
        }
    }
}

一方、こちらのコードは素直にfor文を回しているMain2クラスです。

public class Main2 {
    public static void main(String[] args) {
        BookShelf bookShelf = new BookShelf(4);
        bookShelf.appendBook(new Book("Around the World in 80 Days"));
        bookShelf.appendBook(new Book("Bible"));
        bookShelf.appendBook(new Book("Cinderella"));
        bookShelf.appendBook(new Book("Daddy-Long-Legs"));
        for (int i = 0; i < bookShelf.getLength(); i++) {
            Book book = bookShelf.getBookAt(i);
            System.out.println(book.getName());
        }
    }
}

この二つのコードの大きな違いは、ループ部分です。 一見すると、抽象クラスなどややこしいものを使用していないMain2クラスの方が優れているように見えます。 しかし、オブジェクト指向的には、Mainクラスの方が優れた設計であると言えます。

では、なぜIteratorを使うのでしょうか? それは、MainクラスがBookShelfの実装に依存していないからです。

Mainクラスでは、BookShelfクラスのメソッドを使用していません。 しかし、Main2クラスでは、BookShelfクラスのメソッド(getLength()やgetBookAt())を使用しています。 つまり、Main2クラスはBookShelfクラスに依存していることになります。

これの何が問題なのでしょうか? 例えば、BookShelfの実装を配列ではなく、java.util.Vectorを使うように変更したり、 プログラムからではなくDBなど外部から参照するように変更した場合、 Main2クラスでは、ループ部分まで変更する必要があります。 しかし、Mainクラスでは、BookShelfがどのような変更をされようともiteratorメソッドを持っており、 正しいIterator(hasNext()とnext()を実装しているクラスのインスタンス)を返してくれればループ部分の実装を変更する必要がありません。

しかし、Mainクラスを使用した場合でもBookShelfクラスに変更があれば、BookShelgIteratorクラスは変更しなければなりません。 結局、MainクラスでもMain2クラスでも変更する点は多いじゃん!となりますね(笑)

ですが、今回の論点は変更点の多さではなく、依存度についてです。 各クラスの依存度を下げることにより、クラスの再利用化が促進されます。 これこそオブジェクト指向の三大要素の一つであるポリモーフィズムですね。

感想

高専時代にJavaを習って「抽象クラスとかなんで存在してるの...」とか思っていた若かりし自分に教えてあげたい。

ハイブリッド暗号~安全な鍵配送を目指して~

暗号技術のすべて』の第8章を参考にしました。 今回は、鍵の配送について書いていきます。

鍵配送とは

以前の記事で 紹介した共通鍵暗号では、事前に鍵の共有が必要となります。 その際、鍵の配送を安全に行うことはとても重要なことです。

鍵配送の方法

事前に共有

最も素朴なアプローチです。通信を行う以前に直接会って、鍵を手渡しする方法です。 通信相手と直接会うため、最も安全な方法です。

  • 長所
    • なりすまし、鍵の盗難を防ぐことが可能
  • 短所
    • 通信相手が近くにいるとは限らないので、現実的な方法ではない

鍵配送センタ

信頼できる鍵配送サーバ(以下、サーバと略す)が存在し、 ユーザとサーバ間の通信は安全であると仮定します。 サーバが鍵生成や鍵管理を担い、ユーザはそれに対して、鍵生成リクエストや 鍵要求リクエストを送ることで鍵配送を実現しています。

  • 長所
    • 鍵管理をユーザが行う必要がない
    • 秘密鍵が漏洩したとしても、サーバに鍵生成リクエストを送るだけで良い
  • 短所
    • ネットワークの参加者が増加すると、サーバが管理する鍵の総数が指数的に増加する (n 人で通信を行う場合、n(n-1) / 2個の秘密鍵が必要となる)
    • これにより、DoS攻撃などの対象となりやすい

鍵共有プロトコル

鍵共有プロトコルとは、ユーザの一方が生成した鍵を送るのではなく、 ユーザ同士で協調し合うことで、互いに相手の秘密情報を知らないままに 秘密鍵の共有を実現する手法です。 詳しいアルゴリズムについては説明しませんが、以下のような種類があります。

ハイブリッド暗号

共通鍵暗号公開鍵暗号には、それぞれ以下のような欠点があります。

これらの欠点を補う暗号がハイブリッド暗号です。 Webサイトにおける暗号技術SSL、メールの暗号化のPGPなどに用いられています。

ハイブリッド暗号アルゴリズム

アリスがボブに暗号文を送信する場面を想定します。

  1. アリス:共有鍵暗号の共有鍵 key を生成
  2. アリス:公開鍵暗号の公開鍵 pk で key を暗号化し、ボブに送信
  3. ボブ:公開鍵暗号秘密鍵 sk で key を復号
  4. アリス:共通鍵暗号秘密鍵 key で暗号文を生成、ボブに送信
  5. ボブ:共通鍵暗号の共有鍵 key で暗号文を復号

f:id:kakke18:20190308021221j:plain

つまり、公開鍵暗号によって、共通鍵暗号の共有鍵を共有しています。

まとめ

鍵共有には以下の方法がある。

  • 直接会う
  • 鍵配送センタ
  • 鍵共有プロトコル
  • ハイブリッド暗号

ディジタル署名は公開鍵暗号と対称関係にあるのか

暗号技術のすべて』の第7章を参考にしました。 ディジタル署名電子署名)についてです。

ディジタル署名はよく「公開鍵暗号とは対称関係である」といわれますが、そうではないということを説明していきます。

ディジタル署名とは

署名とは、「本人であることを明らかにするために自分の氏名を記すこと」です。

  • 現実世界:文書に捺印
  • 電子的:電子文書に捺印に相当するデータを付与

このように電子的に署名することを ディジタル署名といいます。

ディジタル署名アルゴリズム

ディジタル署名は、

の3つのアルゴリズムで構成されます。 今回は、内部構造は気にせずに入出力だけ書いていきます。

鍵生成アルゴリズム

  • 入力:k(セキュリティパラメータ)
  • 出力:vk(検証鍵)、sk(署名鍵)

検証鍵は誰でも参照できるようにし、署名鍵は他人には秘密にするものです。

署名作成アルゴリズム

  • 入力:m(平文)、sk(署名鍵)
  • 出力:σ(署名)

入力に平文が含まれるので、署名は、平文によって異なるデータとなります。

署名検証アルゴリズム

  • 入力: m(平文)、σ(署名)、vk(検証鍵)
  • 出力:0 or 1

検証が成立すれば1、成立しなければ0を出力します。

ディジタル署名の誤解

アルゴリズムを見ると分かる通り、検証鍵が公開されており、 署名鍵が秘密にされていることから、公開鍵暗号と対比し、

  • 検証鍵=公開鍵
  • 署名鍵=秘密鍵

と捉え,

ディジタル署名秘密鍵で暗号化し、公開鍵で復号する」

と説明されることがありますがこれは大きな誤解です。

公開鍵暗号からディジタル署名が作れるのか?

署名作成アルゴリズム公開鍵暗号の復号アルゴリズム、 署名検証アルゴリズム公開鍵暗号の暗号アルゴリズムを 用いることでディジタル署名アルゴリズムもどきを 作成することが出来ます(下図)。 f:id:kakke18:20190305232253j:plain f:id:kakke18:20190305235359j:plain

これのどこが問題なのでしょうか? 実は、署名検証アルゴリズムに問題があります。 同一の平文に対して、異なる暗号文を出力するような暗号のアルゴリズムを 署名検証アルゴリズムで用いると、検証のための m' が実行したタイミングによって 異なるので、検証がうまくいきません。

結論

以前の記事 で紹介したRSA暗号のように平文空間と暗号文空間が同じな確定的暗号なら 問題ないですが、ElGamal暗号のような確率的暗号では、このような構成に することはできません。

まとめ

公開鍵暗号ディジタル署名は対称関係にありません!!