วันพฤหัสบดีที่ 23 กรกฎาคม พ.ศ. 2563

ปัญหาเบสคลาสแตกง่ายเชิงความหมาย (Semantic Fragile Base Class Problem)

บล็อกนี้ก็เป็นบล็อกที่สามในชุดของปัญหาเบสคลาสแตกง่าย (Fragile Base Class Problem) โดยผมจะขอเรียกสั้น ๆ ว่า FBC ถ้าใครยังไม่ได้อ่านจะกลับไปอ่านก่อนก็ได้นะครับ FBCSyntactic FBC แต่ถ้าจะไม่อ่านก็ไม่เป็นไรนะครับอ่านบล็อกนี้ก่อนได้

สำหรับปัญหา Semantic FBC เป็นปัญหาที่มีผลกระทบมากกว่า Syntactic ครับ เพราะเป็นปัญหาระดับการออกแบบ ไม่ใช่ปัญหาระดับภาษาโปรแกรม นั่นหมายความว่าไม่ว่าเราจะเขียนโปรแกรมด้วยภาษา object-oriented ใด ๆ ถ้ามีการใช้ inheritance และ polymorphism มีโอกาสมีปัญหาได้หมด 

ขอทบทวนอีกสักรอบนะครับว่าปัญหานี้คือปัญหาที่เมื่อมีการแก้ไขตรรกะการทำงานของโปรแกรมในคลาสแม่แล้ว มีผลทำให้พฤติกรรมของคลาสลูกเปลี่ยนไป โดยที่ไม่ได้มีการแก้ไขคลาสลูกเลย ซึ่งปัญหานี้ถ้าเกิดขึ้น จะสร้างความปวดหัวให้กับนักพัฒนาโปรแกรมมากครับ 

เพื่อให้เข้าใจปัญหานี้มาเริ่มดูโค้ดภาษา Java ต่อไปนี้กันครับ ถ้าใครไม่อยากพิมพ์โค้ดเองดาวน์โหลดได้จากที่นี่เลยครับ 

public class SemanticFBCSuperclass {
private int number;
public SemanticFBCSuperclass(int val) {
number = val;
}
public void f1() {
System.out.println("In f1() of SemanticFBCSuperClass");
//number++;
setNumber(number+1);
}
public void setNumber(int val) {
number = val;
}
}

คลาสนี้เป็นคลาสแม่นะครับ ให้สังเกตเมท็อด f1() นะครับมีการพิมพ์ข้อความหนึ่งบรรทัด และเพิ่มค่า number ซึ่งเป็น attribute ในคลาสนี้ขึ้น 1โดยใช้เมท็อด setNumber()

ต่อไปไปดูคลาสลูกของคลาสแม่ตัวนี้กันครับ

public class SemanticFBCSubclass extends SemanticFBCSuperclass {
public SemanticFBCSubclass(int val) {
super(val);
}
@Override
public void setNumber(int val) {
System.out.println("setNumber() overridden version");
super.setNumber(val);
}
}

จะเห็นว่าในคลาสลูกนี้ โอเวอร์รายด์ (override) เมท็อด setNumber() ของคลาสแม่ โดยพิมพ์ข้อความหนึ่งบรรทัด ก่อนที่จะเรียกเมท็อด setNumber() ของคลาสแม่ 

คราวนี้มาดู Main โปรแกรมกันครับ 

*
* @author sarun
*/
public class FBC {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
SemanticFBCSubclass obj = new SemanticFBCSubclass(5);
obj.f1();

}
}

 ที่ Main โปรแกรมก็ทำงานง่าย ๆ ครับ คือสร้างอ็อบเจกต์ของคลาสลูก แล้วเรียกเมท็อด f1() ซึ่งก็จะไปเรียกเมท็อด f1() ของคลาสแม่นะครับ เพราะคลาสลูกไม่มี f1() แค่คลาสลูกสืบทอดทุกสิ่งทุกอย่างจากแม่ เมื่อคอมไพล์และรันโปรแกรมจะได้ผลดังนี้ครับ 

sarunintakosum@Saruns-MacBook-Pro semanticfbc % javac *.java
sarunintakosum@Saruns-MacBook-Pro semanticfbc % java FBC    
In f1() of SemanticFBCSuperClass
setNumber() overridden version
sarunintakosum@Saruns-MacBook-Pro semanticfbc % 

มาดูที่ผลัพธ์กันครับ ผลลัพธ์สองบรรทัดมาจากเมื่อเรียกเมท็อด f1() ก็จะเริ่มต้นทำงานโดยพิมพ์คำว่า 
In f1() of SemanticFBCSuperClass

จากนั้นเรียกเมท็อด setNumber() แต่เนื่องจากอ็อบเจกต์ที่เรียกใช้เป็นอ็อบเจกต์ของคลาสลูก และเมท็อด setNumber() ถูกโอเวอร์รายด์ ดังนั้นจะเรียก setNumber() ของคลาสลูก ซึ่งก็จะพิมพ์คำว่า  

setNumber() overridden version

จากนั้นจะกลับไปเรียก setNumber() ของคลาสแม่ต่อไป ถ้าตอนนี้ยังไม่เข้าใจก็เอาเป็นแค่ว่ามีผลลัพธ์สองบรรทัดนะครับ 

คราวนี้สมมติว่ามีการทบทวนคลาสแม่กันใหม่ แล้วก็เห็นว่าใน f1() ถ้าจะเพิ่มค่า number ขึ้น 1 จะทำงานเร็วขึ้น ถ้าเพิ่มค่าตรง ๆ เลย ไม่ต้องไปเรียกเมท็อด setNumber() ดังนั้นคลาสแม่จึงถูกแก้เป็นดังนี้ครับ 

public class SemanticFBCSuperclass {
private int number;
public SemanticFBCSuperclass(int val) {
number = val;
}
public void f1() {
System.out.println("In f1() of SemanticFBCSuperClass");
number++;
//setNumber(number+1);
}
public void setNumber(int val) {
number = val;
}
}

จะเห็นนะครับว่าใน f1() ไม่เรียก setNumber() แล้ว 

จากนั้นคอมไพล์คลาสแม่และรันโปรแกรมครับ

sarunintakosum@Saruns-MacBook-Pro semanticfbc % javac SemanticFBCSuperclass.java
sarunintakosum@Saruns-MacBook-Pro semanticfbc % java FBC                        
In f1() of SemanticFBCSuperClass
sarunintakosum@Saruns-MacBook-Pro semanticfbc % 

จะเห็นนะครับว่าผลลัพธ์เหลือ 1 บรรทัดแล้วนะครับ เพราะเมื่อไม่ได้เรียก setNumber() ก็จะไม่ไปเรียก setNumber() ของคลาสลูก ผลลัพธ์เปลี่ยนโดยที่เราไม่ได้แตะโค้ดของคลาสลูกเลย สมมติถ้าคลาสแม่เราไม่ได้เขียนเอง พอมีการเปลี่ยนเวอร์ชันของคลาสแม่แล้วส่งมาให้เราใช้ ปรากฏว่าโปรแกรมให้ผลลัพธ์เปลี่ยนไปจากเดิม ลองคิดดูครับว่าถ้านี่เป็นระบบงานที่เราทำจริง ๆ ที่มีความซับซ้อน มันจะยุ่งแค่ไหน และถ้าเราไม่มีโค้ดของคลาสแม่ เราก็อาจไม่รู้เลยว่าเกิดอะไรขึ้น และปัญหานี้แก้ไม่ได้ด้วยการคอมไพล์ใหม่ วิธีแก้คือต้องออกแบบกันใหม่อย่างเดียว 

ดังนั้นคงเห็นแล้วนะครับว่าปัญหา FBC โดยเฉพาะ Semantic FBC ทำให้เกิดปัญหาถ้าต้องมีการเปลี่ยนแปลงโปรแกรม เนื่องจากมีความขึ้นต่อกันระหว่างคลาสแม่และคลาสลูก ดังนั้นในการพัฒนาโปรแกรมโดยใช้แนวคิดของคอมโพเนนต์ จึงหลีกเลี่ยงการใช้งาน inheritance และ polymorphism และใช้การประกอบแทน 

ก็เป็นอันว่าจบชุดของปัญหา FBC กันไว้ตรงนี้นะครับ โอกาสหน้าจะเขียนเรื่องอะไร ถ้าสนใจก็คอยคิดตามได้นะครับ

ไม่มีความคิดเห็น:

แสดงความคิดเห็น