[Android] มาทำความรู้จัก InterfaceWrapper กันเถอะ

Phongsathon Patthaladilok
te<h @TDG
Published in
3 min readNov 25, 2019

หลายคนๆ คงจะคุ้นเคยกับ Interface อยู่แล้ว สำหรับผมมันเปรียบเสมือนกับ “หน้ากาก” ตัวอย่าง เช่น มีคนใส่หน้ากาก BatMan มาปกป้องเมือง ทุกคนในเมืองจะรู้จักเขาคนนี้ในนาม BatMan แต่ไม่มีใครรู้ว่าคนที่สวมหน้ากากอยู่นั้นตัวจริงเป็นใคร ก็เหมือนกับ Interface ถูกเรียกใช้จากในหลายๆที่เพื่อใช้งาน แต่ก็ไม่มีใครรู้ว่าข้างในนั้น Implement ไว้อย่างไรบ้าง

โดยปกติแล้วเราจะ Implement Interface ด้วยตัว InterfaceImpl และเรียกใช้จากตัว Interface ปกติ โดยคนที่เรียกใช้จะไม่สามารถเปลี่ยนแปลงค่าใน InterfaceImpl ที่ถูก Implement แล้วได้ ตัวอย่าง เช่น

ตัวอย่างการ Implement Interface

ประโยชน์ของมันก็คือว่าในกรณีที่ Interface ถูกเรียกใช้งานจากหลายๆที่ แต่เรามีความต้องการที่จะเปลี่ยนแปลงการ Implement ตัวอย่างเช่น เราเขียน Interface ที่ใช้โหลดรูป โดยใช้ Picasso แล้วมีคนเรียกใช้หลายที่ แล้วเกิดวันดีคืนดี เราไปเจอตัวโหลดรูปตัวใหม่ที่เหมาะกับการใช้งานของเรามากว่าเช่น Glide เราก็จะสามารถแก้ตรง InterfaceImpl ที่เดียวโดยไม่กระทบกับคนที่เรียกใช้ ถ้าเราไม่ใช้ Interface ในทุกจุดที่มีการใช้งาน เราจะต้องตามไปแก้ และมันจะทำให้ “รก” จากการที่มี Code ซ้ำๆกันที่มีการทำงานเหมือนกัน

ตัวอย่าง หน้าจอโทรศัพท์

ทีนี้เราจะมาลองใช้งาน interface กันจริงๆ โดยเราจะ Implement ให้เป็น Interface ที่จะรับ textview เข้ามาทำการเปลี่ยนสีเป็นสีต่างๆ และจะกำหนดให้ function red() ทำการเปลี่ยนสี textview เป็นสีแดง function green() ทำการเปลี่ยนสี textview เป็นสีเขียว และ function blue() ทำการเปลี่ยนสี textview เป็นสีฟ้า ตามลำดับเหมือนกับตัวอย่างด้านล่าง

interface ChangeColorInterface {
fun red()

fun green()

fun blue()
}

open class ChangeColorInterfaceImpl(private val textView: TextView,
private val context: Context) : ChangeColorInterface {
override fun red() {
textView.setTextColor(ContextCompat.getColor(context, android.R.color.holo_red_dark))
}

override fun green() {
textView.setTextColor(ContextCompat.getColor(context, android.R.color.holo_green_dark))
}

override fun blue() {
textView.setTextColor(ContextCompat.getColor(context, android.R.color.holo_blue_dark))
}

}

และสำหรับส่วนที่จะเรียกใช้งาน ( helloTextView เป็น Id ของ TextViewใน xml ) เราจะทำการ Init ตัว Interface ด้วยตัว ChangeColorInterfaceImpl() ในตัวอย่างด้านล่างนี้จะเห็นได้ว่า เมื่อกดคลิกที่ตัว Textview มันจะเรียกใช้ function changeTextViewColor() ที่ต้องการ Interface ในการเปลี่ยนสี Textview เป็นสีต่าง ตามค่า loop

class MainActivity : AppCompatActivity() {

var loop = 0

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val initInterface = ChangeColorInterfaceImpl(textView = helloTextView, context = this)

helloTextView?.setOnClickListener { view ->
changeTextViewColor(initInterface)
}

}

private fun changeTextViewColor(changeColorInterface: ChangeColorInterface) {

when (loop) {
0 -> {
changeColorInterface.red()
loop += 1
}
1 -> {
changeColorInterface.blue()
loop += 1
}
else -> {
changeColorInterface.green()
loop -= 2
}
}
}
}

แล้วถ้าหากว่าเราอยากจะใช้ Interface ที่ Implement เป็น Object จะเป็นยังไงตัวอย่าง เช่น อยากจะรับค่าของ EditText เมื่อมีการเปลี่ยนแปลงโดยใช้ TextWatcher (TextWatcher เป็น interface ที่มีอยู่ใน android studio อยู่แล้ว)

ตัวอย่างการ Implement เป็น Object

จะเห็นได้ว่ามันจะมีเส้นสีแดงขึ้นฟ้องว่ามี Error วิธีแก้ก็คือให้เรา Implement Member (AutoComplete) ของที่อยู่ใน Interface ซึ่งจะมีทุก Function ของ TextWatcher ไม่ว่าเราจะอยากได้หรือไม่ เราก็ต้องเลือกเอามาทั้งหมด (ถึงเราอาจจะต้องการแค่ function เดียวก็ตาม มันก็จะฟ้อง error ให้เราไป Implement มาเพิ่มอยู่ดี)

editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {

}

override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

}

override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

}
})

หลังจากเลือกทั้งหมดก็จะได้ code หน้าตาประมาณนี้ และอย่างที่บอกไปด้านบนว่า ถ้าเราต้องการใช้แค่ Function เดียวเท่านั้น เช่น อยากจะได้ String หลังจากที่ user พิมพ์เสร็จแล้ว

var userInput = ""

editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
userInput = p0.toString()
}

override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

}

override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

}
})

หลังจากที่เรา Implement code เพิ่มจะได้หน้าแบบนี้ ซึ่งเป็นตัวอย่างของ Edittext เพียงตัวเดียวเท่านั้น ซึ่งทุกคนก็คงจะไม่เห็นปัญหาอะไร แต่ถ้าหากในหน้านี้เรามี Edittext สัก 10 ตัวที่ต้องการใช้ Function ในลักษณะนี้ จะเกิดอะไรขึ้น คำตอบก็คือ เราจะ Coppy Code ชุดนี้มาวาง 10 รอบ โดยเป็น Edittext ที่จะใช้ Listener ถูกไหมมม แล้วถ้าเราทำแบบนั้น จะเกิดอะไรขึ้น Code ในหน้านี้ก็จะรกมากๆ จาก code ที่เราไม่ได้ใช้แล้วทีนี้เราจะทำยังไงได้บ้าง!!!

ไม่ต้องกลัวเรามีวิธีง่ายๆในการแก้ปัญหานี้มานำเสนอ เราเพียงสร้าง Interface ขึ้นมาอีกหนึ่งอัน โดยที่ Implement ตัว TextWatcher เพิ่มขึ้นมาเท่านั้นเอง และ Interface ที่สร้างขึ้นมาใหม่อันนี้ขออนุญาตใช้ชื่อว่า TextWatcherWrapper

ตัวอย่างการ Implement TextWatcherWrapper

หลังจากสร้างแล้ว ทุกคนคงสงสัยกันว่า จะนำไปใช้งานยังไงละสิ บอกเลยว่าไม่ยากๆ เอาจริงๆก็คือว่าทำเหมือนเดิมเลยเพียงแค่เราเปลี่ยนจาก Implement object : TextWatcher ไปเป็น Implement object :TextWatcherWrapper() แทน

ตัวอย่างการใช้งาน TextWatcherWrapper

และเพียงเท่านี้เราก็จะสามารถใช้งาน TextWatcherWrapper ได้ใน Function ที่เราต้องการโดยไม่มี Code ที่เราไม่ได้ใช้มาให้รกหน้าจออีกต่อไป

ตัวอย่างการImplement TextWatcherWrapper เพิ่ม

จะเห็นได้ว่าเราลด Line Of Code ออกไปได้บางส่วน และอย่างที่บอกไปข้างต้นว่าเราควรใช้ InterfaceWrapper เฉพาะ Class ที่เรียกใช้ Interface ที่มี Function เยอะๆแล้วแล้วใช้ไม่กี่ Function หรือว่ามีการใช้ Interface นั้นเยอะ เพราะถ้าหากเราใช้ทุก Functionใน Interface หรือ ใช้ Interface นั้นเพียงครั้งเดียว มันก็จะทำให้เราเสียเวลาในการไปสร้าง InterfaceWrapper นั้นเอง หรือใครอยากทำเพื่อความสวยงามก็เป็นสิทธิ์ของเจ้าของ Code และสุดท้ายนี้ก็หวังว่าทุกคนที่เข้ามาอ่านจะได้อะไรกลับไปบ้างไม่น้อยก็น้อยมาก ยังไงก็ขอขอบคุณทุกคนที่เสียเวลามาอ่านมากครับ

--

--