実装の共通化ができるインターフェースのデフォルト実装とクラスの委譲でできることが似ているので調べてみました
メソッド
まずはシンプルなものから
インターフェースのデフォルト実装
interface A1{
fun f(){
// do something
}
}
class A1_Class:A1{
init {
f()// 呼び出せれる
}
}
クラスの委譲
interface B1{
fun f()
}
class C1:B1{
override fun f() {
// do something
}
}
class B1_Class:B1 by C1(){
init {
f()// 呼び出せれる
}
}
違い
(当然のことですが)クラスの委譲で書くことが増える以外違いはなさそうですね
プロパティ
インターフェースにはプロパティも記述できるので
インターフェースのデフォルト実装
interface A2{
//val p1:String ="" できない
//var p2:String="" できない
val p3:String//できた!(ただし同じインスタンスを返せない)
get() = ""
var p4:String//できた!(ただし形だけ)
get() = ""
set(value) {}
}
class A2_Class:A2{
init {
//どちらも参照できる
print(p3)
print(p4)
}
}
値が持てない…
クラスの委譲
interface B2{
val p1:String
var p2:String
val p3:String
var p4:String
}
class C2:B2{
override val p1=""
override var p2=""
override val p3: String
get() = ""
private var _p4 =""
override var p4: String
get() = _p4
set(value) { _p4=value }
}
class B2_Class:B2 by C2(){
init {
//もちろん参照できる
print(p1)
print(p2)
print(p3)
print(p4)
}
}
違い
インターフェースのデフォルト実装ではプロパティの初期化子が使えないところと値を保持できないところに違いが出てきましたね(多重継承の禁止に関する制約ですかね?)
拡張関数
インターフェースのデフォルト実装
interface A3<T>{
fun String.f1(){
//do something
}
fun T.f2(){
//do something
}
fun <U> U.f3(){
//do something
}
fun <U> A3<U>.f4(){
//do something
}
}
class A3_Class:A3<String>{
init {
"".f1()
"".f2()
1.f3()
this.f4()
}
}
クラスの委譲
interface B3<T>{
fun String.f1()
fun T.f2()
fun <U> U.f3()
fun <U> B3<U>.f4()
}
class C3<T>:B3<T>{
override fun String.f1() {
//do something
}
override fun T.f2() {
//do something
}
override fun <U> U.f3() {
//do something
}
override fun <U> B3<U>.f4() {
//do something
}
}
class B3_Class:B3<String> by C3(){
init {
"".f1()
"".f2()
1.f3()
this.f4()
}
}
違い
これといった違いはなさそうですね
拡張プロパティ
インターフェースのデフォルト実装
interface A4<T>{
val String.a1:String
get() = ""
val T.a2:String
get() = ""
val <U> U.a3:String
get() = ""
val <U> A4<U>.a4:String
get() = ""
}
class A4_Class:A4<String>{
init {
print("".a1)
print("".a2)
print(1.a3)
print(this.a4)
}
}
プロパティと同様に値は持てないみたいです
クラスの委譲
interface B4<T>{
val String.a1:String
val T.a2:String
val <U> U.a3:String
val <U> B4<U>.a4:String
}
class C4<T>:B4<T>{
//override val String.a1 = "" できない
private val _a =""
override val String.a1: String
get() = _a
override val T.a2: String
get() = _a
override val <U> U.a3: String
get() = _a
override val <U> B4<U>.a4: String
get() = _a
}
class B4_Class:B4<String> by C4(){
init {
print("".a1)
print("".a2)
print(1.a3)
print(this.a4)
}
}
なぜかプロパティの初期化子が使えない…
違い
当然のことながら拡張プロパティはプロパティと同様の違いがありましたね
記事書いてるときに気づきましたが拡張プロパティって初期化子使えないんですね…
部分実装
インターフェースのデフォルト実装
interface A5{
fun f1()
fun f2()
}
interface A5_Sub:A5{
override fun f1() {
//do something
}
}
class A5_Class:A5_Sub{
override fun f2() {
//do something
}
init {
f1()
f2()
}
}
インターフェースは継承ができるのでオーバーライドしてデフォルトの実装を持たせることができるみたいですね
クラスの委譲
interface B5{
fun f1()
fun f2()
}
abstract class C5:B5{
override fun f2() {
//do something
}
}
// class B5_Class:B5 by C5()コンパイルエラー
抽象クラスはインスタンス作成できないのでコンパイルエラーになります
違い
部分実装できる・できないまで差がでましたね
そのほかに
当たり前ですがクラスの委譲の場合コンストラクタが使えます
まとめ
- インターフェースのデフォルト実装は値を持てない
- 部分実装するならインターフェースのデフォルト実装
- コンストラクタを使うならクラスの委譲
- 拡張プロパティは初期化子が使えない
考察
インターフェースの定義の一部に実装を持たせたいのならデフォルト実装でその他の場合はクラスの委譲が良いっていう感じですかね?
もちろん混ぜ合わせて使うこともできますし、実装の共通化なんてするのもそれほど機会があるわけでもないので悩むことも少ないですかね
さいごに
多重継承の問題が浮上してくると思いますが自分はそれについてはまだわかってないことがあるのでリンクを張っておきます
ギークを目指して - Java8のインタフェース実装から多重継承とMixinを考える
あと全く関係ない話ですがこれと似たような疑問でC#6.0のreadonlyな変数とgetterのみの(初期化子ありな)自動実装プロパティの違いがわからないんですがわかる人いたら教えてくださいオナシャス