10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

だからボクはオブジェクト指向が使いこなせない Android/Kotlin編

Posted at

#はじめに
本記事は、Android/Kotlin開発において、オブジェクト指向を使いこなしたい、という方に向けた記事です。
※この記事は「だからボクはオブジェクト指向が使いこなせない Android/Java編」をAndroid/Kotlinで書き直したものです。

私が10年近くAndroidアプリ開発に関わってきて思うのが、開発現場でオブジェクト指向らしいコードを書けるプログラマーは、残念ながら全体の2割にも満たない、ということです。(あくまで私個人の感想です)。
逆に言えば、オブジェクト指向らしいコードを書けるようになるだけで、開発現場で重宝される人材になれます。Androidの最新ライブラリの使い方の勉強をすることも大切ですが、開発現場でより即効性があり、かつ、Android以外のプログラミングにも応用できる、潰しのきく技術、それがオブジェクト指向らしいコードを書く技術です。

##どうしたらオブジェクト指向らしいコードを書く技術を身につけられる?
※具体的な技術的な話をすぐに読みたい方は「開発現場」の段落までスキップしてください。
昔、先輩社員に「どうしたらオブジェクト指向らしいコードを書く技術を身につけられるか」を聞いたところ「開発経験を積むこと」というあいまいな答えをもらい悩んだことがあります。この「開発経験」は自分にとって余程都合が良いプロジェクトでないと意味がありません。テスターとしてプロジェクトに参画するのはもちろん、他者が書いたコードの拡張や改修する役割で参画してもあまりよい経験にはなりません(AndroidのAPIの使い方を知るという意味では意味がありますが)。自分でソフト構成を設計できるような新規案件や大きな機能ブロックを作成できる案件でないとなかなか「オブジェクト指向らしいコードを書く技術」を磨くチャンスがないのです。しかし、多くの仕事内容は、他者が書いたコードの拡張や改修する作業です。
オブジェクト指向らしいコードを書く技術を磨くには、自分で技術の本や記事を積極的に読み、実際に自分でコードを書き、頭をトレーニングする必要があります。

おすすめのトレーニング方法

「オブジェクト指向らしいコードを書く技術」を身につける為におすすめなのが「デザインパターン」と「リファクタリング」の技術を身につけることです。どちらもオブジェクト指向らしいコードを身につける為の要素がたくさん詰まっています。
KotlinはJavaと異なり、比較的新しい言語であるためか、デザインパターンやリファクタリングに関する書籍がなかなかありません。

私自身はJavaでオブジェクト指向らしいコードの書き方を身に着け、それを他の言語にも適用する、という形でやってきました。書籍の充実という観点からすると最初にJavaを覚えることはとても良いことだと思います。しかし、ここ数年でAndroid案件はKotlinの案件が増えてきたこともあり、最初に学習を始めるプログラム言語がKotlinという方も増えてきた印象です。そこで、Kotlinでオブジェクト指向を使いこなす為のとっかかりとなる情報を提供しようと思い、本稿を書きました。

##デザインパターン
デザインパターンについて、もう少し掘り下げます。デザインパターンはGoFの23パターンが有名です。私個人の意見としては、最初は23種全部覚えるよりも、重要ないくつかのパターンを使いこなすのに時間を掛けた方がよいと思っています。次のパターンを優先的にマスターするのがおすすめです。

  • Template Methodパターン
  • Factory Methodパターン
  • Facadeパターン
  • Stateパターン
  • Builderパターン
  • Observerパターン

この中で最初に覚えた方がよいパターンは「Template Method パターン」です。プログラミングのできる方だと、わざわざデザインパターンの名前も付ける必要もないくらいオブジェクト指向の性質そのものだ、と言う方もいると思います。それくらい「基本でかつ奥義」みたいなパターンです。この「Template Method パターン」について本記事を通して学んでいきましょう。

#開発現場

※筆者注:導入部は物語風会話形式で書いてみました。
とあるソフト開発会社「エゥーゴソフト」開発チームの二人の会話から始まります。
##登場人物説明

  • ブライト(リーダー):社会人(&開発経験)5年目。新人の頃からAndroid一筋で、厳しい先輩に教わったこともあり、現在では新規アプリ開発プロジェクトの立ち上げを任されている。
  • アムロ(新人女性社員):大学生の頃は主にC言語でプログラミングしていた。Kotlin言語は入門書の7割くらいは読んだが、正直オブジェクト指向と言われてもピンとこない。新人研修のときに初めてAndroidアプリの作り方を学んだ。

##ブライトさんの作成依頼
ブライトリーダー:アムロさん、こんな感じのAndroidアプリを作ってほしいんだ。(といって下記のイメージを見せる)

<アプリ仕様>
画面遷移.png

  • アプリを起動すると背景が青い画面を表示し、中央に「NEXT」ボタンを表示すること
  • 「NEXT」ボタンを押下すると、背景が黄色の画面->赤い画面->青い画面と切り替わること
  • Backキーを押下するとアプリが終了すること(Androidアプリでデフォルトの動作のままでよい)

ブライトリーダー:アムロさんはAndroidアプリ開発を新人研修で教わったみたいだけど、いきなり全部作れって言うのは厳しいと思うから、途中まで(青い画面を表示して、ボタンが押された時のイベント処理をどう書けばよいのかがわかるところまで)作ってあるから、これをベースに作ってみて。
アムロ:わかりました。作ってみます。

###ベースとなるコード(ktファイル以外は省略)

サンプル:ObjectOrientationKotlin_base
※サンプルはGithubに格納してあります。

BlueActivity.kt
package com.eugo.objectorientationkotlin

import android.graphics.Color
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity

class BlueActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<View>(R.id.layout).setBackgroundColor(Color.BLUE)
        findViewById<View>(R.id.button).setOnClickListener {
            // 別のActivityに遷移する時の処理イメージ
//            val intent = Intent(this, YellowActivity::class.java)
//            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
//            startActivity(intent)
        }
    }
}

。。。。
アムロさんはAndroid Studioの操作に苦戦しつつも、アプリを作り上げました。
アムロ:できました。こんな感じでどうでしょう。(ブライトさんにAndroid端末の画面を見せながら操作する)
ブライトリーダー:いいね。動作は完璧だね。ソースコードの方を見せてもらえる?

アムロさんが作ったコード
サンプル:ObjectOrientationKotlin_amuro

BlueActivity.kt
package com.eugo.objectorientationkotlin

import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity

class BlueActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<View>(R.id.layout).setBackgroundColor(Color.BLUE)
        findViewById<View>(R.id.button).setOnClickListener {
            val intent = Intent(this, YellowActivity::class.java)
            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
            startActivity(intent)
        }
    }
}
YellowActivity.kt
package com.eugo.objectorientationkotlin

import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity

class YellowActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<View>(R.id.layout).setBackgroundColor(Color.YELLOW)
        findViewById<View>(R.id.button).setOnClickListener {
            val intent = Intent(this, RedActivity::class.java)
            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
            startActivity(intent)
        }
    }
}
RedActivity.kt
package com.eugo.objectorientationkotlin

import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity

class RedActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<View>(R.id.layout).setBackgroundColor(Color.RED)
        findViewById<View>(R.id.button).setOnClickListener {
            val intent = Intent(this, BlueActivity::class.java)
            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
            startActivity(intent)
        }
    }
}

ブライトリーダー:(そうきたか。。。)うーん。残念だけどお仕事の成果物としては、このままのコードではダメだね。
アムロ:えー。ボクのコードの何がダメなんですか。ちゃんと仕様通り動いているじゃないですか。
ブライトリーダー:BlueActivity.ktとYellowActivity.ktとRedActivity.ktってなんか「とてもよく似ている」よね。
アムロ:まあ、BlueActivity.ktをコピペしてYellowActivityクラスとRedActivityクラスを作って、画面の色とか遷移先のActivityクラスのコードを修正したので。
ブライトリーダー:(コピペしたって言っちゃった。正直というか。。。)似たようなコードがあるってことはもっとコードを少なくして、スッキリさせることができるはずなんだ。コードで説明するとわかりずらいから、日本語に置き換えて説明しよう。


3つのクラスの動作を日本語で表現すると、次のようになります。
クラス説明1.png

この中で、クラス毎に異なる部分を赤字にします。
クラス説明2.png

(1)は3つのクラスとも同じですね。(2)と(3)は赤字以外の部分は同じです。
この同じ部分(重複部分)が「無駄」と言えます。無駄な部分を取り消し線で書きます。
クラス説明3.png

この取り消し線の部分がなくなったら、スッキリしたコードになると思いませんか。そこで次のように、赤字の部分を〇〇と△△に置き換えます。

クラス説明4.png

3つのクラスとも同じ内容になりました。
同じ内容なら1つのクラスで表現すればいいですよね。そこで3つのクラスのスーパークラス(親クラス)というものを考えてみます。
クラス説明5.png

これをソースコードで表現すれば、スッキリしたコードになります(スーパークラスのクラス名をColorActivityとします)。
青色やYelloActivityのような情報を〇〇や△△に置き換えることを「抽象化」と言います。抽象化した情報を具体化するのはサブクラス(子クラス)側に任せ、スーパークラスは決めません。ソースコードを見ていきましょう。

サンプル:ObjectOrientationKotlin_final

ColorActivity.kt
package com.eugo.objectorientationkotlin

import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity

abstract class ColorActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main) //(1)
        findViewById<View>(R.id.layout).setBackgroundColor(myColor) //(4)
        findViewById<View>(R.id.button).setOnClickListener {
            val intent = Intent(this@ColorActivity, nextClass) //(5)
            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
            startActivity(intent)
        }
    }

    /**
     * 自Activityの色を取得する
     * @return 自Activityの色
     */
    protected abstract val myColor: Int //(2)

    /**
     * 次に遷移するActivity Classを取得する
     * @return 次に遷移するActivity Class
     */
    abstract val nextClass: Class<*>? //(3)
}

「(1)レイアウトを「activity_main.xml」に設定する」については、3つのクラスで同じ内容でした。同じならば、親クラスで1回書けば、子クラスに書く必要はなくなります。
getMyColor()やgetNextClass()はabstractが付いています。ColorActivityはどんな色か、どんなクラスかを具体的に決めません。決めるのはサブクラスに任せます。色や次のクラスを取得し、それを(4)や(5)で使用することだけをスーパークラスで書きます。
サブクラス(子クラス)のソースコードも見ていきましょう。

BlueActivity.kt
package com.eugo.objectorientationkotlin

import android.graphics.Color

class BlueActivity : ColorActivity() {
    override val myColor: Int
        get() = Color.BLUE
    override val nextClass: Class<*>
        get() = YellowActivity::class.java
}
YellowActivity.kt
package com.eugo.objectorientationkotlin

import android.graphics.Color

class YellowActivity : ColorActivity() {
    override val myColor: Int
        get() = Color.YELLOW
    override val nextClass: Class<*>
        get() = RedActivity::class.java
}
RedActivity.kt
package com.eugo.objectorientationkotlin

import android.graphics.Color

class RedActivity : ColorActivity() {
    override val myColor: Int
        get() = Color.RED
    override val nextClass: Class<*>
        get() = BlueActivity::class.java
}

クラス図でクラス関係を整理しておきます。
class.png


アムロ:なるほど。たしかにスッキリした感じがしますね。BlueActivityとか差分の情報しかないって感じがすごくします。サブクラスは、Android固有のメソッドが1つも出てこないんですね。。。でもこのようなコードに改造するのは大変な気がします(というかメンドクサイ)。なんでこんなことをやる必要があるのでしょうか。
ブライトリーダー:簡単に言えば
(1)「バグの量」を減らすこと
(2)クラスの役割分担を明確する
を行うことでメンテナンスしやすくなるんだ。メンテナンスしやすければ「楽に」働ける。
(1)は、ソースコードを人が書く以上、書く量が多ければ多い程ミスが出て、バグが生まれる可能性が高くなる、という考え方から来ている。だから基本的にコード量は少ない方がいいんだ。(Kotlinのコード量で開発工数の見積もりをしようとお客さんがいるけど、正直コード量で見積もりするのはナンセンスだよな。コピペ文化を増長するし。)
(2)は、日本語の赤字をつけた例を見ればわかると思うけど、本来BlueActivityやYelloActivityは「何色か」、「次のAcitvityクラスは何か」の2つを決めるだけでいいはずなのに黒字と赤字が混ざっている。言い換えれば、Android固有のメソッド(API)を呼ぶコード(setContentView()やstartActivity())と呼ばなくてもよいコードが混ざっている。例えば、AndroidのVersionが上がってAPIの仕様変更があって動かなくなったときにどこを修正すればいいのか調査する必要が出てきたとする。この場合、アムロさんのコードでは、BlueActivity、YelloActivity、RedActivityの3つのクラスの調査する必要がある。
<ObjectOrientationKotlin_amuroのソフト構成>
スライド7.PNG

それに比べてObjectOrientationKotlin_finalサンプルは、ColorActivityだけを(基本的に)調査すればいいんだ。
<ObjectOrientationKotlin_finalのソフト構成>
スライド8.PNG

アムロさんには、ぜひ「ObjectOrientationKotlin_final」のようなコードが書けるようになってほしいね。
アムロ:わかりました。がんばります!

Template Method パターンの理解を深める為のQ&A

  • Q.Template Method パターンとは、何かを取得する処理を抽象化するパターンのことである。Yes or No
  • A. No。抽象化の対象は動作(動詞)、つまりメソッドのことです。ただし、〇〇を取得するというメソッドの例が、Template Method パターンを理解する上で一番わかりやすい例だとは思います。
  • Q. 「val parent: Parent = Child()」のようなコードが出てこないんだけど、これって本当にTemplate Method パターンのサンプル?
  • A. 今回のサンプルでは、「val parent: Parent = Child()」の部分をAndroidが行っています。Template Method パターンの例としては、微妙かもしれませんが、メソッドを抽象化する感覚を感じて頂ければと思います。プログラミング言語の入門書を読んだだけだと、なかなか「abstract」や「interface」を自分で使ってみようという気にならないと思います。Template Method パターンを通じて、「abstract」や「interface」を自分が書いているコードに適用できないか考えてみましょう。

あとがき

ここまで読んでいただきありがとうございます。
ここ数年Kotlinの書籍を見てきて、なかなかオブジェクト指向の本って出ないなーと思っています。
「ないなら自分で作ればいい」がモットーの私としては、この状況を改善したいと思っています。
もし、Kotlin版の「オブジェクト指向を使いこなす本」が欲しい!と思う方がいらっしゃいましたら、この記事のコメント欄やTwitter、YouTubeなどでご要望をいただければと思います。
必ず作成することはお約束はできませんが、次の私が作成する「技術同人誌」のテーマになるかもしれません。
本記事が少しでもオブジェクト指向の理解と実戦で活かせる助けになれば幸いです。

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?