บทความ

เปิด / ปิดตามหลักการ SOLID

เอนทิตีซอฟต์แวร์ (คลาสโมดูลฟังก์ชัน ฯลฯ ) ควรเปิดสำหรับส่วนขยาย แต่ปิดเพื่อแก้ไข

การออกแบบซอฟต์แวร์: โมดูลคลาสและฟังก์ชันในลักษณะที่เมื่อจำเป็นต้องใช้ฟังก์ชันใหม่เราไม่ควรแก้ไขโค้ดที่มีอยู่ แต่ควรเขียนโค้ดใหม่ที่โค้ดที่มีอยู่จะใช้ สิ่งนี้อาจดูแปลกโดยเฉพาะกับภาษาเช่น Java, C, C ++ หรือ C # ซึ่งไม่เพียง แต่ใช้กับซอร์สโค้ดเท่านั้น แต่ยังรวมถึงไบนารีด้วย เราต้องการสร้างคุณลักษณะใหม่ในรูปแบบที่ไม่ต้องแจกจ่ายไบนารีไฟล์ปฏิบัติการหรือ DLL ที่มีอยู่ซ้ำ
OCP ในบริบท SOLID

 

SRP และ OCP เสริม

เราได้เห็นหลักการ SRP ของ Single Responsibility แล้วซึ่งระบุว่าโมดูลควรมีเหตุผลเดียวที่จะเปลี่ยนแปลง หลักการ OCP และ SRP เป็นส่วนเสริม โค้ดที่ออกแบบตามหลักการ SRP จะเคารพหลักการ OCP ด้วย เมื่อเรามีโค้ดที่มีเหตุผลเดียวในการเปลี่ยนแปลงการแนะนำฟีเจอร์ใหม่จะสร้างเหตุผลรองสำหรับการเปลี่ยนแปลงนั้น ดังนั้นทั้ง SRP และ OCP จะถูกละเมิด ในทำนองเดียวกันถ้าเรามีโค้ดที่ควรเปลี่ยนก็ต่อเมื่อฟังก์ชันหลักของมันเปลี่ยนไปและไม่ควรเปลี่ยนแปลงเมื่อมีการเพิ่มฟังก์ชันการทำงานใหม่ดังนั้นการปฏิบัติตาม OCP ส่วนใหญ่ก็จะเคารพ SRP ด้วย
นี่ไม่ได้หมายความว่า SRP จะนำไปสู่ ​​OCP หรือในทางกลับกันเสมอไป แต่ในกรณีส่วนใหญ่หากปฏิบัติตามข้อใดข้อหนึ่งการบรรลุข้อที่สองนั้นค่อนข้างง่าย

 

ตัวอย่างการละเมิดหลักการ 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: รายงานการวิจัยตลาดที่เผยแพร่

คำว่า Smart Lock Market หมายถึงอุตสาหกรรมและระบบนิเวศที่เกี่ยวข้องกับการผลิต การจัดจำหน่าย และการใช้งาน...

27 2024 มีนาคม

รูปแบบการออกแบบคืออะไร: ทำไมต้องใช้มัน การจำแนกประเภท ข้อดีและข้อเสีย

ในวิศวกรรมซอฟต์แวร์ รูปแบบการออกแบบเป็นวิธีแก้ปัญหาที่เหมาะสมที่สุดสำหรับปัญหาที่มักเกิดขึ้นในการออกแบบซอฟต์แวร์ ฉันชอบ…

26 2024 มีนาคม

วิวัฒนาการทางเทคโนโลยีของการมาร์กทางอุตสาหกรรม

การมาร์กทางอุตสาหกรรมเป็นคำกว้างๆ ที่ครอบคลุมถึงเทคนิคต่างๆ ที่ใช้ในการสร้างมาร์กถาวรบนพื้นผิวของ...

25 2024 มีนาคม

ตัวอย่างของ Excel Macros ที่เขียนด้วย VBA

ตัวอย่างแมโคร Excel ง่ายๆ ต่อไปนี้เขียนโดยใช้ VBA เวลาในการอ่านโดยประมาณ: 3 นาที ตัวอย่าง...

25 2024 มีนาคม

อ่านนวัตกรรมในภาษาของคุณ

จดหมายข่าวนวัตกรรม
อย่าพลาดข่าวสารที่สำคัญที่สุดเกี่ยวกับนวัตกรรม ลงทะเบียนเพื่อรับพวกเขาทางอีเมล

ติดตามเรา