การออกแบบซอฟต์แวร์: โมดูลคลาสและฟังก์ชันในลักษณะที่เมื่อจำเป็นต้องใช้ฟังก์ชันใหม่เราไม่ควรแก้ไขโค้ดที่มีอยู่ แต่ควรเขียนโค้ดใหม่ที่โค้ดที่มีอยู่จะใช้ สิ่งนี้อาจดูแปลกโดยเฉพาะกับภาษาเช่น Java, C, C ++ หรือ C # ซึ่งไม่เพียง แต่ใช้กับซอร์สโค้ดเท่านั้น แต่ยังรวมถึงไบนารีด้วย เราต้องการสร้างคุณลักษณะใหม่ในรูปแบบที่ไม่ต้องแจกจ่ายไบนารีไฟล์ปฏิบัติการหรือ DLL ที่มีอยู่ซ้ำ
OCP ในบริบท SOLID
เราได้เห็นหลักการ SRP ของ Single Responsibility แล้วซึ่งระบุว่าโมดูลควรมีเหตุผลเดียวที่จะเปลี่ยนแปลง หลักการ OCP และ SRP เป็นส่วนเสริม โค้ดที่ออกแบบตามหลักการ SRP จะเคารพหลักการ OCP ด้วย เมื่อเรามีโค้ดที่มีเหตุผลเดียวในการเปลี่ยนแปลงการแนะนำฟีเจอร์ใหม่จะสร้างเหตุผลรองสำหรับการเปลี่ยนแปลงนั้น ดังนั้นทั้ง SRP และ OCP จะถูกละเมิด ในทำนองเดียวกันถ้าเรามีโค้ดที่ควรเปลี่ยนก็ต่อเมื่อฟังก์ชันหลักของมันเปลี่ยนไปและไม่ควรเปลี่ยนแปลงเมื่อมีการเพิ่มฟังก์ชันการทำงานใหม่ดังนั้นการปฏิบัติตาม OCP ส่วนใหญ่ก็จะเคารพ SRP ด้วย
นี่ไม่ได้หมายความว่า SRP จะนำไปสู่ OCP หรือในทางกลับกันเสมอไป แต่ในกรณีส่วนใหญ่หากปฏิบัติตามข้อใดข้อหนึ่งการบรรลุข้อที่สองนั้นค่อนข้างง่าย
จากมุมมองทางเทคนิคล้วนๆหลักการเปิด / ปิดนั้นง่ายมาก ความสัมพันธ์ที่เรียบง่ายระหว่างสองคลาสเช่นเดียวกับความสัมพันธ์ด้านล่างเป็นการละเมิดหลักการ OCP
คลาส User ใช้คลาส Logic โดยตรง หากเราต้องการใช้คลาส Logic ที่สองในลักษณะที่อนุญาตให้เราใช้ทั้งคลาสปัจจุบันและคลาสใหม่ได้จำเป็นต้องเปลี่ยนคลาส Logic ที่มีอยู่ ผู้ใช้เชื่อมโยงโดยตรงกับการใช้ตรรกะไม่มีทางที่เราจะให้ตรรกะใหม่โดยไม่ส่งผลกระทบต่อตรรกะปัจจุบัน และเมื่อเราพูดถึงภาษาที่พิมพ์แบบคงที่คลาส User ก็มีแนวโน้มที่จะต้องมีการปรับเปลี่ยนเช่นกัน หากเราพูดถึงภาษาที่คอมไพล์แล้วแน่นอนว่าทั้ง User executable และ Logic executable หรือไดนามิกไลบรารีจะต้องมีการคอมไพล์ใหม่และการจัดส่งควรหลีกเลี่ยงเมื่อเป็นไปได้
เมื่ออ้างอิงถึงโครงร่างก่อนหน้านี้เราสามารถอนุมานได้ว่าคลาสใด ๆ ที่ใช้คลาสอื่นโดยตรงอาจนำไปสู่การละเมิดหลักการเปิด / ปิด
สมมติว่าเราต้องการเขียนคลาสที่สามารถให้ความคืบหน้าของไฟล์ที่ดาวน์โหลด "เป็นเปอร์เซ็นต์" ผ่านแอปพลิเคชันของเรา เราจะมีคลาสหลักสองคลาสคือความคืบหน้าและไฟล์และฉันเดาว่าเราต้องการใช้มันดังนี้:
function testItCanGetTheProgressOfAFileAsAPercent () {
$ file = ไฟล์ใหม่ ();
$ file-> length = 200;
$ file-> ส่ง = 100;
$ progress = ความคืบหน้าใหม่ (ไฟล์ $);
$ this-> assertEquals (50, $ progress-> getAsPercent ());
}
ในรหัสนี้เราเป็นผู้ใช้ Progress เราต้องการรับค่าเป็นเปอร์เซ็นต์โดยไม่คำนึงถึงขนาดไฟล์จริง เราใช้ไฟล์เป็นแหล่งข้อมูล ไฟล์มีความยาวเป็นไบต์และฟิลด์ที่เรียกว่าส่งซึ่งแสดงถึงจำนวนข้อมูลที่ส่งไปยังผู้ดาวน์โหลด เราไม่สนใจว่าค่าเหล่านี้จะอัปเดตอย่างไรในแอปพลิเคชัน เราสามารถสันนิษฐานได้ว่ามีตรรกะมหัศจรรย์บางอย่างที่ทำสิ่งนี้ให้กับเราดังนั้นในการทดสอบเราสามารถตั้งค่าได้อย่างชัดเจน
ไฟล์คลาส {
ความยาว $ สาธารณะ;
ส่งสาธารณะ $;
}
คลาสไฟล์เป็นเพียงออบเจ็กต์ข้อมูลธรรมดาที่มีสองฟิลด์ แน่นอนว่าควรมีข้อมูลและพฤติกรรมอื่น ๆ เช่นชื่อไฟล์เส้นทางเส้นทางสัมพัทธ์ไดเรกทอรีปัจจุบันประเภทสิทธิ์และอื่น ๆ
ความคืบหน้าระดับ {
ไฟล์ $ ส่วนตัว;
function __construct (ไฟล์ $ file) {
$ this-> file = $ ไฟล์;
}
ฟังก์ชัน getAsPercent () {
ส่งคืน $ this-> file-> ส่ง * 100 / $ this-> file-> length;
}
}
ความคืบหน้าเป็นเพียงคลาสที่ยอมรับไฟล์ในตัวสร้าง เพื่อความชัดเจนเราได้ระบุประเภทตัวแปรในพารามิเตอร์ตัวสร้าง มีวิธีการเดียวที่มีประโยชน์ใน Progress คือ getAsPercent () ซึ่งจะรับค่าและความยาวที่ส่งจากไฟล์และเปลี่ยนเป็นเปอร์เซ็นต์ เรียบง่ายและใช้งานได้จริง
ดูเหมือนว่ารหัสนี้จะถูกต้อง แต่มันละเมิดหลักการเปิด / ปิด
แต่ทำไม?
แล้วยังไง?
แอพพลิเคชั่นแต่ละตัวที่จะพัฒนาไปตามกาลเวลาจะต้องการคุณสมบัติใหม่ ๆ คุณสมบัติใหม่สำหรับแอปพลิเคชันของเราคืออนุญาตให้สตรีมเพลงแทนที่จะดาวน์โหลดไฟล์เท่านั้น ความยาวของไฟล์แสดงเป็นไบต์ระยะเวลาของเพลงเป็นวินาที เราต้องการเสนอแถบความคืบหน้าให้กับผู้ฟังของเรา แต่เราสามารถนำคลาสที่เขียนไว้ข้างต้นกลับมาใช้ใหม่ได้หรือไม่?
ไม่เราทำไมได้. ความก้าวหน้าของเราผูกพันกับ File สามารถจัดการได้เฉพาะข้อมูลไฟล์แม้ว่าจะสามารถนำไปใช้กับเนื้อหาเพลงได้ แต่การจะแก้ไขได้นั้นเราต้องทำให้ Progress รู้จักเพลงและไฟล์ต่างๆ หากการออกแบบของเราสอดคล้องกับ OCP เราไม่จำเป็นต้องแตะไฟล์หรือความคืบหน้า เราสามารถนำความคืบหน้าที่มีอยู่กลับมาใช้ใหม่และปรับใช้กับเพลงได้
ภาษาที่พิมพ์แบบไดนามิกมีข้อได้เปรียบในการจัดการประเภทอ็อบเจ็กต์ในขณะรันไทม์ สิ่งนี้ช่วยให้เราสามารถลบตัวพิมพ์ออกจากตัวสร้างความคืบหน้าและรหัสจะทำงานต่อไป
ความคืบหน้าระดับ {
ไฟล์ $ ส่วนตัว;
ฟังก์ชัน __construct ($ file) {
$ this-> file = $ ไฟล์;
}
ฟังก์ชัน getAsPercent () {
ส่งคืน $ this-> file-> ส่ง * 100 / $ this-> file-> length;
}
}
ตอนนี้เราสามารถเปิดตัวอะไรก็ได้ที่ Progress และโดยอะไรก็ตามฉันหมายถึงอะไรก็ได้:
คลาสดนตรี {
ความยาว $ สาธารณะ;
ส่งสาธารณะ $;
ศิลปิน $ สาธารณะ;
อัลบั้ม $ สาธารณะ;
สาธารณะ $ releaseDate;
ฟังก์ชัน getAlbumCoverFile () {
ส่งคืน 'รูปภาพ / ปก /' $ this-> ศิลปิน '/'. $ this-> อัลบั้ม '.png';
}
}
และชั้นเรียนดนตรีเช่นเดียวกับข้างต้นจะทำงานได้อย่างสมบูรณ์ เราสามารถทดสอบได้อย่างง่ายดายด้วยการทดสอบที่คล้ายกับ File
function testItCanGetTheProgressOfAMusicStreamAsAPercent () {
$ music = เพลงใหม่ ();
$ เพลง -> ความยาว = 200;
$ music-> ส่ง = 100;
$ progress = ความคืบหน้าใหม่ ($ music);
$ this-> assertEquals (50, $ progress-> getAsPercent ());
}
ดังนั้นโดยพื้นฐานแล้วเนื้อหาที่วัดได้ใด ๆ สามารถใช้กับคลาส Progress ได้ บางทีเราควรแสดงในโค้ดโดยเปลี่ยนชื่อตัวแปร:
ความคืบหน้าระดับ {
เนื้อหาที่วัดได้ส่วนตัว
function __construct ($ MeasurableContent) {
$ this-> MeasurableContent = $ MeasurableContent;
}
ฟังก์ชัน getAsPercent () {
ส่งคืน $ this-> MeasurableContent-> ส่ง * 100 / $ this-> MeasurableContent-> length;
}
}
เมื่อเราระบุ File เป็น typehint เราก็มองโลกในแง่ดีเกี่ยวกับสิ่งที่คลาสของเราสามารถจัดการได้ เป็นเรื่องชัดเจนและหากมีสิ่งอื่นเกิดขึ้นเขาบอกเราว่าผิดพลาดครั้งใหญ่
Una คลาสที่แทนที่เมธอดของคลาสฐานเพื่อให้สัญญาคลาสพื้นฐานไม่ได้รับเกียรติจากคลาสที่ได้รับ
เราไม่ต้องการลงเอยด้วยการพยายามเรียกเมธอดหรือเข้าถึงช่องบนวัตถุที่ไม่เป็นไปตามสัญญาของเรา เมื่อเรามีการพิมพ์สัญญาจะถูกระบุโดยมัน ฟิลด์และวิธีการของคลาส File ตอนนี้เราไม่มีอะไรเลยเราสามารถส่งอะไรก็ได้แม้กระทั่งสตริงและมันจะส่งผลให้เกิดข้อผิดพลาดที่ไม่ดี
แม้ว่าผลลัพธ์สุดท้ายจะเหมือนกันในทั้งสองกรณี ซึ่งหมายถึงการหยุดทำงานของโค้ด แต่ผลลัพธ์แรกก็สร้างข้อความที่ดี อย่างไรก็ตามสิ่งนี้มืดมาก ไม่มีทางรู้ว่าตัวแปรคืออะไร - สตริงในกรณีของเรา - และคุณสมบัติใดที่ถูกค้นหาและไม่พบ เป็นการยากที่จะดีบักและแก้ไขปัญหา โปรแกรมเมอร์ต้องเปิดคลาส Progress อ่านและทำความเข้าใจ สัญญา ในกรณีนี้ เมื่อคุณไม่ได้ระบุคำใบ้อย่างชัดเจนคือ defiถูกทำลายโดยพฤติกรรมของความคืบหน้า มันเป็นสัญญาโดยนัยที่รู้เฉพาะกับโปรเกรสเท่านั้น ในตัวอย่างของเราก็คือ defiเสร็จสิ้นโดยการเข้าถึงสองฟิลด์ ส่ง และ ความยาว ในเมธอด getAsPercent() ในชีวิตจริง สัญญาโดยนัยอาจซับซ้อนมากและยากที่จะค้นพบเพียงแค่มองหาเวลาไม่กี่วินาทีในชั้นเรียน
ขอแนะนำให้ใช้วิธีนี้หากไม่มีเคล็ดลับอื่น ๆ ด้านล่างนี้ที่สามารถนำไปใช้ได้อย่างง่ายดายหรือหากจะก่อให้เกิดการเปลี่ยนแปลงทางสถาปัตยกรรมที่ร้ายแรงซึ่งไม่รับประกันความพยายาม
Ercole Palmeri
คำว่า Smart Lock Market หมายถึงอุตสาหกรรมและระบบนิเวศที่เกี่ยวข้องกับการผลิต การจัดจำหน่าย และการใช้งาน...
ในวิศวกรรมซอฟต์แวร์ รูปแบบการออกแบบเป็นวิธีแก้ปัญหาที่เหมาะสมที่สุดสำหรับปัญหาที่มักเกิดขึ้นในการออกแบบซอฟต์แวร์ ฉันชอบ…
การมาร์กทางอุตสาหกรรมเป็นคำกว้างๆ ที่ครอบคลุมถึงเทคนิคต่างๆ ที่ใช้ในการสร้างมาร์กถาวรบนพื้นผิวของ...
ตัวอย่างแมโคร Excel ง่ายๆ ต่อไปนี้เขียนโดยใช้ VBA เวลาในการอ่านโดยประมาณ: 3 นาที ตัวอย่าง...