- 投稿日:2019-01-27T17:41:12+09:00
JavaDo #14 Kotlinハンズオン
- 投稿日:2019-01-27T17:40:47+09:00
Java開発者に送るKotlinのClass part.2
可視性
JavaとKotlinの可視性はやや異なります。
- Kotlinのデフォルト可視性は
public- Kotlinに
package privateは無い- Kotlin独自の可視性
internalがある (同一モジュール内で参照可)- Kotlinはデフォルトで継承不可 (Javaのfinal扱い)
継承
KotlinでAnimalクラスを継承したDogクラスを定義してみます。
open class Animal { // 継承可なクラスにするためopenをつける open fun greet() { // オーバーライド可能な関数にするためopenをつける } } class Dog: Animal() { // コロンに続けて親クラス名を override fun greet() { // override修飾子をつける println("Bow wow") } }クラスを継承するときは、
classサブクラス名:スーパークラスのコンストラクタと記述します。コンストラクタに引数が必要な場合を見てみましょう。
open class Animal(val name: String) { open fun greet() { } } // Dogのコンストラクタ引数 `name` をAnimalクラスのコンストラクタに渡す class Dog(name: String): Animal(name) { override fun greet() { println("Bow wow") } }コンパニオンオブジェクト
Kotlinにはstatic修飾子はありません。
(あれ?無いですよね?)Kotlinのクラスでstaticメンバやstatic関数を扱いたい場合は、
コンパニオンオブジェクトを使います。コンパニオンオブジェクトとは、
特別なシングルトンインスタンスです。class Dog { companion object { val ancestorName: String = "wolf" } fun greet() { println("Bow wow") } } val ancestor = Dog.ancestorName
companion objectの中でancestorNameというプロパティを定義しました。
このプロパティにアクセスするにはDog.ancestorNameと、staticメンバのようにアクセスします。※ただし、Javaとの互換性のため、staticとしてコンパイルするためのアノテーションは存在します。
object宣言
Kotlinにはシングルトンを簡単に実装する仕組みがあります。
object ResourceManager { fun clean() { // ... } } ResourceManager.clean()
objectキーワードに続けてシングルトンなクラスを定義できます。
通常のクラスと同様にプロパティや関数を定義できますが、
object宣言ではコンストラクタだけ定義できません。
シングルトンなインスタンスにアクセスするにはクラス名を用います。
Javaのstaticメンバへのアクセスに似ています。dataクラス
データを保持するためのシンプルなクラスは、頻繁に実装しますよね?
data class Point(val x : Int, val y: Int) { }
dataキーワードに続けてクラスを定義します。
data classはプライマリコンストラクタで宣言されたプロパティを全て、以下の関数で考慮します。
- equals関数...一致チェックにプロパティの値を考慮
- hashCode関数...ハッシュ値生成にプロパティの値を考慮
- toString関数...プロパティの値を出力
また、copy関数が自動生成されます。
これは、任意のプロパティを変更しながらコピーすることができます。fun moveTo(point: Point, newX: Int): Point { return point.copy(x = newX) }プロパティは全てvalで宣言し、クラスはImmutableとしながら、
値を書き換えたコピーは簡単に生成できます。sealedクラス
sealedクラスは同一ファイル内でしか継承できないクラスです。
sealed class Animal { } class Dog: Animal() { } class Cat: Animal() { }fun greet(animal: Animal) = when(animal) { is Dog -> "bow wow" is Cat -> "meow meow" // else -> elseケースは不要! }上記のgreet関数の中のwhenにご注目ください。
elseケースがありません。
これはsealedクラスの効果です。innerクラス
Javaではクラスの中にクラスを定義する場合、static修飾子をつけるか否かで意味合いが変わります。Kotlinの記述方法と比較してみます。
※あるクラスの中に定義されたAというクラス
Java Kotlin static class A class A class A inner class A Kotlinのデフォルトは
Javaのstatic付きネストクラスと同義となります。プロパティとバッキングフィールド
Kotlinのプロパティは1行で宣言できますが、
ゲッター、セッターをそれぞれカスタマイズすることが可能です。class Dog(name: String) { var name: String = name get() { // カスタムゲッター // ゲッターの中ではfield変数が利用できる // このゲッターはnameに"ちゃん"をつけて返す return field + "ちゃん" } set(value) { // カスタムセッター // セッターは引数名を指定する // このセッターは名前の前から空白を除去してフィールドにセットする field = value.trimMargin() } }デリゲート
Kotlinのクラスはデフォルトで継承不可です。
継承をせずに機能を拡張していく仕組みがあります。Movableというインターフェースと、
Movableを実装したMovableImplクラスを定義しました。interface Movable { fun walk() fun run() fun currentCount(): Int } class MovableImpl: Movable { private var count: Int = 0 override fun walk() { this.count += 1 } override fun run() { this.count += 3 } override fun currentCount(): Int = this.count }このMovableの機能を拡張したDogクラスを定義します。
class Dog(movable: MovableImpl): Movable by movable { }
byキーワードに続けて、Movableの機能を委譲するインスタンスを指定します。
このDogクラスはMovableインターフェースに関する実装が何もありませんが、
自動的にMovableImplに委譲されます。val dog = Dog(MovableImpl()) dog.walk() dog.walk() dog.run() println(dog.currentCount()) // 5プロパティデリゲート
プロパティのゲッター、セッターの振る舞いを委譲することができます。
このようなクラスを定義します。class Dog { var name: String by NameFormatter() } class Cat { var name: String by NameFormatter() }DogクラスもCatクラスもnameというプロパティを持ちます。
どちらもbyに続いてNameFormatterを指定しています。
下のコードを実行してみます。val dog = Dog() dog.name = " ぽち" println(dog.name) //ぽちちゃん val cat = Cat() cat.name = " たま" println(cat.name) //たまちゃんいずれも、nameに代入した文字列から空白を除去し、
ゲッターではサフィックスとして"ちゃん"が付加されています。それでは、NameFormatterの実装を見てみましょう。
class NameFormatter { private var name: String = "" operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return this.name + "ちゃん" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { this.name = value.trimStart() } }
operatorキーワードに続けて、getValueメソッドとsetValueメソッドを実装します。このように、プロパティの挙動を外部に委譲することができます。
- 投稿日:2019-01-27T17:40:04+09:00
Java開発者に送るKotlinのNull安全
null許容型とnull非許容型
Kotlinの型システムはnullになり得る型と、
なり得ない型を区別します。
nullになり得る型は型名の後に?をつけます。val num: Int = null // コンパイルエラー nullになり得ない val num: Int? = null // null許容型であればOKnull許容型の関数やプロパティに直接アクセスすることは出来ません。
コンパイルエラーとなります。val num: Int? = null // null許容型として宣言 val floatNum = num.toFloat() // コンパイルエラーNullPointerExceptionのリスクが大きく軽減されます。
安全呼び出し
Javaでこのようなnullチェックは良く見かけるかと思います。
// Java Dog dog = //nullかも知れない dog.run(); // NullPointerExceptionの可能性アリ if (dog != null) { dog.run(); // Nullチェック済み }同じことをKotlinでも実装してみます。
val dog: Dog? = //nullかも知れない dog.run() // コンパイルエラー if (dog != null) { dog.run() // nullチェック済みのブロックではOK }null許容型オブジェクトの関数は直接呼び出すことが出来ません。
nullチェック済みのifブロックの中ではnull非許容型に自動的にキャストされ、
関数を呼び出すことが出来ます。
これをスマートキャストと言います。また、Kotlinにはもっと便利な糖衣構文があります。
val dog: Dog? = // nullかも知れない dog?.run()
?.に続けて関数の呼び出しやプロパティにアクセスできます。
上の例だとdogがnullだった場合は、ただnullが返されるだけです。
これを安全呼び出しと言います。安全キャスト
Javaでこのようなダウンキャストのコードを見かけるかと思います。
/* * DogクラスはAnimalクラスのサブクラスです */ Animal animal = // ... ((Dog)animal).run(); // ClassCastExceptionの可能性アリ if (animal instanceof Dog) { Dog dog = (Dog)animal; dog.run(); }同じことをKotlinで実装してみます。
/* * DogクラスはAnimalクラスのサブクラスです */ val animal: Animal = // ... (animal as Dog).run() // // ClassCastExceptionの可能性アリ if (animal is Dog) { animal.run() }Kotlinのキャストは
as演算子を使います。
キャストに失敗るすとClassCastExceptionがスローされます。Javaの
instanceof相当するのはis演算子です。
こちらもスマートキャストによって、
ifブロックの中で変数animalはDog型に自動的にキャストされ、
Dogクラスのメソッドがそのまま呼び出せます。また、Kotlinにはもっと便利な糖衣構文があります。
val animal: Animal = // ... (animal as? Dog)?.run()
as?演算子によって、上の例ですとDog?型にキャストされます。
キャストできない場合はnullが返るため安全です。非null表明
安全呼び出し(
.?をつける) 以外にnull許容型を扱う方法があります。val num: Int? = // nullかも知れない val floatNum = num.toFloat() // コンパイルエラー val floatNum = num!!.toFloat() // コンパイルOK しかしNullPointerExceptionの可能性アリ
.!!を付けます。
必要な場面もありますが、極力使うべきではないでしょう。エルビス演算子
Javaでこのような分岐処理をよく見かけるかと思います。
// Java Dog dog = getDog(); // getDogの戻り値はnullかも知れない if (dog == null) { dog = new Dog(); } dog.run();kotlinには「もしnullだったら」の時に便利な演算子があります。
val dog = getDog() ?: Dog() dog.run()
?:エルビス演算子の
左側にはnull許容型の変数
右側にはnullだった場合に実行される処理を記述します。
代替オブジェクトの生成やExceptionをthrowするのが一般的でしょう。非null許容型のクラスプロパティ
クラスのプロパティを宣言する際に、null許容型にせざるを得ない場合があるかと思います。
コンストラクタをオーバーライド出来ないフレームワークのクラス、
あるコールバックの後でなければ初期化出来ないプロパティなど...しかしそれらは通常、null許容型で扱うのは面倒で、実質的に非null許容型で扱いたい場合があります。 (実際にKotlinで開発しているとよくあります)
そんな時は
lateinit修飾子が便利です。// ログイン用のViewクラス class LoginView { // TextViewやButtonなどの子要素を持つが、 // これらはリソースから生成される...という仮想UIシステム lateinit var emailTextView: TextView lateinit var passwordTextView: TextView lateinit var loginButton: Button // Viewがリソースからロードされると子要素が取得可能となる fun onLoadViewFromResource(viewResource: View) { this.emailTextView = viewResource.children[0] this.passwordTextView = viewResource.children[1] this.loginButton = viewResource.children[2] } }
lateinit修飾子は、初期化を遅らせることが出来ますが、
初期化前にプロパティにアクセスするとExceptionとなるので注意が必要です。この他に、
lazyというプロパティデリゲートがあります。
初期化にコストのかかるプロパティなどを遅延初期化させる時に便利です。class LoginView { val loginConnection: LoginConnection by lazy { LoginConnection() // LoginConnectionは生成にコストがかかる } }
- 投稿日:2019-01-27T17:38:50+09:00
Java開発者に送るKotlinの関数とラムダ
関数の定義
関数は以下のように定義します。
// Int型の引数をインクリメントして返す関数 fun increment(i: Int): Int { return i + 1 }fun
関数名(引数リスト):戻り値の型
これが基本形です。名前付き引数
引数の多い関数の呼び出しでは、どの値がどの引数に対応しているのか
分かりづらくなることがあります。Kotlinはこの問題を解消する機能があります。
/* * たこ焼きを注文します * * @param isKarashiMayo からしマヨネーズを選択するか否か * @param isPutAonori 青のりののせるか否か * * @return たこ焼きのインスタンスが返ります */ fun oderTakoyaki(isKarashiMayo: Boolean, isPutAonori: Boolean): Takoyaki { // ... } val takoyaki = oderTakoyaki(true, false) // どっちフラグが青のりだっけ? val takoyaki = oderTakoyaki(isKarashiMayo = true, isPutAonori = false) // 呼び出し側に仮引数名をつけることができます // コードの意図が読み取り易い val takoyaki = oderTakoyaki(isPutAonori = false, isKarashiMayo = true) // 名前を付けると、関数に定義されている順番でなくてもOKデフォルト引数
引数の数が違うだけのオーバーロードが多数存在するクラスが、よくあると思います。
例えば...Kotlinではデフォルト引数を用いることで、このようにバリエーションが増えてしまう状況を回避できます。
/* * たこ焼きを注文します * * @param isKarashiMayo からしマヨネーズを選択するか否か * @param isPutAonori 青のりののせるか否か * * @return たこ焼きのインスタンスが返ります */ fun oderTakoyaki(isKarashiMayo: Boolean = false, isPutAonori: Boolean = true): Takoyaki { // ... } // 呼び出し時に引数を省略可能。省略した場合は、デフォルト値が使用される。 val takoyaki = oderTakoyaki()拡張関数
既存のクラスに、後から勝手に関数を追加できます。
独自の日付フォーマットでパースする関数をString型に追加してみましょう。fun String.toDate(): Date { val format = SimpleDateFormat("yyyy年MM月dd日") return format.parse(this) } val str = "2019年03月09日" val date = str.toDate()fun
拡張対象の型.関数名(引数リスト):戻り値
↑のように定義します。
ちなみに、通常の関数は...
fun関数名(引数リスト):戻り値の型
↑こうでした。
関数名の前に拡張対象の型をつけると拡張関数になります。ちなみに、この拡張対象のオブジェクトをKotlinではレジーバーオブジェクトと呼びます。
単一式関数
関数が1つの式からなる場合は、
{ }を省略することが可能です。
単一式関数は戻り値の型が推論可能な場合、戻り値の型を省略することもできます。// 通常の関数 fun double(x: Int): Int { return x * 2 } // 単一式関数で記述 fun double(x: Int): Int = x * 2 // 単一式関数で戻り値の型を推論 fun double(x: Int) = x * 2ラムダ式
ラムダ式は↓のように書きます。
val sum = { x: Int, y: Int -> x + y }{
引数リスト->本体}
または引数がなければ
{本体}
となります。ラムダ式のパラメータが1つの場合は暗黙の変数
itが使用できます。val double: (Int)->Int = { it * 2 }尚、関数型は次のように記述します。
(引数の型) ->戻り値の型inline関数
ラムダ式は実行コストの高い仕組みです。
Kotlinのラムダ式は無名クラスとして実現されています。変数をキャプチャしたラムダ式は実行されるたびに無名クラスのインスタンスが生成されます。
(Kotlin1.1 Java8バイトコード生成は直接Javaのラムダ式にコンパイルされるらしい)そこで、inline関数です。
inline関数は呼び出される箇所に関数本体を置き換えてコンパイルされます。inline fun edit(context: DataBaseContext, block: (DataBaseContext.Transaction)->Unit) { val transaction = context.beginTransaction() block(transaction) transaction.commit() }inline関数が効力を発揮するのは
引数にラムダ式を受け、それを内部で実行する関数です。
inline関数では、引数のラムダ式も一緒に呼び出し元に展開します。上のinline関数
editを呼び出すサンプルです。val dataBaseContext = DataBaseContext() edit(dataBaseContext) { transaction -> val newObject = Any() transaction.add(newObject) }
edit関数はコンパイル時に展開され、次のようなコードになります(イメージです)val dataBaseContext = DataBaseContext() // ここからedit関数が展開 val transaction = dataBaseContext.beginTransaction() val newObject = Any() transaction.add(newObject) transaction.commit() // ここまでedit関数が展開もう一つのinlineの出番
Kotlinのジェネリックは実行時に型情報は失われます。
例えば、次の関数はコンパイルエラーとなります。
fun <T> mapFilter(list: List<Any>): List<T> { return list.filter { it is T } .map { it as T } }
mapFilter関数の型パラメータTは実行時に失われるためです。
そこで、inline関数にreified修飾子を付加します。// ↓ここ! inline fun <reified T> mapFilter(list: List<Any>): List<T> { return list.filter { it is T } .map { it as T } }このようにすることで、実行時に型情報を保持したまま展開されるため
mapFilter関数がコンパイルできるようになります。
- 投稿日:2019-01-27T17:37:43+09:00
Java開発者に送るKotlinのClass
Java開発者の方に向けて、Kotlinのクラスについて書きます。
クラスの定義
classキーワードに続いて、クラス名を記述します。
Dogクラスを定義してみます。class Dog { }クラスの継承やインターフェースの実装はコロンに続けて記述します。
先ほどのDogクラス、Animalクラスを継承させてみます。class Dog: Animal() { }さらに、Walkableインターフェースを実装してみましょう。
class Dog: Animal(), Walkable { }プロパティ
Kotlinのクラスはプロパティを持つことができます。
これはJavaのフィールド、ゲッター、セッターを合わせたものです。
DogクラスにnameというString型のプロパティを宣言してみましょう。
varキーワードを使います。class Dog { var name: String = "pochi" } val dog = Dog() val aName = dog.name // ゲッターとして動作 dog.name = "taro" // セッターとして動作読み取り専用にしたければ、
valキーワードを使いましょう。class Dog { val name: String = "pochi" } val dog = Dog() val aName = dog.name // ゲッターとして動作 dog.name = "taro" // コンパイルエラー!
valキーワードは再代入不可のため、クラス内部からも値を変更できません。
クラス内で変更可能とする場合はprivate setキーワードを使います。class Dog { var name: String = "pochi" private set fun updateName(newName: String) { this.name = newName } } val dog = Dog() dog.updateName("taro")ここで、「いやいや、JavaでフィールドをPublicにしたのと一緒でしょ?」と思った方は鋭いです。
Kotlinのプロパティはゲッター、セッターそしてフィールドが明確に分かれています。
詳しくはこちらプライマリコンストラクタ
Kotlinのコンストラクタは少し変わった位置に書きます。
class Dog constructor(/* ここがコンストラクタ */) { }
constructorキーワードは省略できます。アノテーションを付加する場合などは省略できません。
この位置に書くコンストラクタをプライマリコンストラクタと呼びます。
コンストラクタと言っても、記述できるのはプロパティとコンストラクタの引数リストです。コンストラクタでプロパティ定義する
コンストラクタ引数に
varやvalキーワードをつけると、そのままプロパティになります。class Dog(var name: String) { }↑は↓と同義です。
class Dog(name: String) { var name: String = name }JavaとKotlinを見比べてみる
JavaでDogクラスを定義します。
nameとageというフィールドがあり、それぞれゲッターとセッターが定義されています。public class Dog { private String name; private int age; public Dog(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }これをKotlinで書くと?
class Dog(var name: String, var age: Int)これだけで済んでしまいます。
- 投稿日:2019-01-27T13:56:55+09:00
Googleアシスタント + Kotlinでアクションのバックエンドを作ってみる
はじめに
2019/01/16にActions on Google(AoG)Client LibraryのJava/Kotlin版が発表されました
![]()
- Announcing the Java & Kotlin client library for Actions on Google
- actions-on-google/actions-on-google-java: Java/Kotlin library for Actions on Google
公式では、Kotlin + Google App Engine上のJavaサーブレットで構築する方法が紹介されています。
KotlinにはKtorというWebフレームワークがあります。
今回はフルKotlinでGoogleアシスタントのアクションを構築する方法を紹介します。使う技術
次の表に、各言語で使う技術構成をまとめました。
GoogleアシスタントのバックエンドはNode.js + Cloud Functions For Firebaseの構成で作られることが多いです。
今回は、Kotlin + Ktor + Google App Engine(GAE)で作ります!![]()
Ktor, GAEは環境構築の一番最初の部分から解説していきます。
言語 フレームワーク 実行環境 Kotlin Ktor PaaS: Google App Engien Node.js (Express) FaaS: Cloud Functions For Firebase Ktorについて
Ktor とは、Kotlinの軽量で簡単に使えるWebフレームワークです。
おなじみJetBrains社が開発をしており、安心感があります。去年(2018)の11月にv1.0がリリースされており、Kotlinサーバサイドでの利用が期待が高まっています。
- Ktor 1.0 Released: A Connected Applications Framework by JetBrains | Kotlin Blog構築手順
Actions on GoogleプロジェクトとDialogflowエージェントの作成
こちらは従来どおりの作り方と変わりありません。
Ktorの環境構築
Intellij IDEにKtorプラグインをインストールしてプロジェクトを作成するのが簡単です。
Ktorプロジェクトを作成できたら、Hello Worldを表示してみましょう。
src/Application.ktを↓のように編集し、Gradleタスクの「run」を押して実行してみましょう。
成功すれば、http://0.0.0.0:8080/にアクセスした時に「hello world」がブラウザに表示されます。package com.example.kotlin.aog import io.ktor.application.* import io.ktor.response.* import io.ktor.routing.get import io.ktor.routing.routing fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args) @Suppress("unused") // Referenced in application.conf @kotlin.jvm.JvmOverloads fun Application.module(testing: Boolean = false) { // ↓↓↓↓↓ 以下を追加する ↓↓↓↓↓ routing { get("/"){ call.respond("hello world") } } }AoG Java/Kotlinクライアントライブラリの使い方
build.gradleに以下を追記し、Gradle Syncを実行します。compile "com.google.actions:actions-on-google:1.0.0"IntentHandler作成
新規に
ConversationComponentsApp.ktを作成してください。
Handlerの作成ポイントは
- DialogflowAppを継承させる。
- ForIntentアノテーションを使ってDialogflowで定義したIntent名と紐付ける。
- ResponseBuilderを使ってレスポンスを組み立てる。
コードはこんな感じです。例ではSimpleResponseを使っていますが、
他のBasicCardやCarousel、Tableなどは公式がサンプルをGithubあげています。
気になる方は、こちらを見てみるといいでしょう。class ConversationComponentsApp : DialogflowApp() { @ForIntent("Default Welcome Intent") fun welcome(request: ActionRequest): ActionResponse { val responseBuilder = getResponseBuilder(request) responseBuilder .add(SimpleResponse() .setDisplayText("純情Action!") .setTextToSpeech("純情Action!Action!")) return responseBuilder.build() } }POSTリクエストのエンドポイント用意
続いて、作ったIntentHandlerにDialogflowからのデータを渡せるようにしましょう。
DialogflowからはPOSTリクエストでJSONが送られてくるので、次のコードの通りに/をルートパスとしてJSONを受け取ります。
call.receiveText()でJSONデータを受け取り、
先程作ったしたConversationComponentsApp#handleRequestにヘッダー情報と一緒に渡します。
これによりIntent名に対応してレスポンスが返されます。
最後に、call.respondを使ってDialogflow側に返却してあげます。fun Application.module(testing: Boolean = false) { val actionsApp= ConversationComponentsApp() routing { post("/"){ // body取得 val jsonBody: String = call.receiveText() // header取得 val headerMap = getHeaderMap(call.request) // IntentHandlerにbody,header情報を渡す val jsonResponse = actionsApp.handleRequest(jsonBody, headerMap).get() call.respond(jsonResponse) } } }// ヘッダー情報をHashMapにKey-Valueでつっこんであげる。 fun getHeaderMap(request: ApplicationRequest): Map<String, String>{ val headers= request.headers val headerMap = HashMap<String, String>() headers.forEach{ k, v -> headerMap[k] = v.toString() } return headerMap }GAEへのデプロイ準備
ターミナル編
Google Cloud SDKをMacにインストールし、初期化まで完了させます。
初期化中にGoogle Cloud Platformプロジェクトを選択する必要があります。Actions on GoogleプロジェクトIDと同じものを選びましょう。
設定できたら、現在の設定に間違いないか確認しておくといいでしょう。
$ gcloud config list [app] promote_by_default = true [core] account = <AoGプロジェクト作成時と同じGoogleアカウントのメールアドレス> disable_usage_reporting = False project = <Actions on GoogleのプロジェクトID(GCP Project id)>GAEのJavaプラグインをインストールします。
$ gcloud components install app-engine-java $ gcloud components updateIntellij IDE編
Build.gradleに以下を追加してください。
これで、GAE用のGradleタスクが活用できるようになります。buildscript { ... dependencies { ... classpath "com.google.cloud.tools:appengine-gradle-plugin:1.3.4" } } apply plugin: 'war' dependencies { ... providedCompile "com.google.appengine:appengine:1.9.60" } appengine { tools.cloudSdkHome="<google-cloud-sdkディレクトリへのパス>" }ここは任意ですが、ディレクトリ構成を整理しておきます。
kotlinファイルは、src/main/kotlinに、プロジェクトルートに存在するktorの設定ファイルをsrc/main/resourcesに移動させます。
また、src/main/webappというディレクトリを追加します。詳細については↓を参考ください![]()
- 構成ファイル | Java の App Engine スタンダード環境 | Google Cloud
- https://github.com/ktorio/ktor-samples/tree/master/deployment/google-appengine-standard
ディレクトリツリー構造はこんな感じになります。
... . └── main ├── kotlin │ ├── Application.kt │ └── ConversationComponentsApp.kt ├── resources │ ├── application.conf │ └── logback.xml └── webapp └── WEB-INF ├── appengine-web.xml ├── logging.properties └── web.xmlGAEへデプロイ! と動作確認!
ここまでの設定が完了したら、
./gradlew appengineDeployかIDEのGradleタスクからappengineDeployを実行してください。
成功すると、https://<gcp-project-id>.appspot.comのURLがコンソールに表示されるので、
こちらをメモっておき、DialogflowのFullfillmentに設定すれば完了です!... Deployed service [default] to [https://<gcp-project-id>.appspot.com] ...今回説明したコードはGithubにあげておきました。参考にしてみてください。
- https://github.com/1coin178/actions-on-google-kotlin-ktor-start-kit
- 投稿日:2019-01-27T13:56:55+09:00
Kotlin+Googleアシスタントでアクションのバックエンドを作ってみる
はじめに
2019/01/16にActions on Google(AoG)Client LibraryのJava/Kotlin版が発表されました
![]()
- Announcing the Java & Kotlin client library for Actions on Google
- actions-on-google/actions-on-google-java: Java/Kotlin library for Actions on Google
公式では、Kotlin + Google App Engine上のJavaサーブレットで構築する方法が紹介されています。
KotlinにはKtorというWebフレームワークがあります。
今回はフルKotlinでGoogleアシスタントのアクションを構築する方法を紹介します。使う技術
次の表に、各言語で使う技術構成をまとめました。
GoogleアシスタントのバックエンドはNode.js + Cloud Functions For Firebaseの構成で作られることが多いです。
今回は、Kotlin + Ktor + Google App Engine(GAE)で作ります!![]()
Ktor, GAEは環境構築の一番最初の部分から解説していきます。
言語 フレームワーク 実行環境 Kotlin Ktor PaaS: Google App Engine Node.js (Express) FaaS: Cloud Functions For Firebase Ktorについて
Ktor とは、Kotlinの軽量で簡単に使えるWebフレームワークです。
おなじみJetBrains社が開発をしており、安心感があります。去年(2018)の11月にv1.0がリリースされており、Kotlinサーバサイドでの利用が期待が高まっています。
- Ktor 1.0 Released: A Connected Applications Framework by JetBrains | Kotlin Blog構築手順
Actions on GoogleプロジェクトとDialogflowエージェントの作成
こちらは従来どおりの作り方と変わりありません。
開発環境の準備
↑のGihtubリポジトリにテンプレートを作成しておきました。環境構築の際にはぜひお使いください!
ダウンロード後、Intellij IDEで開いてください。AoG Java/Kotlinクライアントライブラリの使い方
次に本題のAoG Java/Kotlinクライアントライブラリの使い方を解説していきます。
AoG Java/Kotlinクライアントライブラリは、
build.gradleに↓を指定することでダウンロードしています。compile "com.google.actions:actions-on-google:1.0.0"IntentHandler作成
Dialogflowからのリクエストを受け取り処理するためのIntentHandlerを作成します。
新規に
ConversationComponentsApp.ktを作成してください。
IntentHandlerの作成ポイントは
- DialogflowAppを継承させる。
- ForIntentアノテーションを使ってDialogflowで定義したIntent名と処理を紐付ける。
- ResponseBuilderを使ってレスポンスを組み立てる。
コードはこんな感じです。
class ConversationComponentsApp : DialogflowApp() { @ForIntent("Default Welcome Intent") fun welcome(request: ActionRequest): ActionResponse { val responseBuilder = getResponseBuilder(request) responseBuilder .add(SimpleResponse() .setDisplayText("純情Action!") .setTextToSpeech("純情Action!Action!")) return responseBuilder.build() } @ForIntent("Good Bye") fun goodBye(request: ActionRequest): ActionResponse { val responseBuilder = getResponseBuilder(request) responseBuilder .add("会話を終了します。さようなら") .endConversation() // 会話を終了させる。 Node.js版でのconv.close return responseBuilder.build() } }例ではSimpleResponseを使っていますが、他にBasicCardやCarousel、Tableなどのサンプルを公式がGithubあげてくれています。
気になる方は、こちら↓も見てみるといいでしょう!POSTリクエストのエンドポイント用意
続いて、作ったIntentHandlerにDialogflowからのデータを渡せるようにしましょう。
DialogflowからはPOSTリクエストでJSONが送られてくるので、次のコードの通りに/をルートパスとしてJSONを受け取ります。
call.receiveText()でJSONデータを受け取り、
先程作ったしたConversationComponentsApp#handleRequestにヘッダー情報と一緒に渡します。
これによりIntent名に対応してレスポンスが返されます。
最後に、call.respondを使ってDialogflow側に返却してあげます。fun Application.module(testing: Boolean = false) { val actionsApp= ConversationComponentsApp() routing { post("/"){ // body取得 val jsonBody: String = call.receiveText() // header取得 val headerMap = getHeaderMap(call.request) // IntentHandlerにbody,header情報を渡す val jsonResponse = actionsApp.handleRequest(jsonBody, headerMap).get() call.respond(jsonResponse) } } } // ヘッダー情報をHashMapにKey-Valueでつっこんであげる。 fun getHeaderMap(request: ApplicationRequest): Map<String, String>{ val headers= request.headers val headerMap = HashMap<String, String>() headers.forEach{ k, v -> headerMap[k] = v.toString() } return headerMap }GAEへのデプロイ準備
ターミナル編
Google Cloud SDKをMacにインストールし、初期化まで完了させます。
初期化中にGoogle Cloud Platformプロジェクトを選択する必要があります。Actions on GoogleプロジェクトIDと同じものを選びましょう。
設定できたら、現在の設定に間違いないか確認しておくといいでしょう。
$ gcloud config list [app] promote_by_default = true [core] account = <AoGプロジェクト作成時と同じGoogleアカウントのメールアドレス> disable_usage_reporting = False project = <Actions on GoogleのプロジェクトID(GCP Project id)>GAEのJavaプラグインをインストールします。
$ gcloud components install app-engine-java $ gcloud components updateIntellij IDE編
GAE関連の設定を解説します。
build.gradleに↓を記入しておくことで、GAE用のGradleタスクが活用できるようになります。buildscript { ... dependencies { ... classpath "com.google.cloud.tools:appengine-gradle-plugin:1.3.4" } } ... apply plugin: 'war' ... dependencies { ... providedCompile "com.google.appengine:appengine:1.9.60" } appengine { // ご自身の環境に合わせて変更してください。 tools.cloudSdkHome="<google-cloud-sdkディレクトリへのパス>" }テンプレートコードのディレクトリ構成を整理しておきます。
ディレクトリツリー構造はこんな感じになっています。... . └── main ├── kotlin │ ├── Application.kt │ └── ConversationComponentsApp.kt ├── resources │ ├── application.conf │ └── logback.xml └── webapp └── WEB-INF ├── appengine-web.xml ├── logging.properties └── web.xmlkotlinファイルは、
src/main/kotlinに、プロジェクトルートに存在するktorの設定ファイルがsrc/main/resourcesにあります。
src/main/webappというディレクトリがあるのですが、詳細については↓を参考にしてみてください![]()
- 構成ファイル | Java の App Engine スタンダード環境 | Google Cloud
- https://github.com/ktorio/ktor-samples/tree/master/deployment/google-appengine-standard
GAEへデプロイ! と動作確認!
ここまでの設定が完了したら、
./gradlew appengineDeployかIDEのGradleタスクからappengineDeployを実行してください。
成功すると、https://<gcp-project-id>.appspot.comのURLがコンソールに表示されるので、
こちらをメモっておき、DialogflowのFullfillmentに設定すれば完了です!... Deployed service [default] to [https://<gcp-project-id>.appspot.com] ...
- 投稿日:2019-01-27T13:56:55+09:00
Kotlin + Googleアシスタントでアクションのバックエンドを作ってみる
はじめに
2019/01/16にActions on Google(AoG)Client LibraryのJava/Kotlin版が発表されました
![]()
- Announcing the Java & Kotlin client library for Actions on Google
- actions-on-google/actions-on-google-java: Java/Kotlin library for Actions on Google
公式では、Kotlin + Google App Engine上のJavaサーブレットで構築する方法が紹介されています。
KotlinにはKtorというWebフレームワークがあります。
今回はフルKotlinでGoogleアシスタントのアクションを構築する方法を紹介します。使う技術
次の表に、各言語で使う技術構成をまとめました。
GoogleアシスタントのバックエンドはNode.js + Cloud Functions For Firebaseの構成で作られることが多いです。
今回は、Kotlin + Ktor + Google App Engine(GAE)で作ります!![]()
Ktor, GAEは環境構築の一番最初の部分から解説していきます。
言語 フレームワーク 実行環境 Kotlin Ktor PaaS: Google App Engine Node.js (Express) FaaS: Cloud Functions For Firebase Ktorについて
Ktor とは、Kotlinの軽量で簡単に使えるWebフレームワークです。
おなじみJetBrains社が開発をしており、安心感があります。去年(2018)の11月にv1.0がリリースされており、Kotlinサーバサイドでの利用が期待が高まっています。
- Ktor 1.0 Released: A Connected Applications Framework by JetBrains | Kotlin Blog構築手順
Actions on GoogleプロジェクトとDialogflowエージェントの作成
こちらは従来どおりの作り方と変わりありません。
開発環境の準備(テンプレートを利用)
↑のGihtubリポジトリにテンプレートを作成しておきました。環境構築の際にはぜひお使いください!
ダウンロード後、Intellij IDEで開いてください。AoG Java/Kotlinクライアントライブラリの使い方
次に本題のAoG Java/Kotlinクライアントライブラリの使い方を解説していきます。
AoG Java/Kotlinクライアントライブラリは、
build.gradleに↓を指定することでダウンロードしています。compile "com.google.actions:actions-on-google:1.0.0"IntentHandler作成
Dialogflowからのリクエストを受け取り処理するためのIntentHandlerを作成します。
新規に
ConversationComponentsApp.ktを作成してください。
IntentHandlerの作成ポイントは
- DialogflowAppを継承させる。
- ForIntentアノテーションを使ってDialogflowで定義したIntent名と処理を紐付ける。
- ResponseBuilderを使ってレスポンスを組み立てる。
コードはこんな感じです。
class ConversationComponentsApp : DialogflowApp() { @ForIntent("Default Welcome Intent") fun welcome(request: ActionRequest): ActionResponse { val responseBuilder = getResponseBuilder(request) responseBuilder .add(SimpleResponse() .setDisplayText("純情Action!") .setTextToSpeech("純情Action!Action!")) return responseBuilder.build() } @ForIntent("Good Bye") fun goodBye(request: ActionRequest): ActionResponse { val responseBuilder = getResponseBuilder(request) responseBuilder .add("会話を終了します。さようなら") .endConversation() // 会話を終了させる。 Node.js版でのconv.close return responseBuilder.build() } }例ではSimpleResponseを使っていますが、他にBasicCardやCarousel、Tableなどのサンプルを公式がGithubあげてくれています。
気になる方は、こちら↓も見てみるといいでしょう!POSTリクエストのエンドポイント用意
続いて、作ったIntentHandlerにDialogflowからのデータを渡せるようにしましょう。
DialogflowからはPOSTリクエストでJSONが送られてくるので、次のコードの通りに/をルートパスとしてJSONを受け取ります。
call.receiveText()でJSONデータを受け取り、
先程作ったしたConversationComponentsApp#handleRequestにヘッダー情報と一緒に渡します。
これによりIntent名に対応してレスポンスが返されます。
最後に、call.respondを使ってDialogflow側に返却してあげます。fun Application.module(testing: Boolean = false) { val actionsApp= ConversationComponentsApp() routing { post("/"){ // body取得 val jsonBody: String = call.receiveText() // header取得 val headerMap = getHeaderMap(call.request) // IntentHandlerにbody,header情報を渡す val jsonResponse = actionsApp.handleRequest(jsonBody, headerMap).get() call.respond(jsonResponse) } } } // ヘッダー情報をHashMapにKey-Valueでつっこんであげる。 fun getHeaderMap(request: ApplicationRequest): Map<String, String>{ val headers= request.headers val headerMap = HashMap<String, String>() headers.forEach{ k, v -> headerMap[k] = v.toString() } return headerMap }GAEへのデプロイ準備
ターミナル編
Google Cloud SDKをMacにインストールし、初期化まで完了させます。
初期化中にGoogle Cloud Platformプロジェクトを選択する必要があります。Actions on GoogleプロジェクトIDと同じものを選びましょう。
設定できたら、現在の設定に間違いないか確認しておくといいでしょう。
$ gcloud config list [app] promote_by_default = true [core] account = <AoGプロジェクト作成時と同じGoogleアカウントのメールアドレス> disable_usage_reporting = False project = <Actions on GoogleのプロジェクトID(GCP Project id)>GAEのJavaプラグインをインストールします。
$ gcloud components install app-engine-java $ gcloud components updateIntellij IDE編
GAE関連の設定を解説します。
build.gradleに↓を記入しておくことで、GAE用のGradleタスクが活用できるようになります。buildscript { ... dependencies { ... classpath "com.google.cloud.tools:appengine-gradle-plugin:1.3.4" } } ... apply plugin: 'war' ... dependencies { ... providedCompile "com.google.appengine:appengine:1.9.60" } appengine { // ご自身の環境に合わせて変更してください。 tools.cloudSdkHome="<google-cloud-sdkディレクトリへのパス>" }テンプレートコードのディレクトリ構成を整理しておきます。
ディレクトリツリー構造はこんな感じになっています。... . └── main ├── kotlin │ ├── Application.kt │ └── ConversationComponentsApp.kt ├── resources │ ├── application.conf │ └── logback.xml └── webapp └── WEB-INF ├── appengine-web.xml ├── logging.properties └── web.xmlkotlinファイルは、
src/main/kotlinに、プロジェクトルートに存在するktorの設定ファイルがsrc/main/resourcesにあります。
src/main/webappというディレクトリがあるのですが、詳細については↓を参考にしてみてください![]()
- 構成ファイル | Java の App Engine スタンダード環境 | Google Cloud
- https://github.com/ktorio/ktor-samples/tree/master/deployment/google-appengine-standard
GAEへデプロイ! と動作確認!
ここまでの設定が完了したら、
./gradlew appengineDeployかIDEのGradleタスクからappengineDeployを実行してください。
成功すると、https://<gcp-project-id>.appspot.comのURLがコンソールに表示されるので、
こちらをメモっておき、DialogflowのFullfillmentに設定すれば完了です!... Deployed service [default] to [https://<gcp-project-id>.appspot.com] ...
- 投稿日:2019-01-27T13:56:55+09:00
Actions on Google + Kotlin + Ktorでアクションのバックエンドを作ってみる
はじめに
2019/01/16にActions on Google(AoG)Client LibraryのJava/Kotlin版が発表されました
![]()
- Announcing the Java & Kotlin client library for Actions on Google
- actions-on-google/actions-on-google-java: Java/Kotlin library for Actions on Google
公式では、Kotlin + Google App Engine上のJavaサーブレットで構築する方法が紹介されています。
KotlinにはKtorというWebフレームワークがあります。
今回はフルKotlinでGoogleアシスタントのアクションを構築する方法を紹介します。使う技術
次の表に、各言語で使う技術構成をまとめました。
GoogleアシスタントのバックエンドはNode.js + Cloud Functions For Firebaseの構成で作られることが多いです。
今回は、Kotlin + Ktor + Google App Engine(GAE)で作ります!![]()
Ktor, GAEは環境構築の一番最初の部分から解説していきます。
言語 フレームワーク 実行環境 Kotlin Ktor PaaS: Google App Engien Node.js (Express) FaaS: Cloud Functions For Firebase Ktorについて
Ktor とは、Kotlinの軽量で簡単に使えるWebフレームワークです。
おなじみJetBrains社が開発をしており、安心感があります。去年(2018)の11月にv1.0がリリースされており、Kotlinサーバサイドでの利用が期待が高まっています。
- Ktor 1.0 Released: A Connected Applications Framework by JetBrains | Kotlin Blog構築手順
Actions on GoogleプロジェクトとDialogflowエージェントの作成
こちらは従来どおりの作り方と変わりありません。
Ktorの環境構築
Intellij IDEにKtorプラグインをインストールしてプロジェクトを作成するのが簡単です。
Ktorプロジェクトを作成できたら、Hello Worldを表示してみましょう。
src/Application.ktを↓のように編集し、Gradleタスクの「run」を押して実行してみましょう。
成功すれば、http://0.0.0.0:8080/にアクセスした時に「hello world」がブラウザに表示されます。package com.example.kotlin.aog import io.ktor.application.* import io.ktor.response.* import io.ktor.routing.get import io.ktor.routing.routing fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args) @Suppress("unused") // Referenced in application.conf @kotlin.jvm.JvmOverloads fun Application.module(testing: Boolean = false) { // ↓↓↓↓↓ 以下を追加する ↓↓↓↓↓ routing { get("/"){ call.respond("hello world") } } }AoG Java/Kotlinクライアントライブラリの使い方
build.gradleに以下を追記し、Gradle Syncを実行します。compile "com.google.actions:actions-on-google:1.0.0"IntentHandler作成
新規に
ConversationComponentsApp.ktを作成してください。
Handlerの作成ポイントは
- DialogflowAppを継承させる。
- ForIntentアノテーションを使ってDialogflowで定義したIntent名と紐付ける。
- ResponseBuilderを使ってレスポンスを組み立てる。
コードはこんな感じです。例ではSimpleResponseを使っていますが、
他のBasicCardやCarousel、Tableなどは公式がサンプルをGithubあげています。
気になる方は、こちらを見てみるといいでしょう。class ConversationComponentsApp : DialogflowApp() { @ForIntent("Default Welcome Intent") fun welcome(request: ActionRequest): ActionResponse { val responseBuilder = getResponseBuilder(request) responseBuilder .add(SimpleResponse() .setDisplayText("純情Action!") .setTextToSpeech("純情Action!Action!")) return responseBuilder.build() } }POSTリクエストのエンドポイント用意
続いて、作ったIntentHandlerにDialogflowからのデータを渡せるようにしましょう。
DialogflowからはPOSTリクエストでJSONが送られてくるので、次のコードの通りに/をルートパスとしてJSONを受け取ります。
call.receiveText()でJSONデータを受け取り、
先程作ったしたConversationComponentsApp#handleRequestにヘッダー情報と一緒に渡します。
これによりIntent名に対応してレスポンスが返されます。
最後に、call.respondを使ってDialogflow側に返却してあげます。fun Application.module(testing: Boolean = false) { val actionsApp= ConversationComponentsApp() routing { post("/"){ // body取得 val jsonBody: String = call.receiveText() // header取得 val headerMap = getHeaderMap(call.request) // IntentHandlerにbody,header情報を渡す val jsonResponse = actionsApp.handleRequest(jsonBody, headerMap).get() call.respond(jsonResponse) } } }// ヘッダー情報をHashMapにKey-Valueでつっこんであげる。 fun getHeaderMap(request: ApplicationRequest): Map<String, String>{ val headers= request.headers val headerMap = HashMap<String, String>() headers.forEach{ k, v -> headerMap[k] = v.toString() } return headerMap }GAEへのデプロイ準備
ターミナル編
Google Cloud SDKをMacにインストールし、初期化まで完了させます。
初期化中にGoogle Cloud Platformプロジェクトを選択する必要があります。Actions on GoogleプロジェクトIDと同じものを選びましょう。
設定できたら、現在の設定に間違いないか確認しておくといいでしょう。
$ gcloud config list [app] promote_by_default = true [core] account = <AoGプロジェクト作成時と同じGoogleアカウントのメールアドレス> disable_usage_reporting = False project = <Actions on GoogleのプロジェクトID(GCP Project id)>GAEのJavaプラグインをインストールします。
$ gcloud components install app-engine-java $ gcloud components updateIntellij IDE編
Build.gradleに以下を追加してください。
これで、GAE用のGradleタスクが活用できるようになります。buildscript { ... dependencies { ... classpath "com.google.cloud.tools:appengine-gradle-plugin:1.3.4" } } apply plugin: 'war' dependencies { ... providedCompile "com.google.appengine:appengine:1.9.60" } appengine { tools.cloudSdkHome="<google-cloud-sdkディレクトリへのパス>" }ここは任意ですが、ディレクトリ構成を整理しておきます。
kotlinファイルは、src/main/kotlinに、プロジェクトルートに存在するktorの設定ファイルをsrc/main/resourcesに移動させます。
また、src/main/webappというディレクトリを追加します。詳細については↓を参考ください![]()
- 構成ファイル | Java の App Engine スタンダード環境 | Google Cloud
- https://github.com/ktorio/ktor-samples/tree/master/deployment/google-appengine-standard
ディレクトリツリー構造はこんな感じになります。
... . └── main ├── kotlin │ ├── Application.kt │ └── ConversationComponentsApp.kt ├── resources │ ├── application.conf │ └── logback.xml └── webapp └── WEB-INF ├── appengine-web.xml ├── logging.properties └── web.xmlGAEへデプロイ! と動作確認!
ここまでの設定が完了したら、
./gradlew appengineDeployかIDEのGradleタスクからappengineDeployを実行してください。
成功すると、https://<gcp-project-id>.appspot.comのURLがコンソールに表示されるので、
こちらをメモっておき、DialogflowのFullfillmentに設定すれば完了です!... Deployed service [default] to [https://<gcp-project-id>.appspot.com] ...今回説明したコードはGithubにあげておきました。参考にしてみてください。
- https://github.com/1coin178/actions-on-google-kotlin-ktor-start-kit
- 投稿日:2019-01-27T13:43:00+09:00
Kotlinのイライラするところ(IDE含め)
この記事の動機
kotlinでのプログラミングはいろいろと遊べて楽しい。
しかし同時に、イライラすることもある。環境
Kotlin 1.3.11
IntelliJ idea Ultimate① contract
contractは楽しい。でもいやなところもある。
operator fun 非対応。
inline operator fun Hoge.invoke(block: Hoge.()->Unit)のようなふざけた実装をしたいときはまれによく訪れる。Rendererとかを実装するときとかね。
contractはトップレベル関数でしか使えないのは周知だ。納得している。だが、なぜなのだ… 拡張関数だのに…Hoge.kt@ExperimentalContracts inline operator fun Hoge.invoke(block: Hoge.()->Unit) { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block(this) }一見よさげな実装だが、実際には「Contracts are not allowed for operator functions」と怒られてしまう。何がお気に召さないのか。
- 投稿日:2019-01-27T01:57:56+09:00
Kotlinでデザインパターン Template Method編
はじめに
本記事はJava言語で学ぶデザインパターン入門を参考にしながら
JavaではなくKotlinで実装してみようというものです。調べてみる
Template Methodについて調べてみます。
WikpediaではTemplate Methodは次のように説明されています。Template Method パターンの目的は、ある処理のおおまかなアルゴリズムをあらかじめ決めておいて、 そのアルゴリズムの具体的な設計をサブクラスに任せることである。 そのため、システムのフレームワークを構築するための手段としてよく活用される毎回思うのですがWikipediaはあぁそうなんだ〜で終わる説明ですね。
Template Methodの理解を深めるためには実装したほうが早そうですので実装していきます。実装してみる
次の順序でファイルにPersonを出力する処理をアルゴリズムとします。
このアルゴリズムをTemplate Methodパターンを用いて実装したいと思います。処理1. ファイルをオープンする
処理2. ファイルにPersonを書き込む
処理3. ファイルをクローズするPerson
Personについてですが次のように定義しました。
特にデータにはこだわりありませんのでFirstNameとLastNameを持つ単純なデータクラスとしました。data class Person(val firstName : String, val lastName : String)AbstractWriter
PersonAbstractWriterという抽象クラスのwriteメソッドにアルゴリズムを定義します。
処理1はOpen、処理2はprocess、処理3はcloseというメソッドで処理をします。
この3つのメソッドはabstractとなっていますのでサブクラスで具体的な処理を定義します。このようにTemplate Methodパターンではまず、
ある処理の大まかなアルゴリズムを定義した抽象クラスを作成します。abstract class AbstractWriter() { abstract fun open() abstract fun process(data : Person) abstract fun close() fun write(data : List<Person>) { // 処理1. ファイルをオープンする open() // 処理2. ファイルにデータを書き込む data.forEach { process(it) } // 処理3. ファイルをクローズする close() } }ConcreteWriter
次に抽象クラスのAbstractWriterを継承したサブクラスを定義し、
abstractで宣言していたメソッドをOverrideし具体的にどのように出力するか処理を実装していきます。
今回はPersonをCSV形式またはテキスト形式で書き込めるように処理を実装していきたいと思います。CsvWriter
CSV形式での出力では次のようにヘッダーを出力したいです。
なのでファイルをオープンするときにヘッダーを書き込んでおきます。
あとはCSV形式ですのでカンマ区切りで各プロパティを書き込むようにします。lastName,firstName 姫路,太郎 榊,真喜子 喜々津,優輔class CsvWriter(name : String) : AbstractWriter() { private val name : String = name private val header : String = "lastName,firstName\n" private var file : FileWriter? = null override fun open() { file = FileWriter(name) file?.write(header) } override fun process(data: Person) { val csv = "${data.lastName},${data.firstName}\n" file?.write(csv) } override fun close() { file?.close() } }TextWriter
テキスト形式ですのでPersonをそのまま書き込むだけなのですが、
次のように名簿のようにしたいと思いましたのであわせて番号も書き込むことにしました。1 姫路 太郎 2 榊 真喜子 3 喜々津 優輔class TextWriter(name : String) : AbstractWriter() { private val name : String = name private var count : Int = 0 private var file : FileWriter? = null override fun open() { file = FileWriter(name) } override fun process(data: Person) { count++ var text = "${this.count} ${data.lastName} ${data.firstName}\n" file?.write(text) } override fun close() { file?.close() count = 0 } }Main
サブクラスの定義が終わりましたのでMainから呼び出します。
fun main(args : Array<String>) { val persons = listOf<Person>(Person("太郎", "姫路"), Person("真喜子", "榊"), Person("優輔", "喜々津")) val csvWriter = CsvWriter( "persons.csv") val textWriter = TextWriter("persons.txt") csvWriter.write(persons) textWriter.write(persons) }Mainを実行しますと次のようなファイルが次の内容で出力されます。
lastName,firstName 姫路,太郎 榊,真喜子 喜々津,優輔1 姫路 太郎 2 榊 真喜子 3 喜々津 優輔おわりに
結論としてTemplate Methodパターンでやりたいことは処理の共通化です。
- インタフェースの共通化
- 処理順序の共通化
特に2の処理順序の共通化がTemplate Methodパターンの利点だと思います。
今回の例ですとオープンはいつクローズはいつと順序が決められているため、
サブクラスごとにオープンやクローズのタイミングが違うとかは絶対に起きなくなります。このようにプログラマによって指針が違うような事柄が含まれる場合は、
Template Methodパターンを使って処理を共通化しておくとサブクラスがより管理しやすくなると思います。


