Sunday, June 16, 2013

Abstract, Interface Programming

ก่อนหน้านี้ เราก็ได้มีการพูดถึงเรื่องของ OOP กันมาบ้างเล็กน้อย ซึ่งก็ทำให้เราได้รู้ว่า คุณลักษณะสำคัญต่างๆของ OOP ที่จำเป็นต้องมีนั้น ก็ประกอบด้วย Encapsulation, Inheritance และ Polymorphism ซึ่งแต่ละองค์ประกอบนั้น ก็ถูกแบ่งออกเป็นคุณลักษณะของแต่ละภาษาออกไปตามแต่ละไวยากรณ์ของภาษานั้นๆ แต่ก็ยังมีวิธีการทำงานที่คล้ายๆกันอยู่

บทความนี้ ซึ่งจะกล่าวถึง Abstract Class และ Interface Programming นั้น จะถือว่าถูกจัดอยู่ใน Polymorphism หรือก็คือความหลากหลายนั่นเอง ซึ่งจะอธิบายออกมาด้วยไวยากรณ์ของภาษา Java เพราะเป็นภาษาที่ทำความเข้าใจได้ง่ายกว่า C++ ในหลายๆลักษณะ และเป็นภาษาที่มีผู้เริ่มต้นในการศึกษาการโปรแกรมมิ่ง ศึกษาอยู่มากพอสมควร

ก่อนอื่น เราควรจะเข้าใจความหมายของ Abstract Class และ Interface Programming ให้ดีก่อน เพื่อที่จะได้ศึกษาได้อย่างถูกทาง

Abstract Class -> Class ที่ทำหน้าที่มาเป็น Super Class สำหรับเพื่อให้สืบทอดโดยเฉพาะ โดยจะไม่สามารถนำมาสร้างเป็น Instance เองได้ โดยคลาสลูก จะต้องสืบทอด และ ทำการ เขียนการทำงานใหม่(Override) ให้ Abstract Method ทั้งหมด

Interface -> เป็นคุณสมบัติเพื่อให้ Class ทำการ Implement (คล้ายการสืบทอด) ใช้เพื่อเพิ่มคุณสมบัติให้ Class นั้นๆ โดยจะต้องทำการ Override Method ต่างๆใน Interface ให้หมด

Abstract Class


เมื่ออ่านมาถึงตอนนี้ อาจมีผู้อ่านบางคนสงสัยว่า Abstract Class นั้น มีความจำเป็นอย่างไร เนื่องจากหากเราจะสร้าง Class ขึ้นมา Class หนึ่งแล้ว ทำไมเราจึงจะต้องไม่ให้ Class นั้นๆ สามารถสร้าง Instance ขึ้นมาด้วย น่าจะสร้างขึ้นมาให้เป็น Class ปกติ ก็ยังสามารถถูกสืบทอดได้อยู่ดี

คำตอบในส่วนนี้ หากจะอธิบายแล้ว คงต้องอธิบายด้วยการยกตัวอย่าง จะเข้าใจง่ายที่สุด เริ่มด้วยการแสดงถึงโครงสร้างของการสืบทอด ดังข้างล่างจะดีกว่า



จากรูปด้านบน เป็นการแสดงการสืบทอด โดยจะมี Superclass เป็น Class ชื่อ Shape(รูปร่าง) ซึ่งจะมี Class ที่มาสืบทอด ก็คือ Circle(วงกลม) Triangle(สามเหลี่ยม) และ Rectangle(สี่เหลี่ยม)

ทีนี้้ เราต้องการที่จะบังคับให้แต่ละ Class ที่สืบทอดจาก Shape มีความสามารถในการ "หาพื้นที่" (calArea()) ซึ่งเราสามารถทำได้ โดยกำหนดให้ Class Shape นั้น มี Method calArea() ขึ้นมา และเมื่อ Class ใดๆ ทำการสืบทอด Class Shape ไปแล้ว ก็จะถูกบังคับให้สืบทอด Method calArea() ไปด้วย

แต่เนื่องจาก รูปร่าง(Shape) นั้น เป็นสิ่งที่ถูกสมมุติขึ้นมา ไม่มีรูปร่างที่แน่นอน จึงไม่สามารถเขียนการทำงานภายใต้ Method calArea() ได้ เราจึงต้องสร้าง calArea() ของ Shape ให้เป็น Abstract Method เพื่อที่จะได้ไม่ต้องกำหนดการทำงานให้ Method นี้

Class Shape
public abstract class Shape {
public abstract float calArea();
}

เมื่อ Class ที่เหลือทั้ง 3 ทำการสืบทอด Class Shape ไปแล้ว ก็จำเป็นต้องทำการ Override Abstract Method ของ Superclass ทั้งหมด


Class Circle
public class Circle extends Shape {
double radius;
public Circle(double radius)
{
this.radius = radius;
}

@Override
public double calArea() {
return Math.PI * Math.pow(radius, 2);
}
}



Class Rectangle
public class Rectangle extends Shape {
double lenght;
double height;


public Rectangle(double lenght, double height)
{
this.lenght = lenght;
this.height = height;
}

@Override
public double calArea() {
return lenght * height;
}
}


Class Triangle
public class Triangle extends Shape {
double base;
double height;


public Triangle(double base, double height)
{
this.base = base;
this.height = height;
}

@Override
public double calArea() {
return base * height * 0.5;
}
}

เราก็จะสามารถเรียกใช้ใน main() ได้ดังนี้

public class MainClass {
public static void main(String[] args) {
Circle c = new Circle(5);
Triangle t = new Triangle(3, 6);
Rectangle r = new Rectangle(10, 5);

System.out.println(c.calArea());
System.out.println(t.calArea());
System.out.println(r.calArea());
}
}


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

เนื่องจากแต่ละ Class สืบทอดมาจาก Class เดียวกัน เราจึงสามารถนำ Class ทั้ง 3 ชนิดนี้ หรือจะซัก ร้อยพันชนิด ที่สืบทอดมาจาก Class นี้ มาเก็บใน Array เดียวกัน และวนลูปทำได้

public class MainClass {
public static void main(String[] args) {
Shape[] s = {new Circle(5), new Triangle(3, 6), new Rectangle(10, 5)};
for(int i = 0; i < s.length; i++)
System.out.println(s[i].calArea());
}
}

วิธีนี้ จะมีประโยชน์มาก เมื่อเราต้องใช้ Class ที่หลากหลายชนิด แต่ก็ถือว่าเป็นชนิดเดียวกัน (Subclass is a Superclass) ซึ่งทำให้เราไม่ต้องเขียนการควบคุมหลายๆครั้ง กับการทำงานชนิดเดียวกัน

นอกจากนี้ เรายังสามารถนำวิธีนี้ มาใช้กับการส่งผ่าน Parameter ที่เราต้องการควบคุมขอบเขตของชนิดของ Argument ได้อีกด้วย



public class MainClass {
private static void printShape(Shape shape)
{
System.out.println(shape.calArea());
}

public static void main(String[] args) {
Circle c = new Circle(5);
Triangle t = new Triangle(3, 6);
Rectangle r = new Rectangle(10, 5);

printShape(c);
printShape(t);
printShape(r);
}
}


Interface


เรื่องของ Interface นั้น หากจะศึกษา ควรจะทำการศึกษาเรื่องของ Abstract Class ให้เข้าใจเสียก่อน เพราะ Interface นั้น แม้แนวคิดการทำงานจะต่างกัน แต่การนำมาใช้นั้น คล้ายกันมาก

เมื่อพูดถึง Interface แล้ว เราก็มักจะนึกถึง คุณสมบัติต่างๆ ที่นำมาเพิ่มให้กับ Class เช่นสมมุติว่า เรามี Interface 2 ตัว ชื่อว่า IFlyAble(บินได้) กับ IRunAble(วิ่งได้) โดยเรากำหนดให้ Interface ทั้ง 2 มีลักษณะดังนี้

public interface IFlyAble {
public void Fly();
}



public interface IRunAble {
public void Run();
}

และเราก็มี Class ขึ้นมาอีก 3 Class คือ Human(คน), Bird(นก) และ Plane(เครื่องบิน) โดย Human นั้น สามารถวิ่งได้ Bird สามารถบินได้ และ Plane ทำได้ทั้งวิ่งและบิน

ซึ่งเมื่อเราเอาทั้ง 3 Class มาเขียนเป็น Code แล้ว เราก็ต้องทำการ Override Method ทั้งหมดของ Interface ที่แต่ละ Class ทำการ Implement มา

public class Human implements IRunAble {
@Override
public void Run() {
System.out.println("Human Run");
}
}

public class Bird implements IFlyAble{
@Override
public void Fly() {
System.out.println("Bird Fly");
}
}

public class Plane implements IFlyAble, IRunAble{
@Override
public void Run() {
System.out.println("Plane Run");
}


@Override
public void Fly() {
System.out.println("Plane Fly");
}
}

ทีนี้ เราจะลองสร้าง Method ขึ้นมา ชื่อว่า goFly() กับ goRun() โดยที่กำหนดว่า Argument ที่ถูกส่งมานั้น หากส่งเข้า goFly() ก็จะต้องบินได้ (implement IFlyAble) และ หากส่งเข้า goRun() ก็จะต้องวิ่งได้ (implement IRunAble) เช่นกัน

private static void goFly(IFlyAble flyAble)
{
flyAble.Fly();
}

private static void goRun(IRunAble runAble)
{
runAble.Run();
}
เราก็สามารถนำมาเรียกใช้ใน main ได้ดังนี้

public static void main(String[] args) {
Human h = new Human();
Bird b = new Bird();
Plane p = new Plane();

goRun(h);
goFly(b);
goRun(p);
goFly(p);
}
แต่เราไม่สามารถสั่งคนไปบินได้ เพราะคนบินไม่ได้(ไม่ได้ implement IFlyAble) การเรียก Code ลักษณะนี้จึงทำไม่ได้

goFly(h);

ประโยชน์ของการใช้ Interface นั้น เรามักจะใช้เมื่อผู้สร้าง Method ต้องการกำหนดว่า Class ใดๆ ที่จะถูกส่งมาเป็น Argument จะต้องมีคุณสมบัติตามที่กำหนดไว้ หรือก็คือการ Implement Interface ที่กำหนดไว้นั่นเอง เพราะในความเป็นจริง ผู้ที่สร้าง Method กับผู้ที่สร้าง Class อาจเป็นคนละคนกัน และอาจไม่เคยคุยกันหรือเจอกันเลยซักครั้งก็ได้ จึงไม่อาจสื่อสารให้ Class ที่ส่งเข้ามา มีคุณสมบัติเพียงพอต่อการนำไปใช้ ฉะนั้น ผู้สร้าง Method จึงทำการสร้าง Interface ขึ้นมา และกำหนดว่า หากใครก็ตาม ต้องการที่จะนำ Class ใดๆส่งมาเป็น Argument แล้ว ก็ต้องทำการ implement interface นั้นๆด้วย

หลังจากที่ได้พูดถึงทั้ง Abstract Class และ Interface มาอย่างยาวนาน ก็คิดว่าทุกคนคงได้อะไรไปไม่มากก็น้อยบ้าง ขอให้ทุกคนลองนำวิธีการนี้ ไปใช้ในการเขียนบ้าง เพราะคงช่วยลดเวลาในการทำงาน ไม่มาก ก็น้อย :)

No comments:

Post a Comment