LL脳がscalaの勉強を始めたよ その67

Scalaコップ本19章の続きをやっていきますよー、今回は前回作成した関数型待ち行列Queueクラスを改良しつつ型パラメータについてみていきますよ。ちなみに前回実装したQueueクラスはこんな感じですねー

class Queue[T](
  // 待ち行列の正順リスト
  private val leading:List[T],
  // 待ち行列の逆順リスト
  private val trailing:List[T]
){
  // 2つのリストの同期をとる処理です
  private def mirror = {
    if(leading.isEmpty) new Queue(trailing.reverse, Nil)
    else this
  }
  // 同期をとった正順リストから先頭要素を取りますデス
  def head = mirror.leading.head
  
  // 同期をとった正順リストからtailを
  // 逆順リストはそのままの待ち行列を返します
  def tail = {
    val q = mirror
    new Queue(q.leading.tail, q.trailing)
  }
  
  // 逆順リストについて先頭(待ち行列的には末尾)に要素を追加します
  def append(x:T) = new Queue(leading, x :: trailing)
}

情報隠蔽

前回実装したQueueクラスは処理効率という点を追求しすぎたせいか実装が複雑化して、かつそれが外部にも影響しているという困った状態でした(´・ω・`)とのこと。特にパラメータを2つとって、ソレが該当の待ち行列の正順・逆順っていうのは直感的じゃないよね(´・ω・`)ということなので、ここらへんを情報隠蔽していく方法をやっていきましょーか。

非公開コンストラクターとファクトリーメソッド

まずは基本コンストラクターを隠蔽しますね。Queueクラスではパラメータをクラスフィールドとして使うようにScala的基本コンストラクタの暗黙定義がされていましたが、こいつを隠蔽していますよ。

基本コンストラクタの隠蔽はパラメータリストの前にprivateを宣言します

// private修飾子で基本コンストラクタを隠蔽しますよ
class Queue[T] private (
  private val leading:List[T],
  private val trailing:List[T]
){}

ただし、基本コンストラクタを隠蔽してしまうとクラス内部とコンパニオンオブジェクトからしかアクセスできなくなるので、そもそもQueueクラスのコンストラクタ呼出はできなくなってしまうのです(´・ω・`)ナンテコッタイ、まあ型としては使えるんですけども

scala> new Queue(List(1,2,3), List(4,5))
<console>:6: error: constructor Queue cannot be accessed in object $iw
       new Queue(List(1,2,3), List(4,5))
       ^

なので、基本コンストラクタの隠蔽をしつつもQueueオブジェクトを生成するために補助コンストラクタを作って対処してみます。補助コンストラクタの候補としてはこんな感じのものになるですかね?

// 空の待ち行列を作る補助コンストラクタ
def this() = this(Nil, Nil)

// 連続パラメータを初期要素とする補助コンストラクタ
def this(elems:T*) = this(elems.toList, Nil)

補助コンストラクタを実際に定義してみますよ

// private修飾子で基本コンストラクタを隠蔽しますよ
class Queue[T] private (
  private val leading:List[T],
  private val trailing:List[T]
){
  // 連続パラメータを初期要素とする補助コンストラクタ
  def this(elems:T*) = this(elems.toList, Nil)
}

// 呼出はこんな感じですね
scala> new Queue(1,2,3,4,5)
res5: Queue[Int] = Queue@149e631

情報隠蔽された基本コンストラクタの利用には補助コンストラクタ以外にもコンパニオンオブジェクトを利用するっつー手があるみたいです。

class Queue[T] private (
  private val leading:List[T],
  private val trailing:List[T]
){}
// コンパニオンオブジェクトを定義
object Queue {
  // 初期要素xsを利用して待ち行列を構築しますね
  def apply[T](xs:T*) = new Queue[T](xs.toList, Nil)
}

…のはずが、対話型コンソールだと上手くいかないのは何故(´;ω;`)コンパイルは通るんだけどな

<console>:7: error: constructor Queue cannot be accessed in object Queue
         def apply[T](xs:T*) = new Queue[T](xs.toList, Nil)
                               ^

とりあえず、applyメソッドを定義することでグルーバルメソッドの用にQueueオブジェクトを利用することができるので、こちらが一番よさ気ですな(`・ω・´)

非公開クラスというもうひとつの方法

情報を隠蔽するためにはコンストラクタとメンバを非公開にするのではなく、クラス自体を非公開にしてしまうっていう手もあるらしいです。でも、非公開クラスだとアクセス出来ないじゃんと思うのでが…でもそんな時でもトレイトを使えば大丈夫(`・ω・´)(`・ω・´)シャキーン

具体的にはクラスをオブジェクトの中で非公開にしてしまって、公開インターフェースをトレイトとして外出ししてしまうような構成を組むみたいです。例えばこんな感じのコードになるそうです。

// 外部からアクセスすための公開インターフェースとしてのトレイト
trait Queue[T]{
  def head:T
  def tail:Queue[T]
  def append(x:T):Queue[T]
}

// クラス実装自体を格納するオブジェクトです
object Queue{
  // 待ち行列クラスの実装を行う非公開クラスです
  // 実装内容はこれまでのQueueと同じですネ
  private class QueueImpl[T](
    private val leading:List[T],
    private val trailing:List[T]
    // トレイトミックスインです
    ) extends Queue[T]{
      def mirror = {
        if(leading.isEmpty) new QueueImpl(trailing.reverse, Nil)
        else this
      }
      def head:T = mirror.leading.head
      def tail:QueueImpl[T] = {
        val q = mirror
        new QueueImpl(q.leading.tail, q.trailing)
      }
      def append(x:T) = new QueueImpl(leading, x::trailing)
    }
  // オブジェクト生成用のapplyメソッドデス
  def apply[T](xs:T*):Queue[T] = new QueueImpl[T](xs.toList, Nil)
}

//// んじゃ実行してみますよ
// ファクトリーオブジェクトでQueueオブジェクトを生成します
scala> val q = Queue(1,2,3,4,5)
q: Queue[Int] = Queue$QueueImpl@14943d6

// headメソッドです
scala> q.head                  
res37: Int = 1

// tailメソッドです
scala> q.tail                  
res38: Queue[Int] = Queue$QueueImpl@aaf64e

// appendですよ
scala> q.append(6)             
res39: Queue[Int] = Queue$QueueImpl@b2f2d6

うん、これだとかなりすっきりしますネ。つかトレイトってこんな使い方も出来るのか(`・ω・´)と今更ながら感心。トレイト使いこなせるようになりたいなぁ(´・ω・`)

いじょー

今回も時間切れでココマデですよ(´・ω・`)、次回はアノテーションとかやります