ทำไมต้อง Design Patterns ?
ในการเขียนโปรแกรมอะไรขึ้นมาสักอย่างหนึ่งนอกจากการทำให้โปรแกรมใช้งานได้ ก็อาจจะต้องคิดเผื่อไปถึงการพัฒนาต่อ หรือการแก้ไขปรับปรุงในอนาคต ซึ่งทุกคนก็น่าจะรู้กันว่าเวลาที่เขียนโค้ดก็ควรจะเขียนโค้ดให้ดีให้อ่านง่าน แต่ปัญหาก็คือมาตรฐานของโค้ดที่ดีแต่ละคนนั้นไม่เหมือนกัน ถ้าเขียนโค้ดกับเพื่อนก็อาจจะทดลองกันว่าแบบของใครดีกว่าก็อาจจะตกลงกันว่าจะยึดรูปแบบการเขียนแบบไหนได้ แต่มันจะดีกว่ารึเปล่าถ้าเรามีมาตรฐานให้ทำตาม โดยไม่ต้องลองผิดลองถูกขึ้นมาใหม่
แนวคิดของ Design Patterns เป็นยังไง ?
Design Pattern เป็นพิมพ์เขียว (blueprint) สำหรับการออกแบบซอฟต์แวร์ เพื่อให้การเขียนโค้ดได้มาตรฐานเดียวกัน จะได้แก้ปัญหาโค้ดไม่ดีที่นำไปสู่ซอฟต์แวร์ที่ไม่มีคุณภาพได้ โดย design pattern นี้กว่าจะเกิดขึ้นมาแต่ละแบบก็มาจากการทดลองซ้ำๆจนหารูปแบบหรือวิธีที่ดีที่สุดในการแก้ปัญหาแต่ละอย่าง และด้วยความที่มันเป็นรูปแบบที่ใช้แก้ปัญหาต่างๆ เราจึงไม่สามารถที่จะก็อปปี้เอามาใช้งานทันที แต่ต้องทำความเข้าใจของ design pattern นั้นๆและนำไปปรับใช้กับโค้ดของเราเอง
ตัวอย่าง Design Patterns เจ๋ง ๆ 7 Pattern ที่คิดว่ามันแก้ปัญหาเดิม ๆ ได้ดีมาก ๆ
ก่อนอื่นต้องบอกก่อนว่า design pattern นั้นเค้าแบ่งออกได้เป็น 3 กลุ่มที่มีเป้าหมายต่างกันออกไป ประกอบด้วย
- Creational patterns – เป็นกลุ่มที่ไว้ใช้สร้าง object ในรูปแบบต่างๆ ให้มีความยืดหยุ่น(flexible) และนำโค้ดมาใช้ซ้ำ(reuse)ได้
- Structural patterns – กลุ่มนี้จะเป็นวิธีการนำ object และ class มาใช้งานร่วมกัน สร้างเป็นโครงสร้างที่มีความซับซ้อนยิ่งขึ้น โดยที่ยังมีความยืดหยุ่นและทำงานได้อย่างมีประสิทธิภาพ
- Behavioral patterns – กลุ่มสุดท้ายนี้เป็นวิธีการออกแบบการติดต่อกันระกว่าง object ให้มีความยืดหยุ่นและสามารถติดต่อกันกันได้อย่างไม่มีปัญหา
สนใจหัวข้อไหนกดอ่านได้เลย
1. Factory Method
2. Builder
3. Singleton
4. Adapter
5. Facade
6. Observer
7. Template Method
1. Factory Method
อยู่ในกลุ่ม Creational patterns ดังนั้นพอแปลตามชื่อด้วยแล้ว Factory Method นี้ก็จะเป็นโรงงานสำหรับผลิต object นั่นเอง โดยมีเป้าหมายเพื่อให้การสร้าง object สามารถสร้าง oject หลายรูปแบบได้ โดยที่ตอนเรียกใช้ ใช้วิธีที่เหมือนกันได้ ลองดูตัวอย่างอย่างการสั่งเครื่องดื่มกัน
สมมติให้โรงงานในที่นี้คือพนักงานในร้าน และ object ก็คือเครื่องดื่มต่างๆภายในร้าน ตอนที่ลูกค้าสั่งเครื่องดื่มนั้น ไม่ว่าจะต้องการกาแฟ, ชาเขียว หรืออะไรก็แล้วแต่ สิ่งที่ต้องนั้นเหมือนกันทั้งหมดคือการบอกกับพนักงานและก็จะได้เครื่องดื่มนั้นๆหลับมา โดยไม่ต้องสนใจกระบวนการใรการชงเครื่องดื่มเลย ดังนั้นประโยชน์ในการนำ pattern นี้ไปใช้ก็คือลดความซับซ้อนในการสร้าง Object ลงได้ และถ้ามีการเพิ่มเติมชนิดของ object ก็ไม่กระทบกับวิธีการสร้างให้วุ่นวาย
Main.java
class Main {
public static void main(String[] args) {
Barista barista = new Barista();
// สั่งกาแฟกับบาริสต้า
Beverage order1 = barista.order("coffee");
// สั่งให้ทำมาให้ก็จะได้กาแฟออกมา
order1.brew();
// สั่งชาเขียวกับบาริสต้า
Beverage order2 = barista.order("greentea");
// สั่งเหมือนกับเครื่องดื่มอื่นๆ
order2.brew();
// สั่งโคล่ากับบาริสต้า
Beverage order3 = barista.order("cola");
// สั่งเหมือนๆกันกับเครื่องดื่มอื่น
order3.brew();
}
}
Barista.java
public class Barista {
public Beverage order(String beverageType) {
if (beverageType.equals("coffee")) {
return new Coffee();
} else if (beverageType.equals("greentea")) {
return new Greentea();
} else if (beverageType.equals("cola")) {
return new Cola();
}
return null;
}
}
Beverage.java
public interface Beverage {
void brew();
}
Coffee.java
public class Coffee implements Beverage {
@Override
public void brew() {
make();
System.out.println("กาแฟได้แล้วครับ");
}
private void make() {
String doSomeThing = "ฉีกซองเทใส่แก้วเติมน้ำร้อน";
}
}
Cola.java
public class Cola implements Beverage {
@Override
public void brew() {
openBottle();
System.out.println("โคล่าได้แล้วครับ");
}
private void openBottle() {
String doSomeThing = "เปิดขวดแล้วเทใส่แก้ว";
}
}
Greentea.java
public class Greentea implements Beverage {
@Override
public void brew() {
System.out.println("ชาเขียวได้แล้วครับ");
}
private void collectingTeaLeaves() {
String doSomeThing = "เก็บยอดอ่อนใบชาจากความสูง 1200 เมตร เหนือน้ำทะเล";
}
}
Output:
กาแฟได้แล้วครับ
ชาเขียวได้แล้วครับ
โคล่าได้แล้วครับ
2. Builder
อยู่ในกลุ่ม Creational patterns เช่นกัน ลองมาดูปัญหากันก่อนว่าทำไมถึงมี pattern นี้ขึ้นมา ยกตัวอย่างการสั่งอาหารตามสั่งอย่างผัดกะเพรา ที่สมมุติให้มีวัตถุหลักคือพริก, กระเทียม, เนื้อสัตว์ และน้ำปลา ซึ่งเวลามีคนมาสั่งแม่ค้าก็จะทำให้เราโดยใช้ Builder pattern นี้อยู่ตลอด !
สมมุติมีลูกค้าสองคนคนนึงสั่งกะเพราหมูสับ อีกคนสั่งกะเพราไก่ ถ้าแม่ค้าร้านนี้ไม่ใช้ Builder เวลาลูกค้าคนแรกสั่งก็จะต้องสั่งว่า ‘เอากะเพราหมูสับ ใส่กระเทียม พริก น้ำปลา’ ลูกค้าคนที่สองมาสั่งก็จะต้องสั่งว่า ‘กะเพราไก่ใส่กระเทียม ไม่ใส่พริก ใส่น้ำปลา’ ซึ่งในความเป็นขริงเราไม่ต้องสั่งแบบนั้นด้วย Builder ที่แม่ค้าใช้อยู่ แม่ค้าจะทำกะเพราให้กับเราด้วยวัตถุดิบหลักที่กำหนดเอาไว้ ส่วนใครที่อยากระบุอะไรที่เฉพาะเจาะจงก็ค่อยบอกเท่าที่จำเป็นพอ ลูกค้าทั้งสองคนก็เพียงสั่งแค่ ‘กะเพราหมูสับ’ กับ ‘กะเพราไก่ไม่ใส่พริก’ ก็พอ ถ้าเป็นการสร้าง class ในโค้ดแล้วก้จะลดความยาว ความยุ่งยากในการสร้างลงไปได้มากๆตัวอย่าง class HolyBasil(กะเพรา) ที่ใช้ builder pattern โดยจะไม่ต้องสร้าง setter เหมือนปกติ แต่จะใช้ ฺBuilder class ที่เราสร้างขึ้นมาแทน
HolyBasil.java
public class HolyBasil {
private final String meat; // จำเป็นต้องใส่
private final String fishSauce; // ไม่จำเป็น
private final String garlic; // ไม่จำเป็น
private final String chili; // ไม่จำเป็น
private HolyBasil(HolyBasilBuilder builder) {
this.meat = builder.meat;
this.fishSauce = builder.fishSauce;
this.garlic = builder.garlic;
this.chili = builder.chili;
}
//ต้องมี getter แต่ไม่ต้องมี setter
public String getMeat() {
return meat;
}
public String getfishSauce() {
return fishSauce;
}
public String getgarlic() {
return garlic;
}
public String getchili() {
return chili;
}
@Override
public String toString() {
String order = "HolyBasil "+this.meat + ", fishSauce:"+this.fishSauce+", garlic:"+this.garlic+", chili:"+this.chili;
return order;
}
}
HolyBasilBuilder.java – Builder ที่ทำหน้าที่เป็น setter
public static class HolyBasilBuilder {
private final String meat;
private String fishSauce;
private String garlic;
private String chili;
public HolyBasilBuilder(String meat) {
this.meat = meat;
}
public HolyBasilBuilder fishSauce(String fishSauce) {
this.fishSauce = fishSauce;
return this;
}
public HolyBasilBuilder garlic(String garlic) {
this.garlic = garlic;
return this;
}
public HolyBasilBuilder chili(String chili) {
this.chili = chili;
return this;
}
//object สุดท้ายที่จะส่งออกไป
public HolyBasil build() {
HolyBasil holyBasil = new HolyBasil(this);
return holyBasil;
}
}
Main.java – การเรียกใช้
class Main {
public static void main(String[] args) {
HolyBasil dish1 = new HolyBasil.HolyBasilBuilder("Pork")
.fishSauce("นิดเดียว")
.garlic("เยอะๆ")
.chili("ไม่พริก")
.build();
System.out.println(dish1);
HolyBasil dish2 = new HolyBasil.HolyBasilBuilder("Chicken")
.chili("ขอเผ็ดๆ")
.build();
System.out.println(dish2);
HolyBasil dish3 = new HolyBasil.HolyBasilBuilder("Shimp")
.build();
System.out.println(dish3);
}
}
ผลลัพธ์ที่ออกมา:
HolyBasil Pork, fishSauce:นิดเดียว, garlic:เยอะๆ, chili:ไม่พริก
HolyBasil Chicken, fishSauce:null, garlic:null, chili:ขอเผ็ดๆ
HolyBasil Shimp, fishSauce:null, garlic:null, chili:null
เท่านี้เวลาเราสั่งแม่ค้าก็จะไม่ต้องสั่งถึงวัตถุดิบทั้งหมดสามารถสั่งแค่กะเพรากุ้ง แม่ค้าก็จะทำกะเพรากุ้งโดยที่ใส่อย่างอื่นตามสูตรของแม่ค้า
3. Singleton
อีกสักตัวนึงในกลุ่ม Creational patterns สำหรับตัวนี้จากชื่อแล้วก็น่าจะพอเดาได้ว่าเกี่ยวกับอะไรสักอย่างที่มันเดี่ยวๆ มีอันเดียว ในที่นี้ก็คือการจำกัด object ที่สร้างขึ้นมาให้มีเพียงตัวตัว ไม่ถูกสร้างซ้ำจนเกิดความซ้ำซ้อน หรือทำงานผิดพลาด มาลองดูตัวอย่างจากในชีวิตประจำวันของเรากันดีกว่า
สมมติให้มีห้องน้ำอยู่ห้องหนึ่งแต่ยังไม่ได้ใส่ประตู พอมีคนจะใช้ห้องน้ำก็สร้างประตูขึ้นมา 1 บานแล้วเข้าไปใช้งาน พอคนถัดไปจะมาใช้ห้องน้ำก็สร้างประตูใส่เข้าไปเพิ่มอีกแล้วเข้าใช้ พอคนต่อๆไปมาใช้ก็สร้างประตูเพิ่มเข้าไปอีก นานๆไปก็มีประตูติดอยู่ที่ห้องน้ำเต็มไปหมด ตอนล็อกประตูก็ยังมีประตูอื่นๆเต็มไปหมด ใช้งานห้องน้ำไม่ได้สักที เทียบเป็นโค้ดก็เช่นการที่เราไม่ควบคุมการสร้าง object ใหม่จากคลาสบางคลาส พอเรียกใช้งานก็สร้างใหม่ตลอดจนมี object อยู่ในระบบเยอะเกินไป จะควบคุมการทำงานก็ยากหรืออาจทำให้ทรพยากรต่างๆถูกใช้ไปโดยไม่จำเป็น ดังนั้นจึงมี singleton ขึ้นมาเพื่อไว้ใช้เพื่อคุมกำเนิดให้ class สามารถสร้าง object ได้เพียงตัวเดียว เหมือนกันห้องน้ำที่ถ้าสร้างประตูไว้แล้วก็ใช้ประตูเดิม ไม่ต้องสร้างใหม่ขึ้นมาอีกนั่นเอง
ToiletDoor.java
public class ToiletDoor {
// สร้างประตูที่มีได้แค่อันเดียวเอาไว้
private static ToiletDoor instance = new ToiletDoor();
// กำหนดให้ constructor เป็น private จะได้ไม่มีใครสร้างประตูขึ้นมาได้อีก
private ToiletDoor(){}
// ให้คนที่จะใช้ประตูเรียกใช้ผ่าน getInstance() แล้วส่งประตูที่มีอยู่ไปให้ใช้
public static ToiletDoor getInstance(){
return instance;
}
public void openTheDoor(){
System.out.println("Door is opened.");
}
public void closeTheDoor(){
System.out.println("Door is closed.");
}
}
Main.java
public class Main {
public static void main(String[] args) {
// คนที่จะใช้ประตูก็ใช้ผ่าน getInstance
ToiletDoor toiletDoor = ToiletDoor.getInstance();
// ลองใช้งานประตูได้ปกติ
toiletDoor.openTheDoor();
toiletDoor.closeTheDoor();
// จะไม่สามารถสร้างประตูใหม่แบบนี้ได้เพราะว่า constructor นั้นเป็น private อยู่
// ToiletDoor newToiletDoor = new ToiletDoor();
}
}
Output:
Door is opened.
Door is closed.
4. Adapter
คราวนี้เปลี่ยนมาดูในกลุ่ม Structural patterns กันบ้าง อ่านชื่อแล้วก็ยกตัวอย่างได้ง่ายๆจากชีวิตประจำวันของเราเลยก็คือเป็นการออกแบบเพื่อให้เราสามารถใช้งานผ่านตัวแปลงได้ เช่นเรามีปลั๊กสามตาแต่ที่บ้านมีเต้ารับแบบที่เป้น 2 รู ก็ไม่สามารถใช้งานได้ เราจึงซื้อตัวแปลงมาแล้วเสียบผ่านตัวแปลงนั้นแทน
ซึ่งถ้าเราไปต่างประเทศเจอรูปลั๊กที่ต่างออกไปเราก็แค่ปรับเปลี่ยนตัวแปลงของเรา และเสียบใช้งานผ่านตัวแปลงเหมือนเดิม เช่นเดียวกันกับการเขียนโค้ด เราอาจจะใช้งาน library ภายนอกด้วยการสร้าง class มาใช้เป็น adapter ถ้าในอนาคต library นั้นมีการอัพเดตแล้วมีวิธีเรียกใช้งานเปลี่ยนไป เราก็แค่แก้ไขในส่วน adapter ก็พอ ก็สามารถใช้งานโค้ดหลักได้โดยไม่ต้องแก้ไขอะไร
Main.java
interface TypeA {
public void plugTypeA();
}
class OutletA implements TypeA {
public void plugTypeA() {
System.out.println("Plug into type A outlet.");
}
}
interface TypeB {
public void plugTypeB();
}
class OutletB implements TypeB {
public void plugTypeB() {
System.out.println("Plug into type B outlet.");
}
}
// สร้าง adapter ที่ implement type B เพื่อตอนที่ใช้งาน
// พอสร้าง object จากคลาสนี้แล้วจะได้สามารถใช้งานได้เหมือน type B
class B_to_A_Adapter implements TypeB {
TypeA typeA;
// เนื่องจากเราต้องการให้ adapter นี้ใช้ความสามารถจากคลาส type A ได้
// ดังนั้นเราจึงต้องรับ object คลาส type A ตอนที่สร้าง adapter
// เหมือนกับการที่เราต้องเอา adapter แปลงไปเสียบกับปลั๊กที่กำแพง
public B_to_A_Adapter(TypeA typeA) {
this.typeA = typeA;
}
// สร้างการเรียกใช้งานของปลั๊ฏ type B เหมือนกัน adapter ที่จะมีรูหน้าตาเป็นปลั๊ก 3 ขา
// แต่ขาที่จะไปเสียบกำแพงมีแค่ 2 ขา ก็คือเรียกใช้งาน object type a
// เหมือนกับ adapter ที่เสียบกำแพงอยู่
public void plugTypeB() {
typeA.plugTypeA();
}
}
class Main {
public static void main(String args[]) {
// สร้างรูปลั๊กแบบ A และแบบ B
OutletA myPlugTypeA = new OutletA();
OutletB myPlugTypeB = new OutletB();
// สร้าง adapter ที่รูเป็นแบบ B มี 3 รู
// แต่การทำงานจริงๆคือมีขา 2 ขาเพื่อไว้ไปเสียบ type A ได้
TypeB myAdapter = new B_to_A_Adapter(myPlugTypeA);
// เสียบปลั๊ก 2 ขาเข้าไปในรูปลั๊ก 2 รู
System.out.print("My Type A Plug: ");
myPlugTypeA.plugTypeA();
// เสียบปลั๊ก 3 ขาเข้าไปในรูปลั๊ก 3 รู
System.out.print("My Type B Plug: ");
myPlugTypeB.plugTypeB();
// เสียบปลั๊ก 3 ขาเข้าไปในรูปลั๊ก 2 รู ใช้งานได้ปกติ
System.out.print("My Type B Plug: ");
myAdapter.plugTypeB();
}
}
Output:
My Type A Plug: Plug into type A outlet.
My Type B Plug: Plug into type B outlet.
My Type B Plug: Plug into type A outlet.
5. Facade
สำหรับตัวนี้ก็ยังเป็น Structural patterns อยู่ โดย Facade นั้นไว้ใช้แก้ปัญหาการที่ต้องทำงานหลายๆอย่าง ให้สามารถเรียกใช้ได้ด้วยคำสั่งเดียว อย่างเช่นการที่จะเปลี่ยนแพ็คเกจมือถือ
เราอาจไม่ต้องไปหาข้อมูลเอง หารายละเอียด หาเบอร์กดสมัคร แต่ทำได้ด้วยการโทรหา call center ของผู้ให้บริการและ call center จะช่วยจัดการขั้นตอนทั้งหมดให้เราเองตั้งแต่นำเสนอโปรโมชัน บอกรายละเอียดและทำการสมัครให้เรา ในการนำ Facade ไปใช้ในโค้ดนั้นถ้าใช้มากเกินไปก็อาจจะเป็นการทำให้โค้ดมีความซับซ้อนและแก้ไขยากขึ้น ต้องเลือกใช้ให้เหมาะสมด้วยความระมัดระวัง
Facade Pattern จะรวมการทำงานที่มีหลายขั้นตอน และเรียกใช้ด้วยคำสั่งเดียวดังตัวอย่าง
CallCenterFacade.java
public class CallCenterFacade {
public void changePackage(String number, String noPackage) {
System.out.println("ตรวจสอบแพคเกจปัจจุบันของหมายเลข"+number);
System.out.println("ตรวจสอบข้อมูลการชำระเงิน");
System.out.println("ยืนยันสิทธิ์การเปลี่ยนแพคเกจ");
System.out.println("เปลี่ยนแพคเกจเป็นแพคเกจหมายเลข"+noPackage);
System.out.println("การดำเนินการเสร็จสิ้น");
System.out.println("===================");
}
public void review(int score, String comment) {
System.out.println("ขอบคุณที่ไว้ใจในบริการของเรา");
System.out.println("เราจะเก็บข้อมูลไว้เพื่อพัฒนาต่อไป");
}
}
การเรียกใช้ก็สามารถเรียกใช้ด้วยคำสั่งเดียว
Main.java
class Main {
public static void main(String[] args) {
CallCenterFacade userRequest = new CallCenterFacade();
userRequest.changePackage("08XX6XX11", "5");
userRequest.review(10, "งานเร็วงานไวมากครับ");
}
}
ผลลัพธ์ที่ออกมา:
ตรวจสอบแพคเกจปัจจุบันของหมายเลข08XX6XX11
ตรวจสอบข้อมูลการชำระเงิน
ยืนยันสิทธิ์การเปลี่ยนแพคเกจ
เปลี่ยนแพคเกจเป็นแพคเกจหมายเลข5
การดำเนินการเสร็จสิ้น
===================
ขอบคุณที่ไว้ใจในบริการของเรา
เราจะเก็บข้อมูลไว้เพื่อพัฒนาต่อไป
6. Observer
มาถึงตัวรองสุดท้ายกับกลุ่ม Behavioral patterns สำหรับ Observer นี้จะเป็นการออกแบบเพื่อสร้างการติดต่อรับข้อมูลกันแบบ one to many อย่างการกด subscribe ช่อง Youtube สักช่องหนึ่ง
ถ้าหากมีการลงคลิปใหม่ๆก็จะมีการแจ้งเตือนไปยังคนที่กด subscribe เอาไว้ทุกคนนั่นเอง ถ้าเป็นในโค้ดก็เกือบจะเหมือนกันก็คือไว้ใช้ออกแบบเพื่อให้สามารถกระจายข้อมูลไปยังทุก object ที่มา subscribe เอาไว้ได้นั่นเอง
BorntoDevChannel.java – เป็น subject ให้ observer มา subscribe
import java.util.ArrayList;
import java.util.List;
public class BorntoDevChannel {
// สร้าง List ไว้เก็บคนที่ subscribe ช่องเอาไว้
private List<Viewer> subscribers = new ArrayList<Viewer>();
// method สำหรับให้คนมา subscribe
public void subscribe(Viewer viewer){
subscribers.add(viewer);
}
// ส่งการแจ้งเตือนไปยังทุกคนใน list
public void notifyAllSubscriber(String videoName){
for (Viewer viewer : subscribers) {
viewer.update(videoName);
}
}
}
Viewer.java
public abstract class Viewer {
protected BorntoDevChannel channel;
// สร้าง abstract method เพื่อให้ผู้ชมทุกคนที่สืบทอดจากคลาสนี้
// สามารถรับการแจ้งเตือนได้ในรูปแบบเดียวกัน
public abstract void update(String videoName);
}
Jane.java
public class Jane extends Viewer{
public Jane(BorntoDevChannel b2d){
this.channel = b2d;
this.channel.subscribe(this);
}
@Override
public void update(String videoName) {
System.out.println( "เจนค่ะ เจนค่ะ หนูชื่อเจน มากับนุ่นและก็มากับโบว์ และก็จะไปดู '" + videoName + "' ค่ะ");
}
}
Noon.java
public class Noon extends Viewer{
public Noon(BorntoDevChannel b2d){
this.channel = b2d;
this.channel.subscribe(this);
}
@Override
public void update(String videoName) {
System.out.println( "นุ่นค่ะ นุ่นค่ะ หนูชื่อนุ่น มากับเจนและก็มากับโบว์ และก็จะไปดู '" + videoName + "' นะค่ะ");
}
}
Bow.java
public class Bow extends Viewer{
public Bow(BorntoDevChannel b2d){
this.channel = b2d;
this.channel.subscribe(this);
}
@Override
public void update(String videoName) {
System.out.println( "โบว์ค่ะ โบว์ค่ะ หนูชื่อโบว์ มากับนุ่นและก็มากับเจน และก็จะไปดู '" + videoName + "' ด้วยค่ะ");
}
}
Main.java
class Main {
public static void main(String[] args) {
BorntoDevChannel b2d = new BorntoDevChannel();
// สร้างผู้ชมทั้งสามคนพร้อมส่งช่อง BorntoDev ไปให้ Subscribe
new Jane(b2d);
new Noon(b2d);
new Bow(b2d);
// แจ้งเตือนไปยังผู้ชมทุกคนว่ามีวีดีโอใหม่
b2d.notifyAllSubscriber("เริ่มเขียนโปรแกรมด้วย Java ฉบับไว ๆ เข้าใจที่สุดในโลก");
}
}
Output:
เจนค่ะ เจนค่ะ หนูชื่อเจน มากับนุ่นและก็มากับโบว์ และก็จะไปดู 'เริ่มเขียนโปรแกรมด้วย Java ฉบับไว ๆ เข้าใจที่สุดในโลก' ค่ะ
นุ่นค่ะ นุ่นค่ะ หนูชื่อนุ่น มากับเจนและก็มากับโบว์ และก็จะไปดู 'เริ่มเขียนโปรแกรมด้วย Java ฉบับไว ๆ เข้าใจที่สุดในโลก' นะค่ะ
โบว์ค่ะ โบว์ค่ะ หนูชื่อโบว์ มากับนุ่นและก็มากับเจน และก็จะไปดู 'เริ่มเขียนโปรแกรมด้วย Java ฉบับไว ๆ เข้าใจที่สุดในโลก' ด้วยค่ะ
7. Template Method
มาถึงตัวสุดท้ายที่ยกมาในวันนี้กันกับ Template Method ที่อยู่ในกลุ่ม Behavioral patterns อ่านแล้วก็น่าจะคุ้นๆ เพราะมันเป็นการใช้งานจากแบบเวลาที่เราใช้ template class ในภาษาพวก Java หรือ C นั่นเอง เป้าหมายก็เพื่อลดโค้ดที่มีการทำงานซ้ำซ้อนกันในแต่ละคลาส อย่างเช่นถ้าเรามี class สำหรับทำบะหมี่กึ่งสำเร็จรูปกับคลาสสำหรับชงชา
ทั้งสองคลาสจะต้องมีฟังก์ชันในการต้มน้ำกับเติมน้ำลงในภาชนะเหมือนๆกัน แทนที่เราจะเขียนไว้ซ้ำกันในสองคลาสก็สร้างไว้เป็น template class ที่มีฟังก์ชันการต้มน้ำและเติมน้ำเอาไว้ และให้ทั้งสองคลาสนำเอาไปใช้งานร่วมกันได้
ตัวอย่างเราจะสร้าง abstract class ชื่อ CookProcess เป็น templete ของการชงชาและทำบะหมี่โดยมี method คือ boilWater, pourWater, addIngredient และ waitAMinute ที่เรียกใช้โดย method cook โดยการต้มน้ำและการรินน้ำถูกกำหนดการกระทำไว้แล้ว
CookProcess.java
public abstract class CookProcess {
public final void cook() {
boilWater();
addIngredient();
pourWater();
waitAMinute();
}
public abstract void addIngredient();
public abstract void waitAMinute();
private final void boilWater() {
System.out.println("I'm water and I'm boiled.");
}
private final void pourWater() {
System.out.println("Pour water in container");
}
}
ต่อไปสร้าง class Tea ที่ extend CookProcess ขึ้นมาและกำหนดการกระทำของ method addIngredient และ waitAMinute
Tea.java
public class Tea extends CookProcess {
@Override
public void addIngredient() {
System.out.println("add tea bag");
}
@Override
public void waitAMinute() {
System.out.println("Wait 1 minute");
}
}
ทำแบบเดียวกันกับ class Noodle
Noodle.java
public class Noodle extends CookProcess {
@Override
public void addIngredient() {
System.out.println("add noodle and seasoning");
}
@Override
public void waitAMinute() {
System.out.println("Wait 3 minutes");
}
}
การจะชงชาเพียงสร้างobject Tea โดยประกาศเป็นตัวแปร CookProcess ที่ Tea extend มาจากนั้นใช้ method cook() ของ class CookProcess ก็เป็นอันเรียบร้อย
Main.java
class Main {
public static void main(String[] args) {
System.out.println("I'll cook tea");
CookProcess tea = new Tea();
tea.cook();
System.out.println("Tea is delicious!!!");
System.out.println("");
System.out.println("I'll cook noodle");
CookProcess noodle = new Noodle();
noodle.cook();
System.out.println("This noodle is too spicy :(");
}
}
Output:
I'll cook tea
I'm water and I'm boiled.
add tea bag
Pour water in container
Wait 1 minute
Tea is delicious!!!
I'll cook noodle
I'm water and I'm boiled.
add noodle and seasoning
Pour water in container
Wait 3 minutes
This noodle is too spicy :(
สำหรับตัวอย่างที่กล่าวถึงเป็นเพียงส่วนหนึ่งของ design pattern ที่มีอยู่มากมาย และถ้าใครอยากรู้จักกับ design pattern เพิ่มเติม ก็สามารถเข้าไปศึกษาต่อได้ที่ https://refactoring.guru/design-patterns/what-is-pattern สุดท้ายแล้วก็อย่าลืมว่าแต่ละ pattern นั้นมีข้อดีข้อเสียแตกต่างกันออกไป ดังนั้นก่อนที่จะหยิบตัวไหนไปใช้ ควรศึกษาตัวนั้นอย่างละเอียดว่าเหมาะสมกับงานที่เราจะนำไปใช้หรือไม่ เพราะ design pattern เป็นเพียงแนวทางการเขียนโค้ดเท่านั้น การนำไปใช้แบบผิดๆ อาจจะทำให้เราเขียนโค้ดได้ยากขึ้นอย่างไม่เกิดประโยชน์ก็เป็นได้