RxJava part 1: ก่อนจะมาเป็น RxJava

เขียนโค้ดแนวใหม่ สไตล์ reactive

Travis P
Black Lens
4 min readFeb 6, 2017

--

http://reactivex.io/

ใครๆเค้าก็ว่ากันว่าช่วงนี้ RxJava กำลังมาแรง ผมได้ยินประโยคแบบนี้มาตั้งแต่ปี 2014 จนถึงทุกวันนี้ปี 2017 ก็ยังพูดเหมือนเดิม ผมเคยไปลองเล่น Retrofit+RxJava มาสักพักแล้ว รู้สึกว่าเขียนยืดยาวยากกว่าเดิมอีก ไม่รู้มันจะดีอะไรนักหนา ก็เลยไม่ได้สนใจอะไรต่อ จนกระทั่งไม่นานมานี้ได้มีโอกาสกลับมาศึกษาใหม่อีกครั้ง คราวนี้พอเข้าใจคอนเซปและเห็นความสำคัญของมัน จึงนำมาเล่าให้ฟังกันครับ

RxJava?

ถึงแม้ผมจะพาดหัวไว้ว่า RxJava แต่จริงๆแล้วแนวคิดพวกนี้มันก็เอาไว้อธิบายได้ทั้งตระกูล ReactiveX ทุกภาษาเลยนะครับ แต่ก่อนจะไปทำความรู้จักมัน เรามาทำความรู้จักศัพท์/เทคโนโลยีพื้นฐานของมันก่อนดีกว่า

Functional Programming

สไตล์การเขียนโค้ดเดิมๆที่เราเขียนเค้าเรียกว่า imperative programming คือการที่เราเขียนโค้ดแบบสั่งเอาๆ อย่างละเอียดทุก statement ตัวอย่างในภาษา Java เช่น

List<String> names = Arrays.asList("Alice","Munkaw",null,"Jessie");
// names = ["Alice","Munkaw",null,"Jessie"]
List<String> nonNullNames = new ArrayList<>();
for (String name : names) {
if (name != null) {
nonNullNames.add(name);
}
}
// nonNullNames = ["Alice","Munkaw","Jessie"]
List<Integer> nameLengths = new ArrayList<Integer>();
for (String name : nonNullNames) {
nameLengths.add(name.length);
}
// nameLengths = [5,6,6]
int charSum = 0
for (Integer i : nameLengths) {
charSum += i;
}
// charSum = 17

ทีนี้การเขียนแบบนี้มันเวิ่นเว้อเข้าใจยาก ซึ่งแตกต่างจาก declarative programming ที่จะเน้นอ่านเข้าใจง่าย ไม่ต้องลงรายละเอียดทุก statement

functional programming (FP) เป็น subset ของ declarative programming โดยแนวคิดส่วนหนึ่งของ FP ที่เป็นพื้นฐานของ RxJava ประกอบด้วย

  • first-class function ฟังก์ชั่นถือเป็นออบเจคสามารถโยนไปมาได้
  • higher-order function คือฟังก์ชันที่รับออบเจคฟังก์ชันเป็นพารามิเตอร์
  • pure function คือการที่ฟังก์ชันไม่ได้ยุ่งกับตัวแปรใดๆนอกฟังก์ชันเลย ฟังก์ชันจะใช้ข้อมูลจากตัวแปรที่ส่งผ่านมาทางพารามิเตอร์เท่านั้น
val names = listOf("Alice","Munkaw",null,"Jessie")
// names = ["Alice","Munkaw",null,"Jessie"]
val nonNullNames = names.filter { name -> name != null }
// nonNullNames = ["Alice","Munkaw","Jessie"]
val nameLengths = nonNullNames.map { name -> name.length }
// nameLengths = [5,6,6]
val charSum = nameLength.reduce { i, i2 -> i + i2 }
// charSum = 17

จากตัวอย่างภาษา kotlin ฟังก์ชัน filter รับพารามิเตอร์ 1 ตัวคือ “ฟังก์ชันที่รับ String คืน Boolean” ซึ่งภายใต้ความอ่านง่ายนั้น เบื้องหลังที่ฟังก์ชัน filter ทำ คือวน for ทุกไอเทมในลิสเพื่อเช็คว่าจะเอาหรือไม่เอาไอเทมนี้ตาม logic ในออบเจคฟังก์ชั่นที่เราใส่เข้าไป คล้ายๆกับที่เราทำเองในแบบ imperative อยู่ดี

ถ้าอ่านตัวอย่าง kotlin แล้วมึนๆไม่ต้องตกใจไปครับ คอนเซปพวกนี้ไม่ใช่เรื่องที่เข้าใจกันง่ายๆ syntax ก็ไม่คุ้นเคย แถม Java 6 ไม่สนับสนุนทั้ง first-class function และ higher-order function ถ้าใครสนใจ สามารถไปเจาะลึกต่อตามนี้ครับ

Observer Pattern

Observer pattern เป็น design pattern ที่ไว้ใช้เวลาเราอยากทราบเมื่อเกิดเหตุการณ์ใดเหตุการณ์หนึ่งขึ้น เช่น ปุ่มถูกคลิก

Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// a button is clicked, do something
}
}

หรือตัวแปรเปลี่ยนค่า

class ObservableString {
private String value;
private StringObserver observer;
public void setValue(String s) {
value = s;
if (observer != null) {
observer.onStringChanged(s);
}
}
public void setStringObserver(StringObserver o) {
observer = o;
}
}
ObservableString name = new ObservableString();
name.setStringObserver(new StringObserver() {
@Override
public void onStringChanged(String s) {
System.out.println("onStringChanged: " + s);
}
}
name.setValue("Alice"); // onStringChanged: Alice
name.setValue("Munkaw"); // onStringChanged: Munkaw
name.setValur("Jessie"); // onStringChanged: Jessie

สมมติเราอยาก println() เมื่อ String เปลี่ยนแปลง เราก็ใช้คลาส ObservableString แทน String ธรรมดา แล้ว setStringObserver เอาไว้ จากนั้นทุกครั้งที่มีคนเรียก name.setValue() มันก็จะเรียก observer.onStringChanged() ด้วยเสมอ ถ้าหากไม่ใช้ observer pattern ก็คงต้องทำประมาณนี้

String name = "Alice";
System.out.println(name); // onStringChanged: Alice
name = "Munkaw";
System.out.println(name); // onStringChanged: Munkaw
name = "Jessie";
System.out.println(name); // onStringChanged: Jessie

สังเกตว่าเราจะไม่มี name.getValue() ซึ่งเป็นการดึงค่าเมื่อเราต้องการ (pull) แต่เราจะใช้ onStringChaged(String s) เมื่อมีค่าใหม่เข้ามา (push) แทน

observable นี่ก็เหมือน net idol น่ะครับ
เราสนใจเราก็ไปกด follow เค้า เราก็ถือเป็น observer
พอ net idol โพสรูป ก็มี notification ส่งมาเตือนเรา

ถ้าอ่าน observer pattern ฉบับย่อตรงนี้ไม่สะใจ เชิญไปอ่านอย่างละเอียดที่นี่เลย

ReactiveX

ReactiveX (Reactive eXtension) เป็น library ที่สร้างอยู่บนพื้นฐานของ observer pattern เพื่อเสริมความสามารถของภาษาให้สามารถเขียนในสไตล์ functional และ reactive ได้ มีให้เลือกหลายภาษาเช่น RxJava, RxJS, RxCpp, RxSwift

คำว่า reactive แปลว่าตอบสนองต่อสิ่งเร้า สิ่งเร้าในที่นี้ก็คือไอเทมที่ observable push ออกมาแหละครับ โค้ดสไตล์ rx จะเป็นการเขียนเพื่อรับมือกับไอเทม(หรือ event)ที่เกิดขึ้น จะมีแค่ไอเทมเดียว หรือมีต่อเนื่องกันหลายไอเทมก็แล้วแต่ เราจะเป็นคนสร้าง Observable เองหรือจะให้ library อื่นช่วยสร้างให้ก็แล้วแต่ โดยสามารถกรอง เปลี่ยนแปลง ผสม รวบรวมไอเทมเหล่านั้น หรือเปลี่ยน thread การทำงานได้โดยง่าย

ผมว่าแก่นของ ReactiveX มันก็มีเพียงเท่านี้นะครับ แต่ด้วยความที่มันมีคอนเซปใหม่อย่าง functional, reactive, observer ที่เราอาจไม่รู้จักมาก่อนรวมเข้าด้วยกัน แล้วไหนจะยังมี observable, subscriber, operator มากมายให้ใช้งาน เราจะรู้สึกมึนก็ไม่แปลก กว่าผมจะอ๋อก็เอ๋อไปเป็นสัปดาห์เหมือนกัน เพราะฉะนั้นโพสนี้ก็เลยตั้งใจเขียนเพื่อปรับพื้นฐาน ให้เข้าใจกันก่อนว่ามันคืออะไร มีไว้ทำไม

แล้วจะตื่นเต้นกันทำไม

http://jakewharton.com/exploring-rxjava-2-for-android/

การเขียน app ในปัจจุบันมันมีความ reactive ในตัวอยู่แล้วครับ ไม่ว่าจะเป็น web app หรือ mobile app เราก็ต้องคอยฟัง user event ไม่ว่าจะเป็น button click หรือ checkbox แล้วเราก็ต้องตอบสนองต่อ event นั้น อาจจะง่ายอย่างการเปลี่ยนสี background หรือเรียก api แสดงข้อมูลเพิ่ม หรืออาจจะซับซ้อนถึงขนาดเรียกหลายๆ api เอาข้อมูลมารวมกันจากนั้นค่อยแสดงผล หรือจะเป็นการฟัง socket แล้วไหนยังจะต้องคอยฟัง system event อีก โอย แค่นี้ก็วุ่นแล้ว

ที่แย่ที่สุดคือเราต้องรับมือกับ event พวกนี้ด้วยโค้ดสไตล์ imperative ผ่าน callback ซึ่งง่อยมาก จะทำอะไรต่อกันก็ต้องซ้อนกันเข้าไปแล้วก็เกิด callback hell ขึ้น หรือสมมติจะยิง api 2 อันค่อยแสดงผลนี้ก็ต้องมาเช็คว่าทำสำเร็จแล้วทั้ง 2 api ใน 2 callback แค่คิดก็ขี้เกียจแล้วครับ ReactiveX เกิดขึ้นมาเพื่อให้การทำงานแบบนี้ง่ายขึ้น

TL;DR

  • การเขียนแอพทั้งเว็บและโมบายในปัจจุบัน มีความ reactive อยู่ในตัว เราต้องตอบสนองต่อเหตุการณ์ต่างๆ ไม่ว่าจะเป็น user input, api call, system event
  • imperative programming จัดการพวกนี้ด้วย callback ซึ่งถ้ามี callback เยอะๆแล้วโค้ดจะอ่านไม่รู้เรื่อง
  • observer pattern เป็น pattern สำหรับการคอยฟังเพื่อตอบสนองต่อข้อมูล/เหตุการณ์ที่เกิดขึ้น
  • RxJava สานต่อ observer pattern ออกมาทำให้ใช้งานได้อย่างยืดหยุ่น สามารถประมวลผลข้อมูลได้ง่าย และอ่านรู้เรื่องมากขึ้น ด้วยการใช้คอนเซปจาก functional programming

สำหรับโพสนี้เอาไว้เท่านี้ก่อน อ่านรู้เรื่องไม่รู้เรื่อง อยากให้เพิ่มเติมตรงไหน อยากให้ปรับปรุงตรงไหนบอกได้ครับ เรามาเรียนรู้ไปด้วยกัน
และเช่นเคย ช่วยกัน 💚 recommend/share แผยแพร่กันให้ทั่วครับ

--

--

Travis P
Black Lens

Android Developer, Kotlin & Flutter Enthusiast and Gamer