Skip to content

第 7 章: プロトコルとレコードによるポリモーフィズム

7.1 はじめに

この章からは FizzBuzz プログラムに追加仕様を実装しながら、Clojure におけるポリモーフィズムを学びます。Clojure は関数型言語ですが、defprotocoldefrecord による型ベースのポリモーフィズムを提供しています。

追加仕様

1 から 100 までの数をプリントするプログラムを書け。
ただし 3 の倍数のときは数の代わりに「Fizz」と、5 の倍数のときは「Buzz」とプリントし、
3 と 5 両方の倍数の場合には「FizzBuzz」とプリントすること。
タイプごとに出力を切り替えることができる。
タイプ 1 は通常、タイプ 2 は数字のみ、タイプ 3 は FizzBuzz の場合のみをプリントする。

7.2 TODO リスト

  • タイプ 1 の場合(通常の FizzBuzz)
  • タイプ 2 の場合(数字のみ)
  • タイプ 3 の場合(FizzBuzz の場合のみ)
  • それ以外のタイプの場合(例外を発生)

7.3 値オブジェクト — FizzBuzzValue

defrecord による不変データ構造

Clojure の defrecord は不変のデータ構造を定義します。Java のクラスと異なり、フィールドは変更できません。

;; src/fizzbuzz/domain/model.clj
(ns fizzbuzz.domain.model)

(defrecord FizzBuzzValue [number value])

(defn create-fizz-buzz-value [n]
  (->FizzBuzzValue n (str n)))

->FizzBuzzValue は自動生成されるコンストラクタ関数です。

述語関数

(defn fizz? [n]
  (zero? (mod n 3)))

(defn buzz? [n]
  (zero? (mod n 5)))

(defn fizz-buzz? [n]
  (and (fizz? n) (buzz? n)))

Clojure の慣例として、述語関数(真偽値を返す関数)は名前の末尾に ? を付けます。

7.4 プロトコル — FizzBuzzType

defprotocol によるインターフェース定義

defprotocol は Java のインターフェースに相当します。実装すべき関数のシグネチャを定義します。

;; src/fizzbuzz/domain/type.clj
(ns fizzbuzz.domain.type
  (:require [fizzbuzz.domain.model :as model]))

(defprotocol FizzBuzzType
  "FizzBuzz タイプごとの文字列生成プロトコル"
  (generate-string [this value]
    "FizzBuzzValue から文字列を生成する"))

レコードによるプロトコル実装

各タイプをレコードとして定義し、プロトコルを実装します。

;; タイプ 1: 通常の FizzBuzz
(defrecord FizzBuzzType01 []
  FizzBuzzType
  (generate-string [_this value]
    (let [n (:number value)]
      (cond
        (model/fizz-buzz? n) "FizzBuzz"
        (model/fizz? n) "Fizz"
        (model/buzz? n) "Buzz"
        :else (str n)))))

;; タイプ 2: 数字のみ
(defrecord FizzBuzzType02 []
  FizzBuzzType
  (generate-string [_this value]
    (str (:number value))))

;; タイプ 3: FizzBuzz の場合のみ
(defrecord FizzBuzzType03 []
  FizzBuzzType
  (generate-string [_this value]
    (let [n (:number value)]
      (cond
        (model/fizz-buzz? n) "FizzBuzz"
        (model/fizz? n) "Fizz"
        (model/buzz? n) "Buzz"
        :else ""))))

7.5 テストの実装

;; test/fizzbuzz/domain/type_test.clj
(deftest fizz-buzz-type01-test
  (testing "タイプ 1: 通常の FizzBuzz"
    (let [t (type/->FizzBuzzType01)]
      (is (= "1" (type/generate-string t (model/create-fizz-buzz-value 1))))
      (is (= "Fizz" (type/generate-string t (model/create-fizz-buzz-value 3))))
      (is (= "Buzz" (type/generate-string t (model/create-fizz-buzz-value 5))))
      (is (= "FizzBuzz" (type/generate-string t (model/create-fizz-buzz-value 15)))))))

テスト実行 → GREEN。

7.6 まとめ

この章では以下のことを学びました。

  • defrecord による不変データ構造(値オブジェクト)の定義
  • defprotocol によるインターフェースの定義
  • レコードによるプロトコルの実装(ポリモーフィズム)
  • 述語関数の命名規則(? サフィックス)
  • let によるローカルバインディング