รูปแบบการออกแบบ
- พื้นฐานของซอฟต์แวร์เชิงวัตถุที่นำกลับมาใช้ใหม่ได้
รูปแบบการออกแบบเป็นชุดของการใช้ซ้ำที่รู้จักกันสำหรับคนส่วนใหญ่แคตตาล็อกที่จำแนกและประสบการณ์การออกแบบรหัส การใช้รูปแบบการออกแบบคือการนำรหัสกลับมาใช้ใหม่ทำให้โค้ดเข้าใจง่ายขึ้นโดยผู้อื่นและมั่นใจในความน่าเชื่อถือของรหัส ไม่ต้องสงสัยเลยว่ารูปแบบการออกแบบนั้นเป็น win-win สำหรับตัวเองคนอื่น ๆ และระบบ รูปแบบการออกแบบทำให้การรวบรวมรหัสออกแบบอย่างแท้จริง รูปแบบการออกแบบเป็นรากฐานที่สำคัญของวิศวกรรมซอฟต์แวร์เช่นเดียวกับอิฐและหินในอาคาร การใช้รูปแบบการออกแบบอย่างมีเหตุผลในโครงการสามารถแก้ปัญหาได้อย่างสมบูรณ์แบบ ตอนนี้แต่ละรูปแบบมีหลักการที่สอดคล้องกันเพื่อให้สอดคล้องกับมัน แต่ละรูปแบบอธิบายถึงปัญหาที่เกิดขึ้นซ้ำ ๆ รอบตัวเราและการแก้ปัญหาหลักของปัญหาซึ่งเป็นเหตุผลว่าทำไมมันจึงสามารถใช้กันอย่างแพร่หลาย บทนี้เป็นรูปแบบการออกแบบของความงามของ Java [วิวัฒนาการจากซีรี่ส์มือใหม่ถึงผู้เชี่ยวชาญ] เราจะศึกษาบทนี้ในการรวมกันของทฤษฎีและการปฏิบัติ ฉันหวังว่าผู้ที่ชื่นชอบโปรแกรมจะได้เรียนรู้รูปแบบการออกแบบที่ดีและเป็นวิศวกรซอฟต์แวร์ที่ยอดเยี่ยม!
1. การจำแนกประเภทของรูปแบบการออกแบบ
โดยรวมแล้วรูปแบบการออกแบบแบ่งออกเป็นสามประเภท:
โหมดการสร้างมีห้าประเภท: โหมดวิธีโรงงานโหมดโรงงานนามธรรมโหมดซิงเกิลโหมดผู้สร้างและโหมดต้นแบบ
มีโหมดโครงสร้างเจ็ดโหมด: โหมดอะแดปเตอร์, โหมดมัณฑนากร, โหมดพร็อกซี, โหมดปรากฏตัว, โหมดบริดจ์โหมดการรวมและโหมดความบันเทิง
โหมดพฤติกรรมทั้งหมดสิบเอ็ด: โหมดนโยบายโหมดวิธีแม่แบบโหมดผู้สังเกตการณ์โหมดย่อยซ้ำโหมดโซ่ความรับผิดชอบโหมดคำสั่งโหมดบันทึกโหมดสถานะโหมดสถานะผู้เยี่ยมชมโหมดสื่อกลางและโหมดล่าม
ในความเป็นจริงมีอีกสองหมวดหมู่: โหมดพร้อมกันและโหมดพูลเธรด มาใช้รูปภาพเพื่ออธิบายโดยรวม:
2. หกหลักการออกแบบรูปแบบ
1. เปิดหลักการปิด
หลักการของการเปิดและปิดคือการเปิดให้ส่วนขยายและใกล้เคียงกับการดัดแปลง เมื่อต้องมีการขยายโปรแกรมคุณไม่สามารถแก้ไขรหัสต้นฉบับเพื่อให้ได้เอฟเฟกต์ปลั๊กร้อน ดังนั้นในคำหนึ่งมันคือ: เพื่อให้โปรแกรมขยายได้และง่ายต่อการบำรุงรักษาและอัพเกรด เพื่อให้ได้เอฟเฟกต์ดังกล่าวเราจำเป็นต้องใช้อินเทอร์เฟซและคลาสนามธรรมซึ่งเราจะกล่าวถึงในการออกแบบเฉพาะในภายหลัง
2. หลักการทดแทน Liskov
หลักการทดแทน Liskov LSP เป็นหนึ่งในหลักการพื้นฐานของการออกแบบเชิงวัตถุ หลักการทดแทนที่หลากหลายกล่าวว่าชั้นเรียนฐานใด ๆ สามารถปรากฏขึ้น subclasses สามารถปรากฏขึ้นได้อย่างแน่นอน LSP เป็นรากฐานที่สำคัญของการสืบทอดและนำกลับมาใช้ใหม่ เฉพาะเมื่อคลาสอนุพันธ์สามารถแทนที่คลาสฐานและฟังก์ชั่นของหน่วยซอฟต์แวร์จะไม่ได้รับผลกระทบสามารถนำคลาสฐานกลับมาใช้ใหม่ได้อย่างแท้จริงและคลาสอนุพันธ์ยังสามารถเพิ่มพฤติกรรมใหม่ตามคลาสฐาน หลักการทดแทนของริกเตอร์เป็นส่วนเสริมของหลักการ "ปิดปิด" ขั้นตอนสำคัญในการใช้หลักการ "เปิดปิด" คือสิ่งที่เป็นนามธรรม ความสัมพันธ์ในการสืบทอดระหว่างคลาสฐานและคลาสย่อยคือการใช้งานที่เป็นรูปธรรมของสิ่งที่เป็นนามธรรมดังนั้นหลักการทดแทนที่หลากหลายจึงเป็นมาตรฐานของขั้นตอนที่เฉพาะเจาะจงในการใช้นามธรรม - จากสารานุกรม Baidu
3. หลักการผกผันการพึ่งพาอาศัยกัน
นี่คือพื้นฐานของหลักการเปิดและปิด เนื้อหาเฉพาะ: การเขียนโปรแกรมอย่างแท้จริงของอินเทอร์เฟซขึ้นอยู่กับสิ่งที่เป็นนามธรรมมากกว่าคอนกรีต
4. หลักการแยกอินเทอร์เฟซ
หลักการนี้หมายถึง: การใช้อินเทอร์เฟซที่แยกได้หลายตัวดีกว่าการใช้อินเทอร์เฟซเดียว นอกจากนี้ยังหมายถึงการลดระดับการมีเพศสัมพันธ์ระหว่างชั้นเรียน จากที่นี่เราจะเห็นได้ว่ารูปแบบการออกแบบเป็นแนวคิดการออกแบบของซอฟต์แวร์โดยเริ่มจากสถาปัตยกรรมซอฟต์แวร์ขนาดใหญ่เพื่อความสะดวกในการอัพเกรดและบำรุงรักษา ดังนั้นบทความข้างต้นจึงปรากฏขึ้นหลายครั้ง: ลดการพึ่งพาและลดการมีเพศสัมพันธ์
5. หลักการ Demeter
ทำไมหลักการของความรู้น้อยที่สุด? นั่นคือเอนทิตีหนึ่งควรโต้ตอบกับเอนทิตีอื่น ๆ ให้น้อยที่สุดเท่าที่จะเป็นไปได้เพื่อให้โมดูลการทำงานของระบบค่อนข้างเป็นอิสระ
6. หลักการใช้ซ้ำแบบคอมโพสิต
หลักการคือการพยายามใช้วิธีการสังเคราะห์/การรวมตัวมากกว่าการสืบทอด
3. รูปแบบการออกแบบ 23 รูปแบบของ Java
เริ่มต้นจากส่วนนี้เราแนะนำรายละเอียดเกี่ยวกับแนวคิดสถานการณ์แอปพลิเคชัน ฯลฯ ของ 23 รูปแบบการออกแบบใน Java และวิเคราะห์พวกเขาร่วมกับลักษณะและหลักการของรูปแบบการออกแบบ
1. วิธีโรงงาน
โหมดวิธีโรงงานมีสามประเภท:
11. แบบจำลองโรงงานธรรมดาคือการสร้างคลาสโรงงานและสร้างอินสแตนซ์ของบางคลาสที่ใช้อินเทอร์เฟซเดียวกัน ก่อนอื่นดูแผนภาพความสัมพันธ์:
ตัวอย่างเช่น: (ให้ตัวอย่างการส่งอีเมลและข้อความ)
ก่อนอื่นสร้างอินเทอร์เฟซทั่วไประหว่างสอง:
ผู้ส่งอินเทอร์เฟซสาธารณะ {โมฆะสาธารณะส่ง (); - ประการที่สองสร้างคลาสการใช้งาน:
Mailsender คลาสสาธารณะใช้ผู้ส่ง {@Override โมฆะสาธารณะส่ง () {system.out.println ("นี่คือ Mailsender!"); }} คลาสสาธารณะ SMSSENDER ใช้ผู้ส่ง {@Override โมฆะสาธารณะส่ง () {System.out.println ("นี่คือผู้ส่ง SMS!"); -ในที่สุดการก่อสร้างโรงงาน:
คลาสสาธารณะ sendfactory {ผู้ส่งสาธารณะผลิต (ประเภทสตริง) {if ("mail" .equals (ประเภท)) {ส่งคืน mailsender ใหม่ (); } อื่นถ้า ("sms" .equals (ประเภท)) {ส่งคืน smssender ใหม่ (); } else {system.out.println ("โปรดป้อนประเภทที่ถูกต้อง!"); คืนค่า null; -มาทดสอบกันเถอะ:
Public Class FactoryTest {โมฆะสาธารณะคงที่หลัก (String [] args) {SendFactory Factory = new SendFactory (); ผู้ส่งผู้ส่ง = Factory.Produce ("SMS"); Sender.Send (); -เอาท์พุท: นี่คือผู้ส่ง SMS!
22. โหมดวิธีการเป็นโรงงานหลายโหมด เป็นการปรับปรุงโหมดวิธีการโรงงานธรรมดา ในโหมดวิธีการทำโรงงานธรรมดาหากสตริงที่ผ่านไม่ถูกต้องวัตถุไม่สามารถสร้างได้อย่างถูกต้อง โหมดวิธีการโรงงานหลายวิธีมีวิธีการหลายวิธีในโรงงานเพื่อสร้างวัตถุแยกต่างหาก แผนภาพความสัมพันธ์:
เพียงแก้ไขรหัสด้านบนและเปลี่ยนคลาส SendFactory ดังนี้:
คลาสสาธารณะ SendFactory {Publicer Producemail () {return New Mailsender (); } ผู้ส่งสาธารณะผลิต () {ส่งคืน smssender ใหม่ (); - คลาสทดสอบมีดังนี้:
Public Class FactoryTest {โมฆะสาธารณะคงที่หลัก (String [] args) {SendFactory Factory = new SendFactory (); ผู้ส่งผู้ส่ง = Factory.producemail (); Sender.Send (); -เอาท์พุท: นี่คือ Mailsender!
33. โหมดวิธีการโรงงานแบบคง ที่ตั้งค่าวิธีการในโหมดวิธีการหลายวิธีด้านบนเป็นแบบคงที่และไม่จำเป็นต้องสร้างอินสแตนซ์เพียงแค่เรียกมันโดยตรง
คลาสสาธารณะ sendfactory {Producter ผู้ส่งคงที่ Producemail () {return New Mailsender (); } ผู้ส่งคงที่สาธารณะผลิต () {ส่งคืน smssender ใหม่ (); - Public Class FactoryTest {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {ผู้ส่งผู้ส่ง = sendFactory.producemail (); Sender.Send (); -เอาท์พุท: นี่คือ Mailsender!
โดยรวมแล้วแบบจำลองโรงงานมีความเหมาะสม: เมื่อจำเป็นต้องสร้างผลิตภัณฑ์จำนวนมากและมีอินเทอร์เฟซทั่วไปสามารถสร้างได้ผ่านแบบจำลองวิธีการโรงงาน ในบรรดาสามโหมดข้างต้นโหมดแรกไม่สามารถสร้างวัตถุได้อย่างถูกต้องหากสตริงที่ผ่านเข้ามาไม่ถูกต้องและโหมดที่สามไม่จำเป็นต้องสร้างอินสแตนซ์คลาสโรงงานเมื่อเทียบกับที่สอง ดังนั้นในกรณีส่วนใหญ่เราจะเลือกโหมดที่สาม - โหมดวิธีการคงที่ของโรงงาน
2. รูปแบบโรงงานนามธรรม
มีปัญหากับโมเดลวิธีการโรงงานซึ่งก็คือการสร้างชั้นเรียนขึ้นอยู่กับคลาสโรงงาน กล่าวคือหากคุณต้องการขยายโปรแกรมคุณต้องปรับเปลี่ยนคลาสโรงงานซึ่งละเมิดหลักการปิด ดังนั้นจากมุมมองการออกแบบจึงมีปัญหาบางอย่าง วิธีแก้ปัญหา? สิ่งนี้ใช้รูปแบบโรงงานนามธรรมเพื่อสร้างคลาสโรงงานหลายคลาส ด้วยวิธีนี้เมื่อต้องการฟังก์ชั่นใหม่คุณสามารถเพิ่มคลาสโรงงานใหม่ได้โดยตรงโดยไม่ต้องแก้ไขรหัสก่อนหน้า เนื่องจากโรงงานนามธรรมไม่ใช่เรื่องง่ายที่จะเข้าใจเราก่อนที่จะดูไดอะแกรมก่อนจากนั้นติดตามรหัสซึ่งง่ายต่อการเข้าใจ
โปรดดูตัวอย่าง:
ผู้ส่งอินเทอร์เฟซสาธารณะ {โมฆะสาธารณะส่ง (); -สองคลาสการใช้งาน:
Mailsender คลาสสาธารณะใช้ผู้ส่ง {@Override โมฆะสาธารณะส่ง () {system.out.println ("นี่คือ Mailsender!"); }} คลาสสาธารณะ SMSSENDER ใช้ผู้ส่ง {@Override โมฆะสาธารณะส่ง () {System.out.println ("นี่คือผู้ส่ง SMS!"); -สองประเภทจากโรงงาน:
คลาสสาธารณะ SendMailFactory ใช้ผู้ให้บริการ {@Override ผู้ส่งสาธารณะผลิต () {ส่งคืน mailsender ใหม่ (); - คลาสสาธารณะ SendsMsFactory ใช้ผู้ให้บริการ {@Override ผู้ส่งสาธารณะผลิต () {ส่งคืน smssender ใหม่ (); - ให้อินเทอร์เฟซ:
ผู้ให้บริการส่วนต่อประสานสาธารณะ {ผู้ส่งสาธารณะผลิต (); -คลาสทดสอบ:
การทดสอบระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {ผู้ให้บริการผู้ให้บริการ = ใหม่ sendmailfactory (); ผู้ส่งผู้ส่ง = Provider.Produce (); Sender.Send (); -ในความเป็นจริงข้อได้เปรียบของโมเดลนี้คือหากคุณต้องการเพิ่มฟังก์ชั่นตอนนี้: ส่งข้อมูลที่ทันเวลาคุณจะต้องสร้างคลาสการใช้งานใช้อินเทอร์เฟซผู้ส่งและในเวลาเดียวกันทำให้คลาสโรงงานใช้อินเทอร์เฟซผู้ให้บริการซึ่งก็โอเคและไม่จำเป็นต้องเปลี่ยนรหัสสำเร็จรูป การทำเช่นนี้จะทำให้สามารถปรับขนาดได้มากขึ้น!
3. โหมดซิงเกิลตัน
Singleton เป็นรูปแบบการออกแบบที่ใช้กันทั่วไป ในแอปพลิเคชัน Java วัตถุซิงเกิลสามารถตรวจสอบได้ว่าใน JVM มีเพียงหนึ่งอินสแตนซ์ของวัตถุ รุ่นนี้มีประโยชน์หลายประการ:
1. บางคลาสถูกสร้างขึ้นบ่อยขึ้นและสำหรับวัตถุขนาดใหญ่บางอย่างนี่เป็นระบบที่มีขนาดใหญ่
2. ตัวดำเนินการใหม่ถูกกำจัดความถี่ของหน่วยความจำระบบจะลดลงและความดันของ GC จะลดลง
3. บางหมวดหมู่เช่นกลไกการซื้อขายหลักของการแลกเปลี่ยนควบคุมกระบวนการซื้อขาย หากสามารถสร้างหลายหมวดหมู่ระบบจะยุ่งเหยิงอย่างสมบูรณ์ (ตัวอย่างเช่นหากผู้บัญชาการหลายคนปรากฏตัวในคำสั่งกองทัพบกในเวลาเดียวกันมันจะอยู่ในความวุ่นวาย) ดังนั้นโดยการใช้แบบจำลองซิงเกิลตันเท่านั้นเราสามารถมั่นใจได้ว่าเซิร์ฟเวอร์ธุรกรรมหลักจะควบคุมกระบวนการทั้งหมดได้อย่างอิสระ
ก่อนอื่นมาเขียนคลาส Singleton ง่าย ๆ :
Singleton คลาสสาธารณะ { /* ถืออินสแตนซ์แบบคงที่ส่วนตัวเพื่อป้องกันการอ้างอิง ค่าที่นี่เป็นโมฆะโดยมีวัตถุประสงค์เพื่อให้บรรลุการโหลดขี้เกียจ*/ อินสแตนซ์เดี่ยวแบบคงที่ส่วนตัว = null; /* ตัวสร้างส่วนตัวเพื่อป้องกันการสร้างอินสแตนซ์*/ ส่วนตัว Singleton () {}/* วิธีการทางวิศวกรรมแบบคงที่เพื่อสร้างอินสแตนซ์*/ สาธารณะคงที่ Singleton GetInstance () {ถ้า (อินสแตนซ์ == null) {อินสแตนซ์ = ใหม่ซิงเกิล (); } return อินสแตนซ์; } /* หากใช้วัตถุสำหรับการทำให้เป็นอนุกรมสามารถมั่นใจได้ว่าวัตถุนั้นยังคงสอดคล้องกันก่อนและหลังการทำให้เป็นอนุกรม* / วัตถุสาธารณะ readResolve () {return instance; -คลาสนี้สามารถตอบสนองความต้องการพื้นฐาน แต่ถ้าเราวางคลาสนี้ด้วยการป้องกันความปลอดภัยไร้สายไร้สายในสภาพแวดล้อมแบบมัลติเธรดจะมีปัญหาอย่างแน่นอน วิธีแก้ปัญหา? ก่อนอื่นเราจะคิดถึงการเพิ่มคำหลักที่ซิงโครไนซ์ลงในวิธี getInstance ดังนี้:
Singleton GetInstance () {ถ้า (อินสแตนซ์ == null) {อินสแตนซ์ = ใหม่ singleton (); } return อินสแตนซ์; -อย่างไรก็ตามคำหลักที่ซิงโครไนซ์ล็อควัตถุนี้ การใช้งานนี้จะลดลงในประสิทธิภาพเนื่องจากทุกครั้งที่คุณเรียก GetInstance () วัตถุจะต้องถูกล็อค ในความเป็นจริงเฉพาะเมื่อวัตถุถูกสร้างขึ้นเป็นครั้งแรกไม่จำเป็นต้องล็อคดังนั้นสถานที่นี้จะต้องปรับปรุง ลองเปลี่ยนเป็นสิ่งต่อไปนี้:
สาธารณะคงที่ singleton getInstance () {ถ้า (อินสแตนซ์ == null) {ซิงโครไนซ์ (อินสแตนซ์) {ถ้า (อินสแตนซ์ == null) {อินสแตนซ์ = ใหม่ singleton (); }}} ส่งคืนอินสแตนซ์; -ดูเหมือนว่าจะแก้ปัญหาที่กล่าวถึงก่อนหน้านี้การเพิ่มคำหลักที่ซิงโครไนซ์ภายในนั่นคือไม่จำเป็นต้องล็อคเมื่อการโทรจำเป็นต้องมีการล็อคเท่านั้นเมื่ออินสแตนซ์เป็นโมฆะและวัตถุถูกสร้างขึ้นซึ่งมีการปรับปรุงประสิทธิภาพบางอย่าง อย่างไรก็ตามในกรณีนี้อาจมีปัญหา ดูสถานการณ์ต่อไปนี้: การสร้างวัตถุและการดำเนินการที่ได้รับมอบหมายในคำแนะนำ Java จะดำเนินการแยกกันนั่นคืออินสแตนซ์ = ใหม่ซิงเกิล (); คำสั่งดำเนินการในสองขั้นตอน อย่างไรก็ตาม JVM ไม่รับประกันคำสั่งของการดำเนินการทั้งสองนี้ซึ่งหมายความว่าเป็นไปได้ว่า JVM จะจัดสรรพื้นที่สำหรับอินสแตนซ์ซิงเกิลใหม่จากนั้นกำหนดโดยตรงให้กับสมาชิกอินสแตนซ์แล้วเริ่มต้นอินสแตนซ์ซิงเกิล สิ่งนี้อาจทำให้เกิดข้อผิดพลาด ลองใช้เธรด A และ B เป็นตัวอย่าง:
A> A และ B เธรดป้อนครั้งแรกหากการตัดสินในเวลาเดียวกัน
B> ครั้งแรกเข้าสู่บล็อกที่ซิงโครไนซ์เนื่องจากอินสแตนซ์เป็นโมฆะมันจะดำเนินการอินสแตนซ์ = new Singleton ();
C> เนื่องจากกลไกการเพิ่มประสิทธิภาพภายใน JVM ทำให้ JVM ใช้หน่วยความจำว่างเปล่าที่จัดสรรให้กับอินสแตนซ์ซิงเกิลและกำหนดให้กับสมาชิกอินสแตนซ์ (โปรดทราบว่า JVM ไม่ได้เริ่มต้นการเริ่มต้นอินสแตนซ์นี้ในเวลานี้) จากนั้นออกจากบล็อกที่ซิงโครไนซ์
D> B เข้าสู่บล็อกที่ซิงโครไนซ์ เนื่องจากอินสแตนซ์ไม่ได้เป็นโมฆะในเวลานี้จึงออกจากบล็อกที่ซิงโครไนซ์ทันทีและส่งคืนผลลัพธ์ไปยังโปรแกรมที่เรียกว่าวิธีการ
e> ในเวลานี้เธรด B ตั้งใจที่จะใช้อินสแตนซ์ซิงเกิล แต่พบว่ามันยังไม่ได้เริ่มต้นดังนั้นจึงเกิดข้อผิดพลาด
ดังนั้นยังมีข้อผิดพลาดที่เป็นไปได้ในโปรแกรม ในความเป็นจริงกระบวนการทำงานของโปรแกรมนั้นซับซ้อนมาก จากจุดนี้เราจะเห็นได้ว่าโดยเฉพาะอย่างยิ่งในการเขียนโปรแกรมในสภาพแวดล้อมแบบมัลติเธรดนั้นยากและท้าทายมากขึ้น เราปรับปรุงโปรแกรมเพิ่มเติม:
คลาสคงที่คลาสส่วนตัว SingletonFactory {อินสแตนซ์แบบสแตติกส่วนตัว = ใหม่ซิงเกิล (); } สาธารณะคงที่ Singleton getInstance () {return singletonfactory.instance; -สถานการณ์ที่แท้จริงคือรูปแบบ Singleton ใช้คลาสภายในเพื่อรักษาการใช้งานของ Singletons กลไกภายในของ JVM สามารถตรวจสอบให้แน่ใจว่าเมื่อมีการโหลดคลาสกระบวนการโหลดของคลาสนี้จะเป็นเอกสิทธิ์เฉพาะของเธรด ด้วยวิธีนี้เมื่อเราเรียก GetInstance เป็นครั้งแรก JVM สามารถช่วยเราให้แน่ใจว่าอินสแตนซ์ถูกสร้างขึ้นเพียงครั้งเดียวและจะทำให้มั่นใจได้ว่าหน่วยความจำที่กำหนดให้กับอินสแตนซ์จะเริ่มต้นดังนั้นเราจึงไม่ต้องกังวลเกี่ยวกับปัญหาข้างต้น ในเวลาเดียวกันวิธีนี้จะใช้กลไกการยกเว้นซึ่งกันและกันเมื่อมีการเรียกเป็นครั้งแรกซึ่งแก้ปัญหาประสิทธิภาพต่ำ ด้วยวิธีนี้เราสรุปรูปแบบซิงเกิลที่สมบูรณ์แบบชั่วคราว:
คลาสสาธารณะ Singleton { /* วิธีตัวสร้างส่วนตัวเพื่อป้องกันการสร้างอินสแตนซ์* / Private Singleton () {} /* ใช้คลาสภายในที่นี่เพื่อรักษาซิงเกิล* / คลาสคงที่คลาสคงที่ SingletonFactory {อินสแตนซ์ส่วนตัวแบบคงที่ = ใหม่ Singleton (); } /* รับอินสแตนซ์* / สาธารณะ Singleton GetInstance () {return singletonfactory.instance; } /* หากใช้วัตถุสำหรับการทำให้เป็นอนุกรมสามารถมั่นใจได้ว่าวัตถุนั้นยังคงสอดคล้องกันก่อนและหลังการทำให้เป็นอนุกรม* / วัตถุสาธารณะ readresolve () {return getInstance (); -ในความเป็นจริงมันไม่จำเป็นต้องเป็นความจริงที่ว่ามันสมบูรณ์แบบ หากมีข้อยกเว้นถูกโยนลงในตัวสร้างอินสแตนซ์จะไม่ถูกสร้างขึ้นและจะมีข้อผิดพลาด ดังนั้นจึงไม่มีอะไรสมบูรณ์แบบเราสามารถเลือกวิธีการใช้งานที่เหมาะสมที่สุดสำหรับสถานการณ์แอปพลิเคชันของเราตามสถานการณ์จริง บางคนใช้สิ่งนี้ด้วย: เพราะเราจำเป็นต้องซิงโครไนซ์เมื่อสร้างคลาสตราบใดที่เราแยกการสร้างและ getInstance () และเพิ่มคำหลักที่ซิงโครไนซ์ลงในการสร้างแยกต่างหากก็เป็นไปได้เช่นกัน
ระดับสาธารณะ singletontest {อินสแตนซ์แบบคงที่ส่วนตัว = null; Private Singletontest () {} Void Syncinit () {ถ้า (อินสแตนซ์ == null) {อินสแตนซ์ = ใหม่ singletontest (); }} สาธารณะคงที่ singletontest getInstance () {ถ้า (อินสแตนซ์ == null) {syncinit (); } return อินสแตนซ์; -หากคุณพิจารณาประสิทธิภาพโปรแกรมทั้งหมดจะต้องสร้างอินสแตนซ์เพียงครั้งเดียวดังนั้นประสิทธิภาพจะไม่มีผลกระทบใด ๆ
อาหารเสริม: วิธี "อินสแตนซ์เงา" ใช้เพื่อซิงโครไนซ์คุณสมบัติของวัตถุซิงเกิลตัน
ระดับสาธารณะ singletontest {อินสแตนซ์แบบคงที่ส่วนตัว = null; คุณสมบัติเวกเตอร์ส่วนตัว = null; Public Vector GetProperties () {คุณสมบัติกลับ; } Private SingleTontest () {} Void Syncinit () {ถ้า (อินสแตนซ์ == null) {อินสแตนซ์ = new Singletontest (); }} สาธารณะคงที่ singletontest getInstance () {ถ้า (อินสแตนซ์ == null) {syncinit (); } return อินสแตนซ์; } โมฆะสาธารณะ updateProperties () {singletontest shadow = new singletontest (); คุณสมบัติ = Shadow.getProperties (); -ผ่านการเรียนรู้รูปแบบ Singleton เราบอกเรา:
1. มันง่ายที่จะเข้าใจโมเดล Singleton แต่ก็ยังยากที่จะนำไปใช้ในรายละเอียด
2. คำหลักที่ซิงโครไนซ์ล็อควัตถุ เมื่อใช้งานจะต้องใช้ในสถานที่ที่เหมาะสม (โปรดทราบว่าวัตถุและกระบวนการที่ต้องล็อคและบางครั้งไม่ใช่วัตถุทั้งหมดและกระบวนการทั้งหมดต้องล็อค)
ณ จุดนี้รูปแบบซิงเกิลถูกพูดถึงโดยทั่วไป ในตอนท้ายผู้เขียนก็นึกถึงคำถามอื่นซึ่งก็คือการใช้วิธีการเรียนแบบคงที่เพื่อให้ได้ผลของรูปแบบ Singleton ซึ่งเป็นไปได้เช่นกัน ความแตกต่างระหว่างทั้งสองที่นี่คืออะไร?
ขั้นแรกคลาสคงที่ไม่สามารถใช้อินเทอร์เฟซ (ไม่เป็นไรจากมุมมองของชั้นเรียน แต่นั่นจะทำลายแบบคงที่เนื่องจากไม่มีวิธีการปรับเปลี่ยนแบบคงที่ในอินเทอร์เฟซจึงไม่คงที่แม้ว่าจะใช้งาน)
ประการที่สอง Singletons สามารถเริ่มต้นได้และคลาสคงที่มักจะเริ่มต้นเมื่อโหลดเป็นครั้งแรก เหตุผลในการโหลดขี้เกียจคือบางคลาสมีขนาดค่อนข้างใหญ่ดังนั้นการโหลดขี้เกียจช่วยปรับปรุงประสิทธิภาพ
อีกครั้งคลาส Singleton สามารถสืบทอดได้และวิธีการที่สามารถเขียนทับได้ อย่างไรก็ตามวิธีการภายในของคลาสคงที่นั้นคงที่และไม่สามารถเขียนทับได้
จุดสุดท้ายคลาส Singleton มีความยืดหยุ่นมากขึ้น ท้ายที่สุดพวกเขาเป็นเพียงคลาส Java ธรรมดาในแง่ของการดำเนินการ ตราบใดที่พวกเขาตอบสนองความต้องการขั้นพื้นฐานของ Singletons คุณสามารถใช้ฟังก์ชั่นอื่น ๆ ตามที่คุณต้องการได้ แต่คลาสคงที่ไม่สามารถทำได้ จากบทสรุปข้างต้นโดยทั่วไปเราสามารถเห็นความแตกต่างระหว่างทั้งสอง อย่างไรก็ตามในทางกลับกันรูปแบบซิงเกิลที่เรานำไปใช้ในที่สุดจะถูกนำไปใช้ภายในด้วยคลาสคงที่ดังนั้นทั้งสองจึงเกี่ยวข้องกันมาก แต่ระดับของการพิจารณาปัญหาของเรานั้นแตกต่างกัน โดยการรวมแนวคิดสองอย่างเท่านั้นที่สามารถสร้างโซลูชันที่สมบูรณ์แบบได้ เช่นเดียวกับ HashMap ใช้อาร์เรย์ + รายการที่เชื่อมโยงเพื่อนำไปใช้ในความเป็นจริงหลายสิ่งหลายอย่างในชีวิตเป็นเช่นนี้ การใช้วิธีการต่าง ๆ เพื่อจัดการกับปัญหามักจะมีข้อดีและข้อเสีย วิธีที่สมบูรณ์แบบที่สุดคือการรวมข้อดีของแต่ละวิธีในการแก้ปัญหาให้ดีที่สุด!
4. โหมดผู้สร้าง
โมเดลคลาสโรงงานให้รูปแบบของการสร้างคลาสเดียวในขณะที่โมเดลตัวสร้างมุ่งเน้นผลิตภัณฑ์ต่าง ๆ สำหรับการจัดการและใช้เพื่อสร้างวัตถุคอมโพสิต วัตถุคอมโพสิตที่เรียกว่าหมายถึงคลาสบางประเภทที่มีคุณลักษณะที่แตกต่างกัน ในความเป็นจริงโมเดลผู้สร้างได้มาจากการรวมโมเดลโรงงานนามธรรมก่อนหน้าและการทดสอบขั้นสุดท้าย มาดูรหัส:
เช่นเดียวกับหนึ่งก่อนหน้านี้อินเทอร์เฟซผู้ส่งหนึ่งตัวและคลาสการใช้งานสองคลาส Mailsender และ SMSSender ในที่สุดคลาส Builder มีดังนี้:
ผู้สร้างคลาสสาธารณะ {รายการส่วนตัว <ผู้ส่ง> list = new ArrayList <ผู้ส่ง> (); โมฆะสาธารณะ Producemailsender (จำนวน int) {สำหรับ (int i = 0; i <count; i ++) {list.add (mailsender ใหม่ ()); }} โมฆะสาธารณะ ProducesMsSender (จำนวน int) {สำหรับ (int i = 0; i <count; i ++) {list.add (smssender ใหม่ ()); -คลาสทดสอบ:
การทดสอบระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {builder builder = new Builder (); builder.producemailsender (10); -จากมุมมองนี้รูปแบบตัวสร้างจะรวมฟังก์ชั่นจำนวนมากเข้ากับคลาสซึ่งสามารถสร้างสิ่งที่ซับซ้อนมากขึ้น ดังนั้นความแตกต่างจากโมเดลวิศวกรรมคือโมเดลโรงงานมุ่งเน้นไปที่การสร้างผลิตภัณฑ์เดียวในขณะที่โมเดลตัวสร้างมุ่งเน้นไปที่การสร้างวัตถุที่เหมาะสมและหลายส่วน ดังนั้นไม่ว่าจะเลือกโมเดลโรงงานหรือโมเดลผู้สร้างขึ้นอยู่กับสถานการณ์จริง
5. ต้นแบบ
แม้ว่ารูปแบบต้นแบบจะเป็นรูปแบบที่สร้างสรรค์ แต่ก็ไม่มีส่วนเกี่ยวข้องกับรูปแบบทางวิศวกรรม อย่างที่คุณเห็นจากชื่อแนวคิดของรูปแบบนี้คือการคัดลอกและโคลนวัตถุเป็นต้นแบบและสร้างวัตถุใหม่ที่คล้ายกับวัตถุดั้งเดิม บทสรุปนี้จะอธิบายผ่านการคัดลอกวัตถุ ใน Java การคัดลอกวัตถุจะถูกนำไปใช้ผ่าน clone () และคลาสต้นแบบถูกสร้างขึ้นก่อน:
Public Class Prototype ใช้ cloneable {public object clone () พ่น clonenotsupportedException {prototype proto = (prototype) super.clone (); คืนโปรโต; -มันง่ายมาก คลาสต้นแบบจะต้องใช้อินเตอร์เฟส cloneable และเขียนทับวิธีการโคลน ที่นี่วิธีการโคลนสามารถเปลี่ยนเป็นชื่อใด ๆ ได้เนื่องจากอินเทอร์เฟซ cloneable เป็นอินเทอร์เฟซที่ว่างเปล่าคุณสามารถกำหนดชื่อวิธีการของคลาสการใช้งานเช่น Clonea หรือ CloneB ได้โดยพลการเพราะโฟกัสที่นี่คือประโยค super.clone () super.clone () เรียกเมธอด object clone () และในคลาสวัตถุโคลน () เป็นดั้งเดิม จะใช้งานได้อย่างไร? ฉันจะไม่เข้าไปในบทความอื่นเกี่ยวกับการตีความการโทรของวิธีการในท้องถิ่นใน Java ที่นี่ฉันจะรวมสำเนาตื้นและสำเนาวัตถุลึกเข้าด้วยกัน ก่อนอื่นคุณต้องเข้าใจแนวคิดของสำเนาวัตถุลึกและตื้น:
สำเนาตื้น: หลังจากคัดลอกวัตถุตัวแปรของชนิดข้อมูลพื้นฐานจะถูกสร้างขึ้นใหม่ในขณะที่ประเภทการอ้างอิงชี้ไปยังวัตถุดั้งเดิม
สำเนาลึก: หลังจากคัดลอกวัตถุทั้งชนิดข้อมูลพื้นฐานและประเภทการอ้างอิงจะถูกสร้างขึ้นใหม่ พูดง่ายๆการคัดลอกลึกจะถูกคัดลอกอย่างสมบูรณ์ในขณะที่การคัดลอกตื้นไม่ได้ละเอียด
ที่นี่เขียนตัวอย่างการคัดลอกในเชิงลึก:
ต้นแบบระดับสาธารณะใช้ cloneable, serializable {ส่วนตัวคงที่สุดท้าย long serialversionuid = 1l; สตริงส่วนตัว serializableObject ส่วนตัว OBJ; /* สำเนาตื้น*/ โคลนวัตถุสาธารณะ () พ่น clonenotsupportedException {ต้นแบบ proto = (ต้นแบบ) super.clone (); คืนโปรโต; } /* สำเนาลึก* / วัตถุสาธารณะ DeepClone () พ่น IOException, classnotFoundException { /* เขียนไบนารีสตรีมไปยังวัตถุปัจจุบัน* / byteArrayOutputStream bos = new ByteArrayOutputStream (); ObjectOutputStream OOS = ใหม่ ObjectOutputStream (BOS); oos.writeObject (นี่); /* อ่านวัตถุใหม่ที่สร้างขึ้นโดยสตรีมไบนารี*/ byteArrayInputStream bis = byTearrayInputStream ใหม่ (bos.tobyteArray ()); ObjectInputStream OIS = ใหม่ ObjectInputStream (bis); return ois.readObject (); } สตริงสาธารณะ getString () {return string; } โมฆะสาธารณะ setString (สตริงสตริง) {this.string = string; } public serializableObject getObj () {return obj; } โมฆะสาธารณะ setObj (serializableObject obj) {this.obj = obj; }} คลาส serializeableObject ใช้ serializable {ส่วนตัวคงที่สุดท้าย long serialversionuid = 1l; - เพื่อให้ได้สำเนาลึกคุณต้องอ่านอินพุตไบนารีของวัตถุปัจจุบันในรูปแบบของสตรีมจากนั้นเขียนวัตถุที่สอดคล้องกับข้อมูลไบนารี
เราจะยังคงหารือเกี่ยวกับโหมดการออกแบบ ในบทความก่อนหน้านี้ฉันได้พูดคุยเกี่ยวกับ 5 โหมดการสร้าง ในตอนต้นของบทนี้ฉันจะพูดคุยเกี่ยวกับ 7 โหมดโครงสร้าง: โหมดอะแดปเตอร์, โหมดตกแต่ง, โหมดพร็อกซี, โหมดปรากฏตัว, โหมดบริดจ์, โหมดรวมและโหมดความเพลิดเพลิน โหมดอะแดปเตอร์ของวัตถุเป็นที่มาของโหมดต่างๆ มาดูรูปต่อไปนี้:
รูปแบบอะแดปเตอร์แปลงอินเทอร์เฟซของคลาสเป็นตัวแทนอินเทอร์เฟซอื่นที่ลูกค้าคาดหวังโดยมีจุดประสงค์ในการกำจัดปัญหาความเข้ากันได้ของคลาสเนื่องจากอินเทอร์เฟซไม่ตรงกัน ส่วนใหญ่แบ่งออกเป็นสามหมวดหมู่: โหมดอะแดปเตอร์ของคลาสโหมดอะแดปเตอร์ของวัตถุและโหมดอะแดปเตอร์ของอินเทอร์เฟซ ก่อนอื่นมาดูโหมดอะแดปเตอร์ของชั้นเรียนและดูแผนภาพคลาสก่อน:
แนวคิดหลักคือ: มีคลาสต้นทางที่มีวิธีการที่จะปรับเปลี่ยนและกำหนดเป้าหมายเมื่ออินเทอร์เฟซเป้าหมายสามารถกำหนดเป้าหมายได้ ผ่านคลาสอะแดปเตอร์ฟังก์ชั่นต้นฉบับจะขยายไปยังการกำหนดเป้าหมายและอ่านรหัส:
แหล่งที่มาของคลาสสาธารณะ {โมฆะสาธารณะวิธีการ 1 () {system.out.println ("นี่คือวิธีดั้งเดิม!"); - อินเทอร์เฟซสาธารณะกำหนดเป้าหมาย { /* เหมือนกับวิธีการในคลาสดั้งเดิม* / โมฆะสาธารณะวิธีการ 1 (); /* วิธีการของคลาสใหม่*/ โมฆะสาธารณะวิธีการ 2 (); - อะแดปเตอร์คลาสสาธารณะขยายแหล่งที่มาใช้งานได้ {@Override โมฆะสาธารณะวิธีการ 2 () {system.out.println ("นี่คือวิธีการกำหนดเป้าหมาย!"); -คลาสอะแดปเตอร์สืบทอดคลาสต้นทางและใช้อินเทอร์เฟซที่กำหนดเป้าหมาย ต่อไปนี้คือคลาสทดสอบ:
AdapterTest ระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {targetable target = adapter ใหม่ (); target.method1 (); target.method2 (); -เอาท์พุท:
นี่คือวิธีดั้งเดิม!
นี่คือวิธีการกำหนดเป้าหมาย!
ด้วยวิธีนี้คลาสการใช้งานของอินเทอร์เฟซที่กำหนดเป้าหมายมีฟังก์ชั่นของคลาสต้นทาง
โหมดอะแดปเตอร์สำหรับวัตถุ
แนวคิดพื้นฐานเหมือนกับโหมดอะแดปเตอร์ของคลาส เป็นเพียงการแก้ไขคลาสอะแดปเตอร์ เวลานี้คลาสต้นทางไม่ได้รับการสืบทอด แต่คลาสต้นทางจะถูกจัดขึ้นเพื่อแก้ปัญหาความเข้ากันได้ ดูภาพ:
เพียงแก้ไขซอร์สโค้ดของคลาสอะแดปเตอร์:
Wrapper คลาสสาธารณะใช้การกำหนดเป้าหมาย {แหล่งที่มาส่วนตัว; wrapper สาธารณะ (แหล่งที่มา) {super (); this.source = แหล่งที่มา; } @Override โมฆะสาธารณะวิธีการ 2 () {system.out.println ("นี่คือวิธีการกำหนดเป้าหมาย!"); } @Override โมฆะสาธารณะวิธีการ 1 () {source.method1 (); -คลาสทดสอบ:
AdapterTest ระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {แหล่งที่มา = แหล่งที่มาใหม่ (); targetable target = wrapper ใหม่ (แหล่งที่มา); target.method1 (); target.method2 (); -เอาท์พุทเหมือนกับวิธีแรก แต่วิธีการปรับตัวนั้นแตกต่างกัน
โหมดอะแดปเตอร์ที่สามคือโหมดอะแดปเตอร์ของอินเตอร์เฟส อะแดปเตอร์ของอินเทอร์เฟซมีดังนี้: บางครั้งมีหลายวิธีนามธรรมในอินเทอร์เฟซที่เราเขียน เมื่อเราเขียนคลาสการใช้งานของอินเทอร์เฟซเราต้องใช้วิธีการทั้งหมดของอินเทอร์เฟซ เห็นได้ชัดว่าเป็นของเสียเพราะไม่จำเป็นต้องมีวิธีการทั้งหมดและบางครั้งก็จำเป็นต้องมีบางอย่างเท่านั้น เพื่อแก้ปัญหานี้เราได้แนะนำโหมดอะแดปเตอร์ของอินเทอร์เฟซ ด้วยความช่วยเหลือของคลาสนามธรรมคลาสนามธรรมจะใช้อินเทอร์เฟซและใช้วิธีการทั้งหมด เราไม่ได้จัดการกับอินเทอร์เฟซดั้งเดิมและติดต่อกับคลาสนามธรรมเท่านั้น ดังนั้นเราจึงเขียนคลาสสืบทอดคลาสนามธรรมและเขียนวิธีที่เราต้องการ ลองดูที่ไดอะแกรมคลาส:
นี่เป็นเรื่องง่ายที่จะเข้าใจ ในการพัฒนาจริงเรามักจะพบวิธีการที่กำหนดไว้มากเกินไปในอินเทอร์เฟซนี้ดังนั้นบางครั้งเราไม่ต้องการพวกเขาในบางคลาสการใช้งาน ดูรหัส:
อินเทอร์เฟซสาธารณะ sourceable {โมฆะสาธารณะวิธีการ 1 (); โมฆะสาธารณะวิธีการ 2 (); -บทคัดย่อคลาส Wrapper2:
Public Abstract Class Wrapper2 ใช้ sourceable {public void method1 () {} โมฆะสาธารณะ method2 () {}} sourcesub1 public class sourcesub1 ขยาย wrapper2 {โมฆะสาธารณะวิธีการ 1 () {system.out.println ( }} คลาสสาธารณะ sourcesub2 ขยาย wrapper2 {โมฆะสาธารณะวิธีการ 2 () {system.out.println ("subcable subceable sub2!"); }} wrappertest คลาสสาธารณะ {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {sourceable source1 = ใหม่ sourcesub1 (); Sourceable Source2 = ใหม่ sourcesub2 (); แหล่งที่มา 1.Method1 (); แหล่งที่มา 1.Method2 (); Source2.method1 (); Source2.method2 (); -เอาต์พุตทดสอบ:
Surceable Interface แรกของ Sub1!
Surceable Interface Sub2 ของ Sourceable!
มันบรรลุผลของเรา!
หลังจากพูดถึงเรื่องนี้ฉันจะสรุปสถานการณ์แอปพลิเคชันของอะแดปเตอร์สามโหมด:
โหมดอะแดปเตอร์คลาส: เมื่อคุณต้องการแปลงหนึ่งคลาสเป็นคลาสที่ตอบสนองอินเทอร์เฟซใหม่อื่นคุณสามารถใช้โหมดอะแดปเตอร์คลาสเพื่อสร้างคลาสใหม่สืบทอดคลาสดั้งเดิมและใช้อินเทอร์เฟซใหม่
โหมดอะแดปเตอร์วัตถุ: เมื่อคุณต้องการแปลงวัตถุเป็นวัตถุที่เป็นไปตามอินเตอร์เฟสใหม่อื่นคุณสามารถสร้างคลาส wrapper โดยถืออินสแตนซ์ของคลาสดั้งเดิมและในเมธอดคลาส wrapper เพียงเรียกใช้วิธีการอินสแตนซ์
โหมดอะแดปเตอร์ของอินเทอร์เฟซ: เมื่อคุณไม่ต้องการใช้วิธีการทั้งหมดในอินเทอร์เฟซคุณสามารถสร้าง wrapper คลาสนามธรรมเพื่อใช้วิธีการทั้งหมด เมื่อเราเขียนคลาสอื่น ๆ เพียงสืบทอดคลาสนามธรรม
7. มัณฑนากร
ตามชื่อที่แนะนำรูปแบบการตกแต่งคือการเพิ่มฟังก์ชั่นใหม่ให้กับวัตถุและเป็นแบบไดนามิกที่ต้องการวัตถุตกแต่งและวัตถุตกแต่งเพื่อใช้อินเทอร์เฟซเดียวกัน วัตถุตกแต่งมีตัวอย่างของวัตถุตกแต่ง แผนภาพความสัมพันธ์มีดังนี้:
คลาสต้นทางเป็นคลาสการตกแต่งและคลาสมัณฑนากรเป็นคลาสการตกแต่งที่สามารถเพิ่มฟังก์ชั่นบางอย่างในคลาสต้นทางแบบไดนามิก รหัสมีดังนี้:
อินเทอร์เฟซสาธารณะ sourceable {วิธีโมฆะสาธารณะ (); } แหล่งที่มาของคลาสสาธารณะใช้วิธี {@Override public public method () {system.out.println ("วิธีดั้งเดิม!"); }} นักตกแต่งระดับสาธารณะใช้แหล่งที่มา {แหล่งที่มาส่วนตัว มัณฑนากรสาธารณะ (แหล่งที่มาของแหล่งที่มา) {super (); this.source = แหล่งที่มา; } @Override โมฆะสาธารณะวิธีการ () {system.out.println ("ก่อนตกแต่ง!"); source.method (); System.out.println ("After Decorator!"); -คลาสทดสอบ:
Decoratortest ระดับสาธารณะ {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {แหล่งที่มาของแหล่งที่มา = ใหม่แหล่งที่มา (); sourceable obj = มัณฑนากรใหม่ (แหล่งที่มา); obj.method (); -เอาท์พุท:
ก่อนมัณฑนากร!
วิธีดั้งเดิม!
หลังจากตกแต่ง!
สถานการณ์การประยุกต์ใช้โหมดมัณฑนากร:
1. จำเป็นต้องขยายฟังก์ชั่นของคลาส
2. เพิ่มฟังก์ชั่นลงในวัตถุแบบไดนามิกและยังสามารถยกเลิกการยกเลิกได้ (การสืบทอดไม่สามารถทำได้ฟังก์ชั่นที่สืบทอดมานั้นคงที่และไม่สามารถเพิ่มและลบได้แบบไดนามิก)
ข้อเสีย: วัตถุที่คล้ายกันมากเกินไปไม่ใช่เรื่องง่ายที่จะแก้ไขปัญหา!
8. โหมดพร็อกซี (พร็อกซี)
ในความเป็นจริงแต่ละชื่อโมเดลระบุฟังก์ชั่นของโมเดล โมเดลพร็อกซีคือการเพิ่มคลาสเอเจนต์เพิ่มเติมเพื่อดำเนินการบางอย่างในวัตถุต้นฉบับ ตัวอย่างเช่นเมื่อเราเช่าบ้านเรากลับไปหาตัวแทน ทำไม เนื่องจากคุณไม่มีความเข้าใจที่ครอบคลุมเกี่ยวกับข้อมูลเกี่ยวกับบ้านในพื้นที่ฉันหวังว่าจะหาคนที่คุ้นเคยมากขึ้นเพื่อช่วยคุณทำ นี่คือความหมายของตัวแทนที่นี่ ตัวอย่างเช่นบางครั้งเมื่อเราฟ้องเราจำเป็นต้องจ้างทนายความเพราะทนายความมีความเชี่ยวชาญด้านกฎหมายและสามารถดำเนินการในนามของเราและแสดงความคิดของเรา มาดูแผนภาพความสัมพันธ์ก่อน:
ตามคำอธิบายข้างต้นโหมดพร็อกซีเข้าใจได้ง่ายขึ้น มาดูรหัส:
อินเทอร์เฟซสาธารณะ sourceable {วิธีโมฆะสาธารณะ (); } แหล่งที่มาของคลาสสาธารณะใช้วิธี {@Override public public method () {system.out.println ("วิธีดั้งเดิม!"); }} พร็อกซีคลาสสาธารณะใช้แหล่งที่มา {แหล่งที่มาส่วนตัว; พร็อกซีสาธารณะ () {super (); this.source = new Source (); } @Override โมฆะสาธารณะวิธีการ () {ก่อน (); source.method (); atfer (); } private void atfer() { System.out.println("after proxy!"); } private void before() { System.out.println("before proxy!"); -测试类:
public class ProxyTest { public static void main(String[] args) { Sourceable source = new Proxy(); source.method(); -输出:
before proxy!
the original method!
after proxy!
代理模式的应用场景:
如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:
1、修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。
2、就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。
使用代理模式,可以将功能划分的更加清晰,有助于后期维护!
9、外观模式(Facade)
外观模式是为了解决类与类之家的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口,看下类图:(我们以一个计算机的启动过程为例)
我们先看下实现类:
public class CPU { public void startup(){ System.out.println("cpu startup!"); } public void shutdown(){ System.out.println("cpu shutdown!"); } } public class Memory { public void startup(){ System.out.println("memory startup!"); } public void shutdown(){ System.out.println("memory shutdown!"); } } public class Disk { public void startup(){ System.out.println("disk startup!"); } public void shutdown(){ System.out.println("disk shutdown!"); } } public class Computer { private CPU cpu; private Memory memory; private Disk disk; public Computer(){ cpu = new CPU(); memory = new Memory(); disk = new Disk(); } public void startup(){ System.out.println("start the computer!"); cpu.startup(); memory.startup(); disk.startup(); System.out.println("start computer finished!"); } public void shutdown(){ System.out.println("begin to close the computer!"); cpu.shutdown(); memory.shutdown(); disk.shutdown(); System.out.println("computer closed!"); -User类如下:
public class User { public static void main(String[] args) { Computer computer = new Computer(); computer.startup(); computer.shutdown(); -输出:
start the computer!
cpu startup!
memory startup!
disk startup!
start computer finished!
begin to close the computer!
cpu shutdown!
memory shutdown!
disk shutdown!
computer closed!
如果我们没有Computer类,那么,CPU、Memory、Disk他们之间将会相互持有实例,产生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要看到的,有了Computer类,他们之间的关系被放在了Computer类里,这样就起到了解耦的作用,这,就是外观模式!
10、桥接模式(Bridge)
桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。我们来看看关系图:
实现代码:
先定义接口:
public interface Sourceable { public void method(); -分别定义两个实现类:
public class SourceSub1 implements Sourceable { @Override public void method() { System.out.println("this is the first sub!"); } } public class SourceSub2 implements Sourceable { @Override public void method() { System.out.println("this is the second sub!"); -定义一个桥,持有Sourceable的一个实例:
public abstract class Bridge { private Sourceable source; public void method(){ source.method(); } public Sourceable getSource() { return source; } public void setSource(Sourceable source) { this.source = source; } } public class MyBridge extends Bridge { public void method(){ getSource().method(); -测试类:
public class BridgeTest { public static void main(String[] args) { Bridge bridge = new MyBridge(); /*Calling the first object*/ Sourceable source1 = new SourceSub1(); bridge.setSource(source1); bridge.method(); /*Calling the second object*/ Sourceable source2 = new SourceSub2(); bridge.setSource(source2); bridge.method(); -output:
this is the first sub!
this is the second sub!
这样,就通过对Bridge类的调用,实现了对接口Sourceable的实现类SourceSub1和SourceSub2的调用。接下来我再画个图,大家就应该明白了,因为这个图是我们JDBC连接的原理,有数据库学习基础的,一结合就都懂了。
11、组合模式(Composite)
组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便,看看关系图:
直接来看代码:
public class TreeNode { private String name; private TreeNode parent; private Vector<TreeNode> children = new Vector<TreeNode>(); public TreeNode(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public TreeNode getParent() { return parent; } public void setParent(TreeNode parent) { this.parent = parent; } //Add child node public void add(TreeNode node){ child.add(node); } //Delete child node public void remove(TreeNode node){ child.remove(node); } //Get child node public Enumeration<TreeNode> getChildren(){ return children.elements(); } } public class Tree { TreeNode root = null; public Tree(String name) { root = new TreeNode(name); } public static void main(String[] args) { Tree tree = new Tree("A"); TreeNode nodeB = new TreeNode("B"); TreeNode nodeC = new TreeNode("C"); nodeB.add(nodeC); tree.root.add(nodeB); System.out.println("build the tree finished!"); -使用场景:将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。
12、享元模式(Flyweight)
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
FlyWeightFactory负责创建和管理享元单元,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象,FlyWeight是超类。一提到共享池,我们很容易联想到Java里面的JDBC连接池,想想每个连接的特点,我们不难总结出:适用于作共享的一些个对象,他们有一些共有的属性,就拿数据库连接池来说,url、driverClassName、username、password及dbname,这些属性对于每个连接来说都是一样的,所以就适合用享元模式来处理,建一个工厂类,将上述类似属性作为内部数据,其它的作为外部数据,在方法调用时,当做参数传进来,这样就节省了空间,减少了实例的数量。
看个例子:
看下数据库连接池的代码:
public class ConnectionPool { private Vector<Connection> pool; /*Public properties*/ private String url = "jdbc:mysql://localhost:3306/test"; private String username = "root"; private String password = "root"; private String driverClassName = "com.mysql.jdbc.Driver"; private int poolSize = 100; private static ConnectionPool instance = null; Connection conn = null; /*Construct method, do some initialization work*/ private ConnectionPool() { pool = new Vector<Connection>(poolSize); for (int i = 0; i < poolSize; i++) { try { Class.forName(driverClassName); conn = DriverManager.getConnection(url, username, password); pool.add(conn); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } } /* Return to connect to the connection pool*/ public synchronized void release() { pool.add(conn); } /* Return a database connection in the connection pool*/ public synchronized Connection getConnection() { if (pool.size() > 0) { Connection conn = pool.get(0); pool.remove(conn); return conn; } else { return null; -通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据库重新创建的开销,提升了系统的性能!本章讲解了7种结构型模式,因为篇幅的问题,剩下的11种行为型模式,
本章是关于设计模式的最后一讲,会讲到第三种设计模式――行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。这段时间一直在写关于设计模式的东西,终于写到一半了,写博文是个很费时间的东西,因为我得为读者负责,不论是图还是代码还是表述,都希望能尽量写清楚,以便读者理解,我想不论是我还是读者,都希望看到高质量的博文出来,从我本人出发,我会一直坚持下去,不断更新,源源动力来自于读者朋友们的不断支持,我会尽自己的努力,写好每一篇文章!希望大家能不断给出意见和建议,共同打造完美的博文!
先来张图,看看这11中模式的关系:
第一类:通过父类与子类的关系进行实现。第二类:两个类之间。第三类:类的状态。第四类:通过中间类
13、策略模式(strategy)
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数,关系图如下:
图中ICalculator提供同意的方法,
AbstractCalculator是辅助类,提供辅助方法,接下来,依次实现下每个类:
首先统一接口:
public interface ICalculator { public int calculate(String exp); -辅助类:
public abstract class AbstractCalculator { public int[] split(String exp,String opt){ String array[] = exp.split(opt); int arrayInt[] = new int[2]; arrayInt[0] = Integer.parseInt(array[0]); arrayInt[1] = Integer.parseInt(array[1]); return arrayInt; -三个实现类:
public class Plus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"//+"); return arrayInt[0]+arrayInt[1]; - public class Minus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"-"); return arrayInt[0]-arrayInt[1]; - public class Multiply extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"//*"); return arrayInt[0]*arrayInt[1]; -简单的测试类:
public class StrategyTest { public static void main(String[] args) { String exp = "2+8"; ICalculator cal = new Plus(); int result = cal.calculate(exp); System.out.println(result); -输出:10
策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。
14、模板方法模式(Template Method)
解释一下模板方法模式,就是指:一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用,先看个关系图:
就是在AbstractCalculator类中定义一个主方法calculate,calculate()调用spilt()等,Plus和Minus分别继承AbstractCalculator类,通过对AbstractCalculator的调用实现对子类的调用,看下面的例子:
public abstract class AbstractCalculator { /*Main method, implement calls to other methods of this class*/ public final int calculate(String exp,String opt){ int array[] = split(exp,opt); return calculate(array[0],array[1]); } /* Method rewritten by subclass*/ abstract public int calculate(int num1,int num2); public int[] split(String exp,String opt){ String array[] = exp.split(opt); int arrayInt[] = new int[2]; arrayInt[0] = Integer.parseInt(array[0]); arrayInt[1] = Integer.parseInt(array[1]); return arrayInt; } } public class Plus extends AbstractCalculator { @Override public int calculate(int num1,int num2) { return num1 + num2; -测试类:
public class StrategyTest { public static void main(String[] args) { String exp = "8+8"; AbstractCalculator cal = new Plus(); int result = cal.calculate(exp, "//+"); System.out.println(result); -我跟踪下这个小程序的执行过程:首先将exp和"//+"做参数,调用AbstractCalculator类里的calculate(String,String)方法,在calculate(String,String)里调用同类的split(),之后再调用calculate(int ,int)方法,从这个方法进入到子类中,执行完return num1 + num2后,将值返回到AbstractCalculator类,赋给result,打印出来。正好验证了我们开头的思路。
15、观察者模式(Observer)
包括这个模式在内的接下来的四个模式,都是类和类之间的关系,不涉及到继承,学的时候应该记得归纳,记得本文最开始的那个图。观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。先来看看关系图:
我解释下这些类的作用:MySubject类就是我们的主对象,Observer1和Observer2是依赖于MySubject的对象,当MySubject变化时,Observer1和Observer2必然变化。AbstractSubject类中定义着需要监控的对象列表,可以对其进行修改:增加或删除被监控对象,且当MySubject变化时,负责通知在列表内存在的对象。我们看实现代码:
一个Observer接口:
public interface Observer { public void update(); -两个实现类:
public class Observer1 implements Observer { @Override public void update() { System.out.println("observer1 has received!"); } } public class Observer2 implements Observer { @Override public void update() { System.out.println("observer2 has received!"); -Subject接口及实现类:
public interface Subject { /*Add observer*/ public void add(Observer observer); /*Delete observer*/ public void del(Observer observer); /*Notify all observers*/ public void notifyObservers(); /*Own operation*/ public void operation(); } public abstract class AbstractSubject implements Subject { private Vector<Observer> vector = new Vector<Observer>(); @Override public void add(Observer observer) { vector.add(observer); } @Override public void del(Observer observer) { vector.remove(observer); } @Override public void notifyObservers() { Enumeration<Observer> enumo = vector.elements(); while(enumo.hasMoreElements()){ enumo.nextElement().update(); } } } public class MySubject extends AbstractSubject { @Override public void operation() { System.out.println("update self!"); notifyObservers(); -测试类:
public class ObserverTest { public static void main(String[] args) { Subject sub = new MySubject(); sub.add(new Observer1()); sub.add(new Observer2()); sub.operation(); -输出:
update self!
observer1 has received!
observer2 has received!
这些东西,其实不难,只是有些抽象,不太容易整体理解,建议读者:根据关系图,新建项目,自己写代码(或者参考我的代码),按照总体思路走一遍,这样才能体会它的思想,理解起来容易!
16、迭代子模式(Iterator)
顾名思义,迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟悉的话,理解本模式会十分轻松。这句话包含两层意思:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。我们看下关系图:
这个思路和我们常用的一模一样,MyCollection中定义了集合的一些操作,MyIterator中定义了一系列迭代操作,且持有Collection实例,我们来看看实现代码:
两个接口:
public interface Collection { public Iterator iterator(); /*get collection elements*/ public Object get(int i); /*get collection size*/ public int size(); } public interface Iterator { //Move forward public Object previous(); //Move backward public Object next(); public boolean hasNext(); //Get first element public Object first(); -两个实现:
public class MyCollection implements Collection { public String string[] = {"A","B","C","D","E"}; @Override public Iterator iterator() { return new MyIterator(this); } @Override public Object get(int i) { return string[i]; } @Override public int size() { return string.length; } } public class MyIterator implements Iterator { private Collection collection; private int pos = -1; public MyIterator(Collection collection){ this.collection = collection; } @Override public Object previous() { if(pos > 0){ pos--; } return collection.get(pos); } @Override public Object next() { if(pos<collection.size()-1){ pos++; } return collection.get(pos); } @Override public boolean hasNext() { if(pos<collection.size()-1){ return true; }else{ return false; } } @Override public Object first() { pos = 0; return collection.get(pos); -测试类:
public class Test { public static void main(String[] args) { Collection collection = new MyCollection(); Iterator it = collection.iterator(); while(it.hasNext()){ System.out.println(it.next()); -输出:ABCDE
此处我们貌似模拟了一个集合类的过程,感觉是不是很爽?其实JDK中各个类也都是这些基本的东西,加一些设计模式,再加一些优化放到一起的,只要我们把这些东西学会了,掌握好了,我们也可以写出自己的集合类,甚至框架!
17、责任链模式(Chain of Responsibility)
接下来我们将要谈谈责任链模式,有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。先看看关系图:
Abstracthandler类提供了get和set方法,方便MyHandle类设置和修改引用对象,MyHandle类是核心,实例化后生成一系列相互持有的对象,构成一条链。
public interface Handler { public void operator(); - public abstract class AbstractHandler { private Handler handler; public Handler getHandler() { return handler; } public void setHandler(Handler handler) { this.handler = handler; - public class MyHandler extends AbstractHandler implements Handler { private String name; public MyHandler(String name) { this.name = name; } @Override public void operator() { System.out.println(name+"deal!"); if(getHandler()!=null){ getHandler().operator(); - public class Test { public static void main(String[] args) { MyHandler h1 = new MyHandler("h1"); MyHandler h2 = new MyHandler("h2"); MyHandler h3 = new MyHandler("h3"); h1.setHandler(h2); h2.setHandler(h3); h1.operator(); -输出:
h1deal!
h2deal!
h3deal!
此处强调一点就是,链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
18、命令模式(Command)
命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。我们看看关系图:
Invoker是调用者(司令员),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象,看实现代码:
public interface Command { public void exe(); } public class MyCommand implements Command { private Receiver receiver; public MyCommand(Receiver receiver) { this.receiver = receiver; } @Override public void exe() { receiver.action(); } } public class Receiver { public void action(){ System.out.println("command received!"); } } public class Invoker { private Command command; public Invoker(Command command) { this.command = command; } public void action(){ command.exe(); } } public class Test { public static void main(String[] args) { Receiver receiver = new Receiver(); Command cmd = new MyCommand(receiver); Invoker invoker = new Invoker(cmd); invoker.action(); -输出:command received!
这个很哈理解,命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开,熟悉Struts的同学应该知道,Struts其实就是一种将请求和呈现分离的技术,其中必然涉及命令模式的思想!
其实每个设计模式都是很重要的一种思想,看上去很熟,其实是因为我们在学到的东西中都有涉及,尽管有时我们并不知道,其实在Java本身的设计之中处处都有体现,像AWT、JDBC、集合类、IO管道或者是Web框架,里面设计模式无处不在。因为我们篇幅有限,很难讲每一个设计模式都讲的很详细,不过我会尽我所能,尽量在有限的空间和篇幅内,把意思写清楚了,更好让大家明白。本章不出意外的话,应该是设计模式最后一讲了,首先还是上一下上篇开头的那个图:
本章讲讲第三类和第四类。
19、备忘录模式(Memento)
主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象,个人觉得叫备份模式更形象些,通俗的讲下:假设有原始类A,A中有各种属性,A可以决定需要备份的属性,备忘录类B是用来存储A的一些内部状态,类C呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。做个图来分析一下:
Original类是原始类,里面有需要保存的属性value及创建一个备忘录类,用来保存value值。Memento类是备忘录类,Storage类是存储备忘录的类,持有Memento类的实例,该模式很好理解。直接看源码:
public class Original { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public Original(String value) { this.value = value; } public Memento createMemento(){ return new Memento(value); } public void restoreMemento(Memento memento){ this.value = memento.getValue(); } } public class Memento { private String value; public Memento(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } public class Storage { private Memento memento; public Storage(Memento memento) { this.memento = memento; } public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; -测试类:
public class Test { public static void main(String[] args) { // Create original class Original origin = new Original("egg"); // Create memo Storage storage = new Storage(origi.createMemento()); // Modify the status of the original class System.out.println("Initialization status is: " + origini.getValue()); origini.setValue("niu"); System.out.println("Modified status is: " + origini.getValue()); // Reply to the original class's state origini.restoreMemento(storage.getMemento()); System.out.println("The state after recovery is: " + origini.getValue()); -输出:
初始化状态为:egg
修改后的状态为:niu
恢复后的状态为:egg
简单描述下:新建原始类时,value被初始化为egg,后经过修改,将value的值置为niu,最后倒数第二行进行恢复状态,结果成功恢复了。其实我觉得这个模式叫“备份-恢复”模式最形象。
20、状态模式(State)
核心思想就是:当对象的状态改变时,同时改变其行为,很好理解!就拿QQ来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。看图:
State类是个状态类,Context类可以实现切换,我们来看看代码:
package com.xtfggef.dp.state; /** * Core class of the state class* 2012-12-1 * @author erqing * */ public class State { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public void method1(){ System.out.println("execute the first opt!"); } public void method2(){ System.out.println("execute the second opt!"); } } package com.xtfggef.dp.state; /** * Switching class for state mode2012-12-1 * @author erqing * */ public class Context { private State state; public Context(State state) { this.state = state; } public State getState() { return state; } public void setState(State state) { this.state = state; } public void method() { if (state.getValue().equals("state1")) { state.method1(); } else if (state.getValue().equals("state2")) { state.method2(); -测试类:
public class Test { public static void main(String[] args) { State state = new State(); Context context = new Context(state); //Set the first state.setValue("state1"); context.method(); //Set the second state.setValue("state2"); context.method(); -输出:
execute the first opt!
execute the second opt!
根据这个特性,状态模式在日常开发中用的挺多的,尤其是做网站的时候,我们有时希望根据对象的某一属性,区别开他们的一些功能,比如说简单的权限控制等。
21、访问者模式(Visitor)
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。―― From 百科
简单来说,访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。简单关系图:
来看看原码:一个Visitor类,存放要访问的对象,
public interface Visitor { public void visit(Subject sub); } public class MyVisitor implements Visitor { @Override public void visit(Subject sub) { System.out.println("visit the subject:"+sub.getSubject()); -Subject类,accept方法,接受将要访问它的对象,getSubject()获取将要被访问的属性,
public interface Subject { public void accept(Visitor visitor); public String getSubject(); } public class MySubject implements Subject { @Override public void accept(Visitor visitor) { visitor.visit(this); } @Override public String getSubject() { return "love"; - ทดสอบ:
public class Test { public static void main(String[] args) { Visitor visitor = new MyVisitor(); Subject sub = new MySubject(); sub.accept(visitor); -输出:visit the subject:love
该模式适用场景:如果我们想为一个现有的类增加新功能,不得不考虑几个事情:1、新功能会不会与现有功能出现兼容性问题?2、以后会不会再需要添加?3、如果类不允许修改代码怎么办?面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法解耦,
22、中介者模式(Mediator)
中介者模式也是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。如果使用中介者模式,只需关心和Mediator类的关系,具体类类之间的关系及调度交给Mediator就行,这有点像spring容器的作用。先看看图:
User类统一接口,User1和User2分别是不同的对象,二者之间有关联,如果不采用中介者模式,则需要二者相互持有引用,这样二者的耦合度很高,为了解耦,引入了Mediator类,提供统一接口,MyMediator为其实现类,里面持有User1和User2的实例,用来实现对User1和User2的控制。这样User1和User2两个对象相互独立,他们只需要保持好和Mediator之间的关系就行,剩下的全由MyMediator类来维护!基本实现:
public interface Mediator { public void createMediator(); public void workAll(); } public class MyMediator implements Mediator { private User user1; private User user2; public User getUser1() { return user1; } public User getUser2() { return user2; } @Override public void createMediator() { user1 = new User1(this); user2 = new User2(this); } @Override public void workAll() { user1.work(); user2.work(); } } public abstract class User { private Mediator mediator; public Mediator getMediator(){ return mediator; } public User(Mediator mediator) { this.mediator = mediator; } public abstract void work(); } public class User1 extends User { public User1(Mediator mediator){ super(mediator); } @Override public void work() { System.out.println("user1 exe!"); } } public class User2 extends User { public User2(Mediator mediator){ super(mediator); } @Override public void work() { System.out.println("user2 exe!"); -测试类:
public class Test { public static void main(String[] args) { Mediator mediator = new MyMediator(); mediator.createMediator(); mediator.workAll(); -输出:
user1 exe!
user2 exe!
23、解释器模式(Interpreter)
解释器模式是我们暂时的最后一讲,一般主要应用在OOP开发中的编译器的开发中,所以适用面比较窄。
The Context class is a context environment class. Plus and Minus are implementations used for calculations respectively. รหัสมีดังนี้:
public interface Expression { public int interpret(Context context); } public class Plus implements Expression { @Override public int interpret(Context context) { return context.getNum1()+context.getNum2(); } } public class Minus implements Expression { @Override public int interpret(Context context) { return context.getNum1()-context.getNum2(); } } public class Context { private int num1; private int num2; public Context(int num1, int num2) { this.num1 = num1; this.num2 = num2; } public int getNum1() { return num1; } public void setNum1(int num1) { this.num1 = num1; } public int getNum2() { return num2; } public void setNum2(int num2) { this.num2 = num2; } } public class Test { public static void main(String[] args) { // Calculate the value of 9+2-8 int result = new Minus().interpret((new Context(new Plus() .interpret(new Context(9, 2)), 8))); System.out.println(result); -最后输出正确的结果:3。
基本就这样,解释器模式用来做各种各样的解释器,如正则表达式等的解释器等等!
原文链接:http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น