Cloud สำหรับ Non-techs

Screen Shot 2560-09-02 at 5.53.39 PM.png

ตั้งแต่ต้นปีที่ผ่านมาเป็นช่วงที่ผมสนใจเรื่อง Cloud เป็นพิเศษแต่ก็ยังไม่มีโอกาสได้เขียนอะไรเกี่ยวกับ Cloud เลย วันนี้ขอแบบชิลล์ๆดีกว่า กับ Cloud สำหรับคนที่ไม่ได้อยู่ในแวดวง Techs

Cloud Computing การประมวลผลแบบก้อนเมฆคืออะไร ?

ก่อนจะไปถึงตรงนั้น เราต้องท้าวความกันก่อนเลยครับ

รู้จักกับการประมวลผล

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

i

การประมวลผล (Computing) นั้นก็คือการนำข้อมูลต่างๆใส่ระบบคอมพิวเตอร์ เพื่อที่จะให้ CPU ทำงานตามคำสั่งของโปรแกรมโดยที่โปรแกรมเมอร์(Programmer) เป็นคนเขียนใส่คำสั่งเหล่านี้
.
.
ยกตัวอย่างเช่น “เครื่องคิดเลข” ที่ถือว่าเป็นคอมพิวเตอร์เหมือนกัน โดยที่มันถูกติดตั้งโปรแกรมคิดเลขมาอยู่ในเครื่อง เมื่อเรากดตัวเลขและคำสั่ง บวก ลบ ก็จะเป็นการส่งข้อมูลเพื่อให้ CPU ไปประมวลผลแล้วก็ส่งผลลัพธ์กลับมาที่หน้าจอ

รูปจาก http://www.bbc.co.uk/staticarchive/e45e9a229d416394b2f1e16dd7f13689b46c20ca.png

ซึ่งโดยทั่วๆไปแล้วพวกเราที่ใช้คอมพิวเตอร์เนี่ยไม่ต้องใช้งานการประมวลผลหนักๆมากนักเราเลยใช้คอมพิวเตอร์กันแบบที่เรียกว่า PC ซึ่งย่อมาจาก Personal Computer หรือ คอมพิวเตอร์ส่วนบุคคล

PC กับ Server

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

Server คือ คอมพิวเตอร์ที่เปิดระบบให้คอมพิวเตอร์เครื่องอื่นๆไม่ว่าจะเป็น PC หรือ Server เครื่องอื่นๆมาเชื่อมต่อได้เพื่อขอดูหรือจัดเก็บข้อมูล

.
.
เช่นรูปด้านล่างนี้จะเป็นการเชื่อมต่อของ Client ซึ่งแปลว่าเครื่องลูกๆ เช่นพวกคอมพิวเตอร์ PC ทั่วๆไปหลายๆเครื่องกำลังเชื่อมต่อกับเครื่อง Server (เครื่องแม่) เพียงเครื่องเดียว

รูปจาก https://www.kullabs.com/uploads/client-server.png

โดยทั่วๆไปแล้วนั้นเราก็มีทางเลือก 2 ทางใหญ่ๆในการติดตั้ง Server

  1. เอา PC มาทำเป็น Server
  2. เครื่องคอมพิวเตอร์สำหรับการเป็น Server โดยเฉพาะ ซึ่งราคาจะแพงกว่าเครื่อง PC เนื่องจากว่าอุปกรณ์ต่างๆจะมีคุณภาพสูงกว่าและรองรับการใช้งานที่ต้องเปิดเครื่องทิ้งไว้ตลอดเวลาเป็นเวลาหลายๆปีได้ดีกว่า

เมื่อระบบใหญ่ขึ้น

มาถึงตรงนี้ทุกคนก็จะรู้แล้วว่า เครื่อง Server หนึ่งเครื่องนั้นอาจจะสามารถรองรับการเชื่อมต่อของเครื่องลูกๆได้หลายเครื่องพร้อมกัน
ถ้า Server หนึ่งเครื่องรองรับลูกค้าได้ 100 คน แล้วถ้าลูกค้าเราเพิ่มเป็น 3,000 คนล่ะ ระบบของเราก็ต้องใหญ่ขึ้น 30 เท่าเลยทีเดียว ก็ทำให้เราต้องมีการติดตั้ง Server เพิ่มมากขึ้นเช่นกัน ตรงนี้ก็อาจจะต้องทำมีค่าใช้จ่ายในการดูแลมากขึ้นทั้งค่าไฟ ค่าแอร์ ค่าซ่อมอุปกรณ์  รวมถึงพื้นที่ที่จะเอาไว้ติดตั้งด้วย
.
.
จนถึงกระทั่งอาจจะต้องทำห้องสำหรับติดตั้ง Server กันเลย (ซึ่งถ้าเรียกทับศัพท์ก็มีหลายๆคำที่มีความหมายเดียวกัน Server Room, Server Lab, Server Farm หรือ Data Center – ศูนย์ข้อมูล ) ซึ่งจะมีเครื่อง Server หลายสิบ ร้อย พัน เครื่องเลยทีเดียว

รูปจาก https://www.bournehosting.com/images/image2.jpg

เพราะ Server แพงเรามาแชร์กันดีกว่า

ถ้าเปรียบเทียบการตั้ง Server เองเหมือนกับการสร้างตึกเพื่อเอามาไว้สำหรับเก็บของ ซึ่งกว่าจะได้ตึกที่พร้อมใช้งานนั้นก็ต้องใช้ทั้งแรง เวลา และค่าใช้จ่าย รวมถึงยังสามารถเก็บของได้จำกัดด้วย และที่สำคัญถ้าเราไม่ได้ใช้งานก็ต้องเสียค่าบำรุงรักษา เพราะว่าสร้างมาแล้วเราเป็นเจ้าของตึกนั้น
.
.
การสร้าง ห้อง Server หรือ Data Center นั้นก็เหมือนกับการสร้างตึกแถว ซึ่งมีค่าใช้จ่ายสูงมากขึ้นไปอีก แถมยิ่งถ้าสร้างมาใหญ่แต่สิ่งของที่จะเก็บใช้ดันมีไม่เต็มที่ก็จะยิ่งเสียทิ้งไปเปล่าๆ
.
.
ดังนั้นถ้าหากธุรกิจหรือระบบที่ไม่ได้ต้องการควบคุมทุกอย่างเอง ก็อาจจะใช้ทางเลือกคือ การเช่า
เช่นเดียวกันกับระบบคอมพิวเตอร์ เราไม่จำเป็นที่จะต้องสร้าง Server เอง แต่เราสามารถไปเช่าผู้ให้บริการได้เช่นกัน ซึ่งก็เปรียบเหมือนกับการไปเช่าห้องในคอนโด ที่เราจะต้องอยู่ร่วมกับคนอื่นๆในคอนโดทั้งตึกแต่เราก็มีความเป็นส่วนตัวในห้องที่เช่า ถ้าเราไม่อยากใช้ก็แค่ยกเลิกการเช่า โดยไม่ต้องเสียงค่าดูแลระยะยาว ถ้า Server ที่เช่าอยู่นั้นเริ่มเก็บข้อมูลหรือทำงานไม่เพียงพอ เราก็สามารถเช่าเพิ่มได้ทันทีโดยใช้เวลาเพียงไม่ถึง 10 นาที พร้อมทั้งมีระบบรักษาความปลอดภัยให้ในระดับหนึ่งด้วยเช่นกัน และนี่ก็คือจุดเริ่มต้นของ Cloud

ในที่สุดก็เข้าเรื่อง Cloud

ถ้าเปรียบเทียบกันแล้ว Cloud ก็คือคอนโดที่มีตึกอยู่มากมายทั่วโลก เราสามารถที่จะเช่าเพื่อที่จะใช้งานให้สามารถตอบสนองผู้ใช้ระบบของเราได้(เกือบ)ทั่วโลกเช่นกัน
.
ยกตัวอย่างเช่น ถ้าเรามี Server ที่ตั้งอยู่ที่ไทยเพื่อให้บริการข้อมูลของบริษัท แต่ลูกค้าอยู่ที่อเมริกา ซึ่งการที่เค้าจะมาดูข้อมูลได้นั้นจะต้องผ่านระบบเชื่อมต่อหลายขั้นมากกว่าจะมาถึง Server ของเรา จึงทำให้เกิดความล่าช้า แล้วยิ่งถ้าเป็นข้อมูลที่ต้องดูแบบ ณ เวลานั้น (Realtime) ก็ยิ่งเป็นเรื่องใหญ่
.
ดังนั้นการมีระบบอยู่กระจายทั่วโลกนั้นเป็นการเพิ่มประสบการณ์ผู้ใช้ที่ดีกว่า แต่ถ้าเป็นสมัยก่อน เราจะไปตั้ง Server ที่อเมริกาจะทำได้ง่ายๆรึเปล่านะ(?)

global_infrastructure_v25_7-19-17

แผนที่ศูนย์ข้อมูลของ AWS ซึ่งเป็นผู้ให้บริการ Cloud เจ้าใหญ่ที่สุด ณ เวลานี้

ผู้ให้บริการ Cloud นั้นทำหน้าที่คือสร้าง Data Center ทั่วโลก รวมถึงดูแลรักษาและสร้างระบบเครือข่าย ซึ่งมีรายได้มาจากการให้เช่า ซึ่งผู้ให้บริการรายใหญ่ตอนนี้ก็คือ Amazone Web Services (AWS), Microsoft Azure และ Google Cloud
.
ผู้ใช้บริการ Cloud ก็เหมือนเป็นผู้เช่าที่จ่ายเงินเมื่อต้องการใช้ระบบซึ่งถ้าเลิกใช้ก็ไม่จำเป็นต้องจ่ายต่อ ซึ่งปัจจุบันก็จ่ายกันผ่านบัตรเครดิต
.
ความยิ่งใหญ่ของ Cloud
ในวันนี้ก็คือมันมีระบบกระจายอยู่ทั่วโลก ซึ่งถ้าหากเราต้องการเก็บข้อมูลหรือให้บริการข้อมูลลูกค้าทั้งโลกนั้นการสร้าง Data Center หรือ Server เองนั้นยิ่งเป็นไปได้ยาก ถ้าเทียบการใช้บริการ Cloud ของรายใหญ่ ซึ่งก็จะทำให้ธุรกิจของเรานั้นสามารถตอบโจทย์ลูกค้าได้รวดเร็วทั่วทั้งโลกได้ดียิ่งขึ้น เพราะเราสามารถเช่าและเข้าใช้ระบบได้ในทันที! ซึ่งการที่มันมีระบบคอมพิวเตอร์กระจายตัวอยู่ทั่วโลกก็เปรียบได้กับกลุ่มเมฆที่ครอบคลุมทุกพื้นที่ทั่วโลก

…และนิทานเรื่อง Cloud Computing ก็จบลงเท่านี้ 🙂

เรื่องของตัว D (Dependency Inversion) ในหลักการ SOLID สำหรับคนเขียน OOP

Screen Shot 2560-08-27 at 7.02.07 PM

มาถึงตอนสุดท้ายของ SOLID กันแล้วนะครับ ตัวสุดท้ายตัว  D ซึ่งมาจาก Dependency Inversion Principle (DIP) โดยที่ส่วนตัวผมเองนั้นคิดว่าหลักการข้อนี้ค่อนข้างมีประโยชน์และน่าจะเป็นที่รู้จักกันเยอะระดับนึงอยู่แล้วในโลกของการพัฒนา Software ช่วงนี้

DIP นั้นกล่าวว่า

Class ที่มีการเรียกใช้  Class อื่นๆนั้นไม่ควรที่จะมีการอ้างอิงถึงตัว Object ของ Class ที่ถูกเรียกตรงๆ แต่ควรที่จะเรียกผ่าน Interface หรือ Pointer ของ Base Class

เป้าหมายที่สำคัญของหลักการนี้เพื่อทำให้ Code นั้นมีการผูกติดกันให้น้อยที่สุดกับสิ่งที่เป็น Dependency ของ Class เรา

คำว่า Dependency ในกรณีนี้ก็คือ เช่น เรามี Class SmartPhone แล้วมี Data Member เป็น Object ที่สร้างจาก Class BluetoothDevice เช่น  Code ตัวอย่างนี้

class BluetoothDevice {
 public connect(){ /* do low level network tasks */}
 public scan(){}
} 

class SmartPhone {
 private bt : BluetoothDevice; // มีการถือ object BluetoothDevice ใน Class
 public init(){
   this.bt = new BluetoothDevice(); // มีการสร้าง Object ใน Class
 }
 public connectBluetooth(){
   this.bt.connect();
 }
}

จะเห็นว่า Code ข้างต้นนี้ออกแบบได้ถูกหลัก Single Responsibility แล้วเพราะเราทำการแยก Class ของ SmartPhone และ BluetoothDevice ออกจากกันตามหน้าที่ 🙂 ตามรูปด้านล่าง

Screen Shot 2560-08-28 at 11.10.51 PM

Refactor

แต่ถึงอย่างนั้นด้วย DIP เราจะทำให้ Class นี้มี Design ที่ดีขึ้นไปอีกโดยที่เราจะใช้ Interface เป็นตัวกลางในการ Reference เช่นรูปด้านล่างนี้

Screen Shot 2560-08-28 at 11.12.12 PM

ดังนั้น Code ที่ทำการ Refactor โดยการ Inversion แล้วก็จะมีหน้าตาแบบนี้แทนครับ

 interface IBluetooth {
   connect();
   scan();
 }
 class BluetoothDevice implements IBluetooth { // class นี้ทำการ implements interface
   public connect(){ 
     /* do low level network tasks */
     console.log('bluetooth connect');
   }
   public scan(){}
 } 
 class SmartPhone {
   private bt : IBluetooth; // อ้างถึง Bluetooth ผ่าน Interface ไม่ใช่ Object
   public init(bt : IBluetooth){ // มีการรับ Object ที่มี Interface IBluetooth เป็น Parameter
     this.bt = bt;
   }
   public connectBluetooth(){
     this.bt.connect();
   }
 }

จาก Code ด้านบนนี้จะเห็นว่า Class SmartPhone นั้นไม่ได้มีการอ้างอิงไปถึง Object ของ Class BluetoothDevice ตรงๆแล้วแต่จะใช้ Interface แทนครับ ทำให้การที่จะเรียกใช้ Method ที่ถูกต้องของ BluetoothDevice นั้นจะต้องมีการสร้าง Instance ของมันให้ถูก

.

.

Code ของฝั่งที่จะสร้าง Object หรือ Instance จาก Class SmartPhone จึงต้องเปลี่ยนไปเป็นเช่นนี้

let sp = new SmartPhone();
sp.init(new BluetoothDevice()); // ต้องสร้าง Object ของ BluetoothDevice ส่งไปให้
sp.connectBluetooth();

ซึ่งการทำการส่ง Object ที่เป็น Dependency ของอีก Class นึงเข้าไปแบบนี้คือการทำสิ่งที่เรียกว่า Dependency Injection ( DI ) นั่นเอง

.

Testable Code

ข้อดีที่เราจะได้รับจากการทำ DIP ก็คือการทำให้ Code ของเรานั้น Unit Test ได้ง่ายขึ้น

ลองคิดดูครับว่าถ้า Class BluetoothDevice ตัวจริงนั้นต้องไปต่อ Network จริงๆ แล้วเราจะเขียน Unit Test ของ Class SmartPhone ได้ยังไง

.

การทำ DIP นั้นจะทำให้เราสามารถ Inject Mock Dependency ลงไปได้เช่นดังตัวอย่างในรูปนี้

Screen Shot 2560-08-28 at 11.27.36 PM.png

class MockBluetoothDevice implements IBluetooth {
 public connect(){
   console.log('just mock connect');
 }
 public scan(){
   console.log('just mock scan');
 }
}

Code ตอนเขียน UniTest

let sptest = new SmartPhone();
sptest.init(new MockBluetoothDevice()); // ส่งตัวปลอม(Mock) ของ BluetoothDevice ไป
sptest.connectBluetooth(); // ไม่ต้องไปต่อ Network จริงๆ

ก็จะเห็นว่า DIP นั้นช่วยทำให้ Code เราเขียน Test ได้ง่ายขึ้น แต่ทั้งนี้ทั้งนั้นทุกวันนี้เราจะเห็นว่ามี Framework ที่ช่วยทำ Dependency Injection มากมายเลยนะครับ แต่ผมอยากให้ทุกคนทราบกันว่านี่คือเบื้องหลังของมันรวมถึงที่มาที่ไปด้วย

เอาล่ะ มาถึงตรงนี้เป็นอันว่าจบหลักสูตร SOLID กันแล้วนะครับ สำหรับคนที่คิดว่ามันเยอะไปสำหรับ 5 หลักนี้ ผมก็คงแนะนำให้ทบทวน 2 ตัว คือ S กับ D ละกันครับ 🙂 และหวังว่าซีรี่ย์นี่จะเป็นประโยชน์กับหลายๆคนนะครับ

SOLID The Series:

 

 

เรื่องของตัว I (Interface segregation) ในหลักการ SOLID สำหรับคนเขียน OOP

Screen Shot 2560-08-23 at 12.13.53 AM.png

มาถึงตอนที่ 4 ของซีรีย์ SOLID กันแล้วนะครับ กับตัว I ซึ่งตัวนี้ย่อมาจาก Interface segregation Principle (ISP)  ที่จะทำให้ Code ที่เขียนด้วยภาษา OOP นั้นดูดีมากขึ้นไปอีกขั้น 😉

ISP นั้นกล่าวถึงหลักการที่ว่า

Class ที่มา Implement เพื่อใช้งาน Interface นั้นๆไม่ควรที่จะต้องมา Implement Method ต่างๆให้ครบโดยที่อาจจะไม่ได้ใช้

ซึ่งถ้าตีความง่ายๆที่สุดเลยก็คือ เราควรอย่าไปกลัวกับการที่จะต้องสร้าง Interface ใหม่ เพราะโดนส่วนใหญ่ Developer อาจจะชอบเพิ่ม Method ใน Interface เดิม ซึ่งมันดูปลอดภัยมากกว่านั่นเอง

เราลองมาดูตัวอย่างกัน …

เริ่มแรกเลย

ผมมี Interface ชื่อ IPhone (ไม่ใช่ Appple iPhone นะครับ :D) ซึ่งมีด้วยกันทั้งหมด 6 Methods

interface IPhone {
        call();
        ring();
        installApp();
        addContact();
        connectWifi();
        connectMobile();
    }

ที่นีผมก็ลองสร้าง Class ชื่อ SmartPhone ที่จะมา Implement ตัว Interface IPhone ดูครับ

class SmartPhone implements IPhone {
 public call(){ /* do something /* }
 public ring(){/* do something /* }
 public installApp(){/* do something /* }
 public addContact(){/* do something /* }
 public connectWifi(){/* do something /* }
 public connectMobile(){/* do something /* }
}

เนื่องจากว่า SmartPhone นั้นมีความสามารถครบก็ดูโอเคดีครับที่เราจะออกแบบ Interface IPhone แบบนี้

แต่ถ้าผมมีอีก Class ชื่อ FeaturePhone ที่ต้อง Implement IPhone แต่ไม่สามารถ install App หรือ ต่อ wifi ได้ล่ะ ด้วยมันก็จะเป็นแบบนี้

class FeaturePhone implements IPhone {
 public call(){}
 public ring(){}
 public installApp(){
     throw new Error('Feature Phone does not support apps..');
 }
 public addContact(){}
 public connectWifi(){
     throw new Error('Feature Phone does not support using Wifi..');
 }
 public connectMobile(){}
 }

และถ้าเรามีอีก Class ชื่อ BasicTablet ที่มันทำอะไรไม่ค่อยได้นอกว่า install App กับต่อ wifi  มันก็จะเป็นแบบนี้

 BasicTablet implements IPhone {
 public call(){
    throw new Error('Basic Tablet does not support call..');
 }
 public ring(){
    throw new Error('Basic Tablet does not support ring..');
 }
 public installApp(){}
 public addContact(){
    throw new Error('Basic Tablet does not support contacts..');
 }
 public connectWifi(){}
 public connectMobile(){
    throw new Error('Basic Tablet does not support using mobile..');
 }
}

ซึ่งจากตัวอย่างทั้ง 2 Class นั้นก็จะเห็นได้ว่า การออกแบบ Interface แบบนี้ดูแล้วมันค่อนข้างกว้างไป

.

.

เริ่มการ Refactor

แน่นอนว่า Interface IPhone ที่สร้างไว้ตั้งแต่ต้นนั้นมันกว้างไป เทคนิคที่จะใช้ครั้งนี้คือ Extract Interface ซึ่งก็คือทำการแบบ Interface IPhone ออกมาเป็น Interface ย่อยๆตามกลุ่มของ Method ที่เรามี

ซึ่งถ้าเราสังเกตุดูจาก Interface IPhone นั้นจะมี Method อยู่ 3 กลุ่มคือ

  1. โทร พื้นฐาน
  2. การใช้งาน App
  3. การเชื่อมต่อ

ดังนั้นหลักจากที่ทำการ Extract Method แล้วจะได้ผลแบบนี้ครับ

interface IPhone {
  call();
  ring();
  addContact();
}

interface ISmartFeatures {
  installApp();
}

interface IMobileConnect {
  connectMobile();
  connectWifi();
}

ที่นี้ Class SmartPhone ของเราก็ต้องแก้ Code ให้มาเป็นแบบนี้แทน

class SmartPhone implements IPhone, ISmartFeatures, IMobileConnect {

  public call(){}
  public ring(){}
  public installApp(){}
  public addContact(){}
  public connectWifi(){}
  public connectMobile(){}

}

ส่วน Class FeaturePhone ก็จะเป็นแบบนี้ ซึ่งก็ยังต้องมีการ handle Method ที่ไม่ Support อยู่แต่ว่าก็จะน้อยลงกว่าตอนแรก

class FeaturePhone implements IPhone, IMobileConnect {

  public call(){}
  public ring(){}
  public addContact(){}
  public connectMobile(){}
  public connectWifi() {
    throw new Error('Feature Phone does not support using Wifi..');
  }

}

ส่วน Class BasicTablet นั้นก็จะคล้ายๆกับ FeaturePhone ก็คือ

class BasicTablet implements ISmartFeatures, IMobileConnect {

  public installApp(){}
  public connectWifi(){}
  public addContact(){}
  public connectMobile(){}
  public connectMobile(){ 
    throw new Error('Basic Tablet does not support using mobile..'); 
  }

}

สรุป

เพื่อนๆก็จะเห็นว่าการทำ Interface Segregation นั้นจะทำให้เราสามารถออกแบบ Interface ได้ดียิ่งขึ้นและไม่เป็นการทำให้ Client ของ Interface นั้นต้องมาเขียน  Code  เพิ่มโดยที่ไม่ได้ Handle อะไรเลย รวมถึงทำให้ Class และ Interface ของเราดูแลได้ง่ายขึ้นและเจาะจงการการทำงานแต่ละเรื่องมากขึ้น

SOLID The Series:

เรื่องของตัว L (Liskov Substitution) ในหลักการ SOLID สำหรับคนเขียน OOP

Screen Shot 2560-08-23 at 12.09.41 AM.png

มาถึงหลักการข้อ 3 ของ SOLID กันแล้วนะครับ สำหรับตัว L ซึ่งย่อมากจาก Liskov Substitution Principle (LSP)  ซึ่งกล่าวด้วยความ Abstact อีกแล้วว่า

Function ที่ใช้ reference หรือ Object ของ คลาสแม่ (Base Class) นั้นควรจะต้องสามารถใช้ Object ของคลาสลูกมันได้โดยไม่ต้องรู้รายละเอียดหรือข้อจำกัดของมัน

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

…เอาล่ะ เราเป็น Developer เราก็มาลองดู Code กันดีกว่าครับ

*เช่นเดิม Code เขียนด้วย TypeScript

ก่อนอื่นเลยผมมี Class Phone ซึ่งมี  2 Methods คือ call() กับ installApp()

    class Phone {
        public call(){
            console.log('Make call...');
        }
        public installApp(){}
    }

ผมก็ได้ทำการเพิ่ม Class มาอีก 2 Classes คือ SmartPhone กับ FeaturePhone โดยการสืบทอดมาจาก Phone

แต่บังเอิญว่า FeaturePhone ดัน install App ไม่ได้เลยต้องทำการ Throw Exception ออกไป

    class SmartPhone extends Phone {
        public installApp(){
            console.log('SmartPhone install the app...');
        }
    }

    class FeaturePhone extends Phone {
        public installApp(){
            throw new Error('Feature Phone does not support install app...');
        }
    }

ถ้าผมที่ Code ฝั่งที่เรียกใช้ Phone จะเป็นแบบนี้ครับ

let phone = new Phone();
phone.installApp(); // ก็  Work ดี

let smPhone = new SmartPhone();
smPhone.installApp(); // ก็ Work ดี

let ftPhone = new FeaturePhone();
ftPhone.installApp(); // พัง เกิด Exception

ซึ่งจะเห็นได้ว่า ผมต้องมาแก้เป็นแบบนี้ เป็นกรณีพิเศษ เช่นต้องใช้ try/catch

try {
    ftPhone.installApp();
}

และนี่แหละคือการผิดหลักการออกแบบที่ดีของหลักการ LSP เพราะเราไม่สามารถเรียกใช้ Class ลูก (FeaturePhone) ได้แบบที่เหมือนเรียก Class แม่ (Phone) ซึ่งการที่เราจะรู้ได้ว่ามัน Throw Exception นั้นก็ต้องไปดู  Code ที่ถูก Implement ใน Class นั้น ซึ่งถ้าเป็น  Code ที่อยู่ใน Libary หรือพัฒนาโดยคนอื่นล่ะ มันก็คงจะดูไม่ได้ง่ายๆใช่มั้ยครับ

การจะแก้ไขปัญหานี้ที่ถูกต้องตามหลัก OO คือต้อง Refactor โดยใช้เทคนิคชื่อ Push Down Method ครับ (ก็คือย้าย Method ที่ไม่จำเป็นใน Class แม่ลงไปที่ Class ลูก)
ฉะนั้นผมจะได้ Class ใหม่แบบนี้แทน

    class Phone {
        public call(){
            console.log('Phone install the app...');
        }
    }
    
    class SmartPhone extends Phone {
        public installApp(){
            console.log('SmartPhone install the app...');
        }
    }

    class FeaturePhone extends Phone {
        // additional FeaturePhone feature e.g. multi sim cards
    }

สิ่งที่เปลี่ยนไปคือ

  1. Method installApp() ถูกย้ายลงมาที่ SmartPhone
  2. FeaturePhone จะไม่มี Method installApp() ให้เรียกแล้ว
  3. ทำให้แก้ปัญหาที่ Code ที่ใช้เรียก ftPhone.installApp() แล้วต้องใช้ try/catch ไปได้! 🙂

SOLID The Series:

 

เรื่องของตัว O (Open-Closed) ในหลักการ SOLID สำหรับคนเขียน OOP

Screen Shot 2560-08-23 at 12.08.07 AM.png

หลังจากที่พูดถึง S (หรือ Single Responsibility) ไปแล้วหลักการต่อมาก็คือ O ซึ่งย่อมาจาก  Open-Closed Principle ซึ่งเป็นหลักการที่ 2 ของ SOLID ที่มีใจความสำคัญแบบ Abstractๆ ว่า

เราควรออกแบบระบบให้ง่ายต่อการเพิ่มเติมอะไรใหม่ๆ โดยที่ไม่ต้องแก้ไขระบบเก่า (ถ้าไม่จำเป็น)

Open-Closed Principle (OCP) จะกล่าวถึงการออกแบบ Class หรือทั้งระบบให้ยืดหยุ่นมากขึ้น โดยที่เราใช้ความสามารถของ OOP มาช่วย โดยยกตัวอย่างเช่น ถ้าเราต้องเพิ่ม Feature ใหม่ให้ Class เรา จุดที่ควรจะแก้ไข Code นั้นควรจะเป็นจุดที่เหมาะสมและไม่ควรกระทบกับสิ่งที่ไม่ควรจะแก้ไข

อธิบายเพิ่มก็ยัง งง อีก…งั้นเรามาดูตัวอย่างกันดีกว่าครับ

เช่นเดิม ผมมี Class SmartPhone ซึ่งมี Constructor ที่มีการ Set ค่าต่างๆให้กับ Object ที่กำลังถูกสร้างตามชื่อ Model เช่น S10 หรือ 8s

*Code ตัวอย่างเป็น TypeScript

    
class SmartPhone {
    private phoneModel : string;
    private phoneFullName : string;
    private screenWidth : number;
    private screenHeigth : number;
    constructor(model : string){
       if(model ==='S10'){
          this.phoneModel ='S10';
          this.screenWidth =5;
          this.screenHeigth =10;
          this.phoneFullName ='Samsung Galaxy S10';
        }
        else if(model ==='8s'){
          this.phoneModel ='8s';
          this.screenWidth =4;
          this.screenHeigth =3;
          this.phoneFullName ='Apple iPhone 8s';
        }
    }
}

จาก Code นี้ถ้าผมทำการเพิ่ม Model ใหม่สิ่งที่ผมต้องทำคือผมต้องผมแก้ Contructor ของ Class SmartPhone โดยการเพิ่ม else if  แล้วทำการเพิ่ม Code ของ Model Nexus 9 เข้าไป

constructor(model : string){
       if(model ==='S10'){
          this.phoneModel ='S10';
          this.screenWidth =5;
          this.screenHeigth =10;
          this.phoneFullName ='Samsung Galaxy S10';
        }
        else if(model ==='8s'){
          this.phoneModel ='8s';
          this.screenWidth =4;
          this.screenHeigth =3;
          this.phoneFullName ='Apple iPhone 8s';
        }
        else if(model ==='Nexus 9'){
          this.phoneModel ='Nx9';
          this.screenWidth =5;
          this.screenHeigth =5;
          this.phoneFullName ='Google Nexus 9';
        }
}
       
การทำแบบนี้ไม่ผิดครับ แต่เนื่องจากเราเขียนภาษา OO และต้องการทำให้ Design ของเราเป็นไปตามหลักการ OCP ดังนั้นเราต้องทำการ Refactor Code กันครับ
ลองคิดดูก่อนนะครับว่าจะ Refactor กันยังไงดี…
.
.
.
เอาล่ะมาเริ่มกัน
เทคนิคที่เราจะใช้ในการเพิ่ม Code Quality ครั้งนี้คือการใช้ Inherithance หรือการสืบทอดนั่นเอง ดังนั้นเรามาสร้างเป็น 3 Classes โดยที่ SmartPhone จะเป็น Base Class
   class SmartPhone { // Base Class
        protected phoneModel : string;
        protected phoneFullName : string;
        protected screenWidth : number;
        protected screenHeigth : number;
        constructor(){
            this.init();
        }
        protected init(){};
    }

    class S10Phone extends SmartPhone{// Inherited จาก Base Class
        protected init(){
            this.phoneModel = 'S10';
            this.screenWidth = 5;
            this.screenHeigth = 10;
            this.phoneFullName = 'Samsung Galasy S10';
        } 
    }

    class i8sPhone extends SmartPhone{// Inherited จาก Base Class
        protected init(){
            this.phoneModel = '8s';
            this.screenWidth = 4;
            this.screenHeigth = 3;
            this.phoneFullName = 'Apple iPhone 8s';
        } 
    }

    class Nx9Phone extends SmartPhone{ // Inherited จาก Base Class
        protected init(){
              this.phoneModel ='Nx9';
              this.screenWidth =5;
              this.screenHeigth =5;
              this.phoneFullName ='Google Nexus 9';
        }
    }

ซึ่งการทำแบบนี้จะเห็นว่าถ้าเราต้องการเพิ่ม model ใหม่ๆเข้าไปเราจะไม่ต้องมาแก้ไข้ base class เลยครับแค่ทำการ Inheritance กับสร้าง Class ใหม่แทน ซึ่งนี่ก็คือหลักการของ OCP ครับ 🙂

SOLID The Series:

เรื่องของตัว S (Single Responsibility) ในหลักการ SOLID

Screen Shot 2560-08-23 at 12.04.58 AM.png

หลังจากที่ผมได้เกริ่นเกี่ยวกับหลักการ SOLID ไปแล้วนั้น วันนี้เราจะมาลองดูกันกับตัวแรกของเรา ซึ่งก็คือตัว S ซึ่งย่อมากจาก Single Responsibility หรือแปลว่า หลักการทำหน้าที่เดียวพอ ซึ่งผมจะขอใช้ตัวอย่างด้วย TypeScript ครับ

Single Responsibility Principle (SRP) นั้นกล่าวถึงการออกแบบ Class ในภาษา Object Oriented เพื่อที่จะทำให้ Class นั้นดูแลและจัดการได้สะดวกมากขึ้น

เริ่มต้น ผมมี Class ตัวอย่างที่ชื่อว่า SmartPhone

class SmartPhone {
   // properties
   private apps : Array<string>;
   private contacts : Array<string>;
   private phoneNumber : string;
   private phoneModel : string;
   private screenWidth : number;
   private screenHeigth : number;

   // methods
   public call( telNum : number){
   }
   public ring(){
   }
   public installApp(appName : string){
      this.apps.push(appName);
   }
   public launchApp(appName : string){
      let app = this.apps.filter(element => element === appName);
   }
   public addContact(contact : string){
      this.contacts.push(contact);
   }
   public getScreenPixel() : number {
      return this.screenWidth * this.screenHeigth;
   }
   public getModel() : string {
      return this.phoneModel;
   }
}

ซึ่งจาก Code ตัวอย่างนี้จะเห็นได้ว่า Class SmartPhone นี้ก็ออกแบบมาตามหลัก OO ระดับหนึ่ง ซึ่งถ้าเรามาดูรายละเอียดดีๆนั้นจะเห็นว่ามันมี 3 ส่วนด้วยกัน

  1. การจัดการ Apps
  2. การจัดการ Contact
  3. ข้อมูลของตัว SmartPhone เอง

แต่จะเห็นว่า Code นี้ยังไม่ได้ใช้หลักการของ SRP เลย ดังนั้นสิ่งที่เราต้องทำคือ เราจะมาลองเปลี่ยน Code ให้สอดคล้องกับหลักการนี้ ขั้นตอนนี้เราเรียกว่า การ Refactor 

สิ่งที่ต้องทำก็คือจะใช้เทคนิคการ  Refactor ที่ชื่อว่า Extract Class ก็คือเป็นการแยก Code ในส่วนของ Properties และ Methods ที่น่าจะรวมกันเป็น Class ใหม่ได้ออกมา

ดังนั้นเราเราจะได้ Class ใหม่มาเพิ่มอีก 3 Class คือ Apps และ Contacts

  1. Class Apps จะเอาการเก็บข้อมูล Array และ Methods การ install กับ launch มาไว้ใน Class นี้
class Apps {
   private apps : Array<string>;
   public install(appName : string){
      this.apps.push(appName);
   }
   public launch(appName : string){
      let app = this.apps.filter(element => element === appName);
   }
}

2. Class Contacts จะเอาไว้จัดการเกี่ยวกับ Contact List

class Contacts {
   private contacts : Array<string>;
   public add(contact : string){
      this.contacts.push(contact);
   }
}

3. Class PhoneInfo เราจะเอามาเก็บข้อมูลทั่วไปเช่น เบอร์โทร ชื่อรุ่น ขนาดหน้าจอ

class PhoneInfo {
   private phoneNumber : string;
   private phoneModel : string;
   private screenWidth : number;
   private screenHeigth : number;
   constructor(model : string){
      this.phoneModel = model;
      this.screenWidth =5;
      this.screenHeigth =10;
   }

   public getModel() : string {
      return this.phoneModel;
   }

   public getScreenPixel() : number {
      return this.screenWidth *this.screenHeigth;
   }
}

ซึ่งเมื่อมาถึงตรงนี้แล้วเราก็ต้องทำการแก้ Class หลักของเราก็คือ Class SmartPhone ให้มาเรียกใช้งานทั้ง 3 Classes นี้

// Smart Phone V2
class SmartPhone {
   private apps : Apps; // เรียกใช้ Class Apps
   private info : PhoneInfo; // เรียกใช้ Class PhoneInfo
   private contacts : Contacts; // เรียกใช้ Class Contacts

   constructor(){
      this.apps =new Apps();
      this.info =new PhoneInfo('S10');
      this.contacts =new Contacts();
   }

   public call( telNum : number){
   }

   public ring(){
   }

   public installApp(appName : string){
       this.apps.install(appName); // เรียกใช้ Method ของ Class Apps
   }

   public launchApp(appName : string){
      this.apps.launch(appName);// เรียกใช้ Method ของ Class Apps
   }

   public addContact(contact : string){
      this.contacts.add(contact);// เรียกใช้ Method ของ Class Contacts
   }

   public getScreenPixel() : number {
      return this.info.getScreenPixel();// เรียกใช้ Method ของ Class PhoneInfo
   }

   public getModel() : string {
       return this.info.getModel();// เรียกใช้ Method ของ Class PhoneInfo
   }
}

หลังจากทำการ Refactor เสร็จก็จะเห็นได้ว่า Class SmartPhone Verion 2 นั้นจะเป็นระเบียบมากขึ้น ไม่ต้องมาเก็บตัวแปรที่เป็น String หรือ Array ที่ไม่เกี่ยวข้องอะไรกับตัว Class นี้เองเลย แต่จะย้ายไปเก็บใน Class ย่อยๆแทนเพื่อที่จะให้ Code นั้นเหมาะสมกับ Class แต่ละ Class จริงๆ

เท่านี้เพื่อนๆก็จบหลักสูตร Single Responsibility Principle กันแล้วครับ หวังว่าจะเป็นประโยชน์กับทุกคน แล้วเจอกันตอนหน้าด้วยหลักการตัว O 🙂

หลักการ SOLID สำหรับคนที่เขียน OOP

SOLID เป็นหลักการ 5 ข้อที่จะช่วยให้ Developer ที่เขียนภาษา Object Oriented เช่นพวก C#, Java, C++) นั้นสามารถเขียน Code ได้ดูเป็นระบบระเบียบมากขึ้น รวมถึงสามารถที่จะดูแลและแก้ไขได้ง่ายและสุดท้ายทำให้พวกเราดูเป็น Profressional กันด้วย 🙂

ถึงแม้ว่าหลักการของ SOLID นั้นมีมาก็หลาย 10 ปีแล้วแต่ผมคิดว่าสำหรับนักพัฒนาที่เขียนภาษา OO ก็เป็นสิ่งที่ยังควรรู้ รวมถึงคนที่เขียนภาษาอื่นๆเช่นพวก Script หรือ Dynamic languages ก็อาจจะเอาไปประยุกต์ใช้ได้เหมือนกัน

เอาล่ะเรามาดูกันว่าหลักการ 5 ข้อนั้นย่ามาจากอะไรบ้าง

S – Single Responsibility Principle

กล่าวถึง Class หนึ่งๆนั้นควรจะมีแค่หน้าที่ ความรับผิดชอบเดียวเท่านั้น

O – Open Closed Principle

กล่าวถึง ระบบของ Software นั้นควรง่ายและเป็นระบบเปิดต่อการต่อยอดโดยที่แก้ไขระบบหลักให้น้อยที่สุด

L – Liskov substitution Principle

กล่าวถึง Object ของ Class ที่ระบบได้เรียกใช้นั้นควรจะสามารถเรียกใช้ Class ลูกๆได้โดยที่ไม่ต้องแก้ไขระบบเลย (ข้อนี้อาจจะเข้าใจยากนิดนึงนะครับ อาจจะต้องลองดูตัวอย่าง)

I – Interface segregation Principle

กล่าวถึงการแบ่งย่อย Interface ให้เป็นสัดสวนตามหน้าที่ของมัน ดีกว่ามี Interface เดียวที่มีหลายๆ Methods

D – Dependency Inversion Principle

กล่างถึงการที่ Class ต่างๆติดต่อกับอีก Class อื่นๆนั้น ควรจะติดต่อกันผ่าน Interface มากกว่า Object ของ Class นั้นๆตรงๆ

 

 

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

ref : https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)