หนึ่งในหลักการที่สำคัญที่สุดของการเขียนโปรแกรมเชิงวัตถุ - การกำหนดอินเทอร์เฟซภายในจากอินเทอร์เฟซภายนอก
นั่นคือแนวทางปฏิบัติที่ "ต้องทำ" ในการพัฒนาสิ่งที่ซับซ้อนกว่าแอป "hello world"
เพื่อทำความเข้าใจสิ่งนี้ เรามาแยกตัวออกจากการพัฒนาและหันสายตาของเราไปสู่โลกแห่งความเป็นจริง
โดยปกติแล้ว อุปกรณ์ที่เราใช้จะค่อนข้างซับซ้อน แต่การแยกส่วนอินเทอร์เฟซภายในจากภายนอกทำให้สามารถใช้งานได้โดยไม่มีปัญหา
เช่น เครื่องชงกาแฟ. เรียบง่ายจากภายนอก: ปุ่ม จอภาพ รูเล็กๆ...และผลลัพธ์ที่ได้คือกาแฟชั้นเยี่ยม! -
แต่ข้างใน… (ภาพจากคู่มือซ่อม)
รายละเอียดมากมาย แต่เราสามารถใช้มันได้โดยไม่ต้องรู้อะไรเลย
เครื่องชงกาแฟค่อนข้างเชื่อถือได้ใช่ไหม? เราสามารถใช้งานได้หลายปี และเฉพาะในกรณีที่มีสิ่งผิดปกติเกิดขึ้นเท่านั้น ให้นำไปซ่อมแซม
เคล็ดลับของความน่าเชื่อถือและความเรียบง่ายของเครื่องชงกาแฟ – รายละเอียดทั้งหมดได้รับการปรับแต่งอย่างดีและ ซ่อนอยู่ ภายใน
หากเราถอดฝาครอบป้องกันออกจากเครื่องชงกาแฟ การใช้งานจะซับซ้อนกว่ามาก (ต้องกดตรงไหน) และเป็นอันตราย (อาจทำให้เกิดไฟฟ้าช็อตได้)
ดังที่เราจะได้เห็นในการเขียนโปรแกรมวัตถุก็เหมือนกับเครื่องชงกาแฟ
แต่เพื่อที่จะซ่อนรายละเอียดภายใน เราจะไม่ใช้ฝาครอบป้องกัน แต่จะใช้ไวยากรณ์พิเศษของภาษาและแบบแผน
ในการเขียนโปรแกรมเชิงวัตถุ คุณสมบัติและวิธีการแบ่งออกเป็นสองกลุ่ม:
อินเทอร์เฟซภายใน – วิธีการและคุณสมบัติ เข้าถึงได้จากวิธีอื่นของคลาส แต่ไม่ใช่จากภายนอก
อินเทอร์เฟซภายนอก - วิธีการและคุณสมบัติ สามารถเข้าถึงได้จากภายนอกชั้นเรียน
หากเรายังคงเปรียบเทียบกับเครื่องชงกาแฟ สิ่งที่ซ่อนอยู่ภายใน: ท่อหม้อน้ำ อุปกรณ์ทำความร้อน และอื่นๆ ก็คืออินเทอร์เฟซภายใน
อินเทอร์เฟซภายในใช้สำหรับวัตถุในการทำงาน รายละเอียดใช้ซึ่งกันและกัน ตัวอย่างเช่น มีการต่อท่อหม้อไอน้ำเข้ากับองค์ประกอบความร้อน
แต่จากภายนอกเครื่องชงกาแฟจะถูกปิดด้วยฝาครอบป้องกัน เพื่อไม่ให้ใครเข้าถึงได้ รายละเอียดถูกซ่อนไว้และไม่สามารถเข้าถึงได้ เราสามารถใช้คุณสมบัติต่างๆ ผ่านอินเทอร์เฟซภายนอก
ดังนั้นสิ่งที่เราต้องใช้วัตถุก็คือการรู้อินเทอร์เฟซภายนอกของมัน เราอาจไม่รู้เลยว่ามันทำงานอย่างไรภายใน และนั่นก็เยี่ยมมาก
นั่นเป็นการแนะนำทั่วไป
ใน JavaScript ฟิลด์อ็อบเจ็กต์มีสองประเภท (คุณสมบัติและวิธีการ):
สาธารณะ: เข้าถึงได้จากทุกที่ ประกอบด้วยอินเทอร์เฟซภายนอก จนถึงขณะนี้เราใช้เพียงทรัพย์สินและวิธีการสาธารณะเท่านั้น
ส่วนตัว: เข้าถึงได้จากภายในชั้นเรียนเท่านั้น สิ่งเหล่านี้มีไว้สำหรับอินเทอร์เฟซภายใน
ในภาษาอื่น ๆ หลายภาษายังมีช่อง "ป้องกัน" อีกด้วย: เข้าถึงได้จากภายในชั้นเรียนเท่านั้นและช่องที่ขยาย (เช่น ส่วนตัว แต่รวมถึงการเข้าถึงจากคลาสที่สืบทอด) ยังมีประโยชน์สำหรับอินเทอร์เฟซภายในอีกด้วย ในแง่หนึ่งพวกมันแพร่หลายมากกว่าคลาสส่วนตัว เพราะโดยปกติแล้วเราต้องการให้คลาสที่สืบทอดเพื่อให้เข้าถึงคลาสเหล่านั้นได้
ฟิลด์ที่มีการป้องกันไม่ได้ถูกนำมาใช้ใน JavaScript ในระดับภาษา แต่ในทางปฏิบัติ ฟิลด์ที่มีการป้องกันนั้นสะดวกมาก ดังนั้นจึงถูกจำลอง
ตอนนี้เราจะสร้างเครื่องชงกาแฟใน JavaScript พร้อมคุณสมบัติประเภทนี้ทั้งหมด เครื่องชงกาแฟมีรายละเอียดมากมาย เราจะไม่สร้างโมเดลให้เรียบง่าย (แม้ว่าเราจะทำได้ก็ตาม)
มาสร้างคลาสเครื่องชงกาแฟแบบง่ายๆ กันก่อน:
คลาส CoffeeMachine { ปริมาณน้ำ = 0; //ปริมาณน้ำภายใน ตัวสร้าง (กำลัง) { this.power = พลังงาน; alert( `สร้างเครื่องชงกาแฟแล้ว กำลังไฟ: ${power}` ); - - // สร้างเครื่องชงกาแฟ ให้ coffeeMachine = CoffeeMachine ใหม่ (100); //เติมน้ำ กาแฟMachine.waterAmount = 200;
ขณะนี้คุณสมบัติ waterAmount
และ power
เป็นสาธารณะแล้ว เราสามารถรับ/ตั้งค่าจากภายนอกเป็นค่าใดๆ ก็ได้
มาเปลี่ยนคุณสมบัติ waterAmount
เพื่อป้องกันเพื่อให้ควบคุมได้มากขึ้น ตัวอย่างเช่น เราไม่ต้องการให้ใครตั้งค่าต่ำกว่าศูนย์
คุณสมบัติที่ได้รับการป้องกันมักจะนำหน้าด้วยขีดล่าง _
นั่นไม่ได้บังคับใช้ในระดับภาษา แต่มีแบบแผนที่รู้จักกันดีระหว่างโปรแกรมเมอร์ว่าไม่ควรเข้าถึงคุณสมบัติและวิธีการดังกล่าวจากภายนอก
ดังนั้นทรัพย์สินของเราจะถูกเรียกว่า _waterAmount
:
คลาส CoffeeMachine { _ปริมาณน้ำ = 0; ตั้งค่า waterAmount (มูลค่า) { ถ้า (ค่า < 0) { ค่า = 0; - this._waterAmount = ค่า; - รับปริมาณน้ำ () { คืนสิ่งนี้._waterAmount; - ตัวสร้าง (กำลัง) { this._power = กำลัง; - - // สร้างเครื่องชงกาแฟ ให้ coffeeMachine = CoffeeMachine ใหม่ (100); //เติมน้ำ กาแฟMachine.waterAmount = -10; // _waterAmount จะกลายเป็น 0 ไม่ใช่ -10
ขณะนี้การเข้าถึงอยู่ภายใต้การควบคุม ดังนั้นการตั้งค่าปริมาณน้ำให้ต่ำกว่าศูนย์จึงเป็นไปไม่ได้
สำหรับคุณสมบัติ power
ขอให้เป็นแบบอ่านอย่างเดียว บางครั้งอาจเกิดขึ้นว่าต้องตั้งค่าคุณสมบัติในเวลาที่สร้างเท่านั้น จากนั้นจึงไม่ต้องแก้ไข
เครื่องชงกาแฟก็เป็นเช่นนั้นเอง พลังไม่เคยเปลี่ยนแปลง
ในการทำเช่นนั้น เราเพียงแต่ต้องสร้าง getter เท่านั้น แต่ไม่ต้องสร้าง setter:
คลาส CoffeeMachine { - ตัวสร้าง (กำลัง) { this._power = กำลัง; - รับพลังงาน () { คืนสิ่งนี้._power; - - // สร้างเครื่องชงกาแฟ ให้ coffeeMachine = CoffeeMachine ใหม่ (100); alert(`กำลังคือ: ${coffeeMachine.power}W`); // กำลังไฟฟ้า: 100W กาแฟMachine.power = 25; // ข้อผิดพลาด (ไม่มีตัวตั้งค่า)
ฟังก์ชันตัวรับ/ตัวตั้งค่า
ที่นี่เราใช้ไวยากรณ์ getter/setter
แต่ส่วนใหญ่แล้ว get.../set...
ฟังก์ชั่นที่ต้องการจะเป็นดังนี้:
คลาส CoffeeMachine { _ปริมาณน้ำ = 0; setWaterAmount (มูลค่า) { ถ้า (ค่า < 0) ค่า = 0; this._waterAmount = ค่า; - getWaterAmount() { คืนสิ่งนี้._waterAmount; - - ใหม่ CoffeeMachine().setWaterAmount(100);
ดูยาวกว่าเล็กน้อย แต่ฟังก์ชันต่างๆ มีความยืดหยุ่นมากกว่า พวกเขาสามารถยอมรับข้อโต้แย้งได้หลายข้อ (แม้ว่าเราจะไม่ต้องการมันในตอนนี้ก็ตาม)
ในทางกลับกัน ไวยากรณ์ get/set จะสั้นกว่า ดังนั้นท้ายที่สุดแล้วไม่มีกฎเกณฑ์ที่เข้มงวด ขึ้นอยู่กับคุณที่จะตัดสินใจ
ฟิลด์ที่มีการป้องกันได้รับการสืบทอดมา
ถ้าเราสืบทอด class MegaMachine extends CoffeeMachine
ก็ไม่มีอะไรขัดขวางไม่ให้เราเข้าถึง this._waterAmount
หรือ this._power
จากเมธอดของคลาสใหม่ได้
ดังนั้นเขตข้อมูลที่ได้รับการคุ้มครองจึงสามารถสืบทอดได้ตามธรรมชาติ ไม่เหมือนส่วนตัวที่เราจะเห็นด้านล่าง
นอกจากนี้ล่าสุด
นี่เป็นส่วนเพิ่มเติมล่าสุดของภาษา ยังไม่รองรับในเอ็นจิ้น JavaScript หรือรองรับบางส่วน จำเป็นต้องมีการเติมโพลี
มีข้อเสนอ JavaScript ที่เสร็จสมบูรณ์แล้ว ซึ่งเกือบจะเป็นมาตรฐานแล้ว ซึ่งให้การสนับสนุนระดับภาษาสำหรับคุณสมบัติและวิธีการส่วนตัว
Privates ควรขึ้นต้นด้วย #
สามารถเข้าถึงได้จากภายในชั้นเรียนเท่านั้น
ตัวอย่างเช่น นี่คือคุณสมบัติ #waterLimit
ส่วนตัว และวิธีการตรวจสอบน้ำส่วนตัว #fixWaterAmount
:
คลาส CoffeeMachine { #ขีดจำกัดน้ำ = 200; #fixWaterAmount(มูลค่า) { ถ้า (ค่า <0) ส่งกลับ 0; ถ้า (value > this.#waterLimit) ส่งคืน this.#waterLimit; - setWaterAmount (มูลค่า) { this.#waterLimit = this.#fixWaterAmount(value); - - ให้ coffeeMachine = CoffeeMachine ใหม่ (); // ไม่สามารถเข้าถึงส่วนตัวจากนอกชั้นเรียนได้ coffeeMachine.#fixWaterAmount(123); // ข้อผิดพลาด เครื่องชงกาแฟ#waterLimit = 1000; // ข้อผิดพลาด
ในระดับภาษา #
เป็นเครื่องหมายพิเศษว่าฟิลด์นี้เป็นแบบส่วนตัว เราไม่สามารถเข้าถึงได้จากภายนอกหรือจากคลาสที่สืบทอด
ฟิลด์ส่วนตัวไม่ขัดแย้งกับฟิลด์สาธารณะ เราสามารถมีทั้งฟิลด์ #waterAmount
และ public waterAmount
ส่วนตัวได้ในเวลาเดียวกัน
ตัวอย่างเช่น เรามาทำให้ waterAmount
เป็นตัวเข้าถึงสำหรับ #waterAmount
:
คลาส CoffeeMachine { #ปริมาณน้ำ = 0; รับปริมาณน้ำ () { คืนสิ่งนี้#waterAmount; - ตั้งค่า waterAmount (มูลค่า) { ถ้า (ค่า < 0) ค่า = 0; this.#waterAmount = มูลค่า; - - ให้เครื่อง = CoffeeMachine ใหม่ (); machine.waterAmount = 100; การแจ้งเตือน(machine.#waterAmount); // ข้อผิดพลาด
ฟิลด์ส่วนตัวต่างจากที่ได้รับการคุ้มครองโดยตัวภาษาเอง นั่นเป็นสิ่งที่ดี
แต่ถ้าเราสืบทอดมาจาก CoffeeMachine
เราก็จะไม่สามารถเข้าถึง #waterAmount
ได้โดยตรง เราจะต้องพึ่งพา waterAmount
getter/setter:
คลาส MegaCoffeeMachine ขยาย CoffeeMachine { วิธี() { การแจ้งเตือน ( this.#waterAmount ); // ข้อผิดพลาด: สามารถเข้าถึงได้จาก CoffeeMachine เท่านั้น - -
ในหลายสถานการณ์ ข้อจำกัดดังกล่าวรุนแรงเกินไป หากเราขยาย CoffeeMachine
เราอาจมีเหตุผลที่ถูกต้องในการเข้าถึงข้อมูลภายใน นั่นเป็นสาเหตุที่มีการใช้ฟิลด์ที่มีการป้องกันบ่อยขึ้น แม้ว่าไวยากรณ์ของภาษาจะไม่รองรับก็ตาม
ฟิลด์ส่วนตัวไม่สามารถใช้ได้เนื่องจาก [ชื่อ]
สนามส่วนตัวเป็นพิเศษ
ดังที่เราทราบ โดยปกติแล้วเราสามารถเข้าถึงฟิลด์ต่างๆ ได้โดยใช้ this[name]
:
ผู้ใช้คลาส { - พูดสวัสดี() { ให้ fieldName = "ชื่อ"; alert(`สวัสดี ${this[fieldName]}`); - -
ด้วยฟิลด์ส่วนตัวที่เป็นไปไม่ได้: this['#name']
ใช้งานไม่ได้ นั่นเป็นข้อจำกัดทางไวยากรณ์เพื่อรับรองความเป็นส่วนตัว
ในแง่ของ OOP การกำหนดขอบเขตอินเทอร์เฟซภายในจากอินเทอร์เฟซภายนอกเรียกว่าการห่อหุ้ม
มันให้ประโยชน์ดังต่อไปนี้:
ป้องกันผู้ใช้ไม่ให้ยิงตัวเองเข้าที่เท้า
ลองนึกภาพว่ามีทีมนักพัฒนาใช้เครื่องชงกาแฟ ผลิตโดยบริษัท “Best CoffeeMachine” และใช้งานได้ดี แต่ฝาครอบป้องกันถูกถอดออก ดังนั้นอินเทอร์เฟซภายในจึงถูกเปิดเผย
นักพัฒนาซอฟต์แวร์ทุกคนมีอารยธรรม พวกเขาใช้เครื่องชงกาแฟตามที่ตั้งใจไว้ แต่หนึ่งในนั้นคือจอห์น ตัดสินใจว่าเขาเป็นคนที่ฉลาดที่สุด และได้ปรับแต่งภายในเครื่องชงกาแฟ เครื่องชงกาแฟจึงล้มเหลวในสองวันต่อมา
นั่นไม่ใช่ความผิดของจอห์นแน่นอน แต่เป็นความผิดของคนที่ถอดฝาครอบป้องกันออกแล้วปล่อยให้จอห์นจัดการเอง
เช่นเดียวกับในการเขียนโปรแกรม หากผู้ใช้คลาสจะเปลี่ยนสิ่งที่ไม่ได้ตั้งใจให้เปลี่ยนจากภายนอก – ผลที่ตามมาไม่สามารถคาดเดาได้
รองรับได้
สถานการณ์ในการเขียนโปรแกรมมีความซับซ้อนมากกว่าเครื่องชงกาแฟในชีวิตจริง เพราะเราไม่เพียงแค่ซื้อเพียงครั้งเดียว รหัสนี้ได้รับการพัฒนาและปรับปรุงอย่างต่อเนื่อง
หากเรากำหนดขอบเขตอินเทอร์เฟซภายในอย่างเคร่งครัด นักพัฒนาคลาสจะสามารถเปลี่ยนคุณสมบัติและวิธีการภายในได้อย่างอิสระ แม้ว่าจะไม่แจ้งให้ผู้ใช้ทราบก็ตาม
หากคุณเป็นนักพัฒนาคลาสดังกล่าว เป็นเรื่องดีที่รู้ว่าวิธีการส่วนตัวสามารถเปลี่ยนชื่อได้อย่างปลอดภัย พารามิเตอร์สามารถเปลี่ยนแปลงได้ และแม้กระทั่งถูกลบออก เนื่องจากไม่มีโค้ดภายนอกที่ขึ้นอยู่กับวิธีการเหล่านั้น
สำหรับผู้ใช้ เมื่อมีเวอร์ชันใหม่ออกมา อาจเป็นการยกเครื่องภายในทั้งหมด แต่ก็ยังอัปเกรดได้ง่ายหากอินเทอร์เฟซภายนอกเหมือนกัน
ซ่อนความซับซ้อน
ผู้คนชื่นชอบการใช้สิ่งที่เรียบง่าย อย่างน้อยก็จากภายนอก สิ่งที่อยู่ข้างในนั้นแตกต่างออกไป
โปรแกรมเมอร์ก็ไม่มีข้อยกเว้น
จะสะดวกเสมอเมื่อมีการซ่อนรายละเอียดการใช้งาน และมีอินเทอร์เฟซภายนอกที่เรียบง่ายและมีเอกสารประกอบครบถ้วน
หากต้องการซ่อนอินเทอร์เฟซภายใน เราใช้คุณสมบัติที่ได้รับการป้องกันหรือส่วนตัว:
ฟิลด์ที่มีการป้องกันเริ่มต้นด้วย _
นั่นเป็นแบบแผนที่รู้จักกันดี ไม่ได้บังคับใช้ในระดับภาษา โปรแกรมเมอร์ควรเข้าถึงเฉพาะฟิลด์ที่ขึ้นต้นด้วย _
จากคลาสและคลาสที่สืบทอดมาจากฟิลด์นั้น
ช่องส่วนตัวเริ่มต้นด้วย #
JavaScript ทำให้แน่ใจว่าเราสามารถเข้าถึงได้เฉพาะจากภายในชั้นเรียนเท่านั้น
ขณะนี้ ช่องส่วนตัวไม่ได้รับการสนับสนุนอย่างดีในเบราว์เซอร์ แต่สามารถเติมข้อมูลได้