ทำให้การพึ่งพาการฉีดระหว่างแพ็คเกจเป็นงานเล็กๆ น้อยๆ ด้วยการตั้งค่านี้
TL; DR: การใช้
injectable
,injectable_generator
และget_it
เราสร้างอินสแตนซ์get_it
และส่งต่อผ่านแพ็คเกจต่างๆ และให้แต่ละแพ็คเกจใช้การกำหนดค่ากับอินสแตนซ์get_it
นี้
บทนำ
โดยปกติ การตั้งค่าการฉีดการขึ้นต่อกันเกี่ยวข้องกับการตั้งค่าที่มีลักษณะดังต่อไปนี้:
เราดึงข้อมูลอินสแตนซ์ get_it
และใช้เวทย์มนตร์ในการสร้างโค้ดแฟนซีเพื่อทำให้สิ่งต่าง ๆ เกิดขึ้นในเบื้องหลัง และเราใส่คำอธิบายประกอบคลาสของเราด้วยสิ่งต่าง ๆ เช่น @Injectable
หรือ @LazySingleton
เพื่อให้สามารถใช้งานได้ผ่าน di
ทั้งหมดนี้เป็นวิธีที่สะดวกและง่ายดายในการทำ di ของเรา ดีกว่าต้องลงทะเบียนทุกอย่างด้วยตนเองตลอดเวลา
แต่เรากำลังพูดถึงแพ็คเกจเดียวเท่านั้น โดยปกติแล้ว นั่นคือแอป Flutter ของคุณ เป็นต้น
จะเป็นอย่างไรหากคุณมีโปรเจ็กต์ที่คุณสามารถควบคุมแพ็คเกจหลาย ๆ แพคเกจได้ และยังต้องการใช้โซลูชัน di ง่าย ๆ นี้
คุณอาจต้องการให้โครงสร้างแพ็คเกจดังกล่าวคลายการพึ่งพาในโครงการของคุณหรือใช้สถาปัตยกรรมบางอย่าง (คาดเดา ;))
ฉันมีวิธีแก้ปัญหาสำหรับคุณแล้ว!
การตั้งค่าโฟลว์การพึ่งพา
ก่อนที่เราจะเริ่มต้น ฉันต้องการชี้แจงว่าผลิตภัณฑ์ขั้นสุดท้ายจะมีลักษณะอย่างไรด้วยผังงานต่อไปนี้:
หากคุณลองคิดดู สิ่งนี้ก็สมเหตุสมผลดี เพราะ App
ขึ้นอยู่กับแพ็คเกจที่เหลือทั้งหมด
มันง่ายมากจริงๆ
วิธีการใช้งาน
หมายเหตุ: สำหรับตัวอย่างที่ให้มา การตั้งค่าทั้งหมดนี้ใช้ความพยายามมากเกินไป แต่ก็ช่วยให้แนวคิดนี้ข้ามไปได้
เริ่มต้นด้วยการกำหนดแอปพลิเคชันง่ายๆ ตัวอย่างทั่วไปของฉันมักจะเป็นตัวนับ ดังนั้นเรามาเริ่มกันเลย
แอปพลิเคชันของเราจะเป็นตัวนับพื้นฐานที่สามารถเพิ่มได้ โดยค่าจะถูกเก็บไว้ในหน่วยความจำ โชคดีที่ Flutter มีส่วนสำคัญในเรื่องนี้อยู่แล้วเมื่อเราสร้างโปรเจ็กต์ใหม่
การสมัครจะแบ่งออกเป็น 4 แพ็คเกจ:
- แพ็คเกจ
counter
ที่ส่งออกอินเทอร์เฟซCounter
และอินเทอร์เฟซCounterStorage
และการนำไปใช้สำหรับอินเทอร์เฟซCounter
ชื่อCounterImpl
- แพ็คเกจ
counter_storage
ที่ใช้อินเทอร์เฟซการจัดเก็บข้อมูลจากแพ็คเกจcounter
- แพ็คเกจ
logger
ที่ส่งออกอินเทอร์เฟซLogger
และกำหนดการใช้งานLoggerImpl
- แพ็คเกจ
app
ที่สร้างโดย Flutter
นี่คือกราฟที่แสดงให้เห็นว่าแพ็คเกจเหล่านี้พึ่งพาซึ่งกันและกันอย่างไร:
เลเยอร์ app
คว้าตัวนับและการนำไปใช้งานจาก counter
และยังใช้ Logger
ของแพ็คเกจ logger
อีกด้วย
เหตุผลที่ counter_storage
ขึ้นอยู่กับ counter
ก็เพราะว่าต้องการอินเทอร์เฟซ CounterStorage
เพื่อให้ counter_storage
สามารถดำเนินการ CounterStorageImpl
ได้
ในทางปฏิบัติ การดำเนินการโค้ดจะเป็นดังนี้:
การฉีดพึ่งพาคือวิธีที่เราทำสิ่งนี้ ดังนั้นนี่คือทีละขั้นตอน:
ขั้นตอนที่ 1: ตั้งค่าแพ็คเกจ
เพื่อความสอดคล้อง เราจะดำเนินการใช้งาน DI เหมือนในไดอะแกรมด้านบนทุกประการ คุณสามารถดูการดำเนินการขั้นตอนนี้เสร็จสิ้นได้ "ที่นี่"
สำหรับการสร้างแพ็คเกจ คุณสามารถใช้คำสั่งต่อไปนี้ (ตามลำดับ):
mkdir dart-easy-di cd dart-easy-di # 1st package dart create counter # 2nd package dart create counter_storage # 3rd package dart create logger # Finally, the app package flutter create app
(หมายเหตุ: คุณสามารถคัดลอกและวางบล็อกคำสั่งทั้งหมดนี้แล้ววางลงในเทอร์มินัลแล้วรันได้)
เราจะเพิ่มคลาสที่จำเป็นลงในแพ็คเกจของเราเพื่อเสร็จสิ้นขั้นตอนนี้
ในแพ็คเกจ counter
ให้เพิ่มคลาสต่อไปนี้:
CounterImpl
เป็นการดำเนินการพื้นฐานของ Counter
ที่ใช้ CounterStorage
เป็นการขึ้นต่อกัน
เราจำเป็นต้องส่งออกอินเทอร์เฟซ Counter
และ CounterStorage
ดังนั้นเราจึงดำเนินการดังกล่าวที่นี่:
การกำหนดค่าแพ็คเกจแรกเสร็จสิ้นแล้ว! กันต่อไป. มากำหนดค่าแพ็คเกจ CounterStorage
กัน
เพิ่มไฟล์ต่อไปนี้:
เราจำเป็นต้องเพิ่มการพึ่งพา counter
เพื่อให้เราสามารถนำเข้าได้
นั่นทำให้แพ็คเกจ counter_storage
ของเราเสร็จสิ้นในตอนนี้ คุณจะสังเกตเห็นว่าเราไม่ได้ส่งออกอะไรเลย แล้วในที่สุดเราจะเข้าถึงมันได้อย่างไร? ด้วยเวทย์มนตร์ DI แน่นอน!
เราจะไปที่ขั้นตอนต่อไปนี้ มาดูแลlogger
กันต่อไป เพิ่มไฟล์ต่อไปนี้:
สุดท้าย เราส่งออกอินเทอร์เฟซ Logger
:
เอาล่ะ! ตอนนี้เหลือเพียงแพ็คเกจ app
สำหรับขั้นตอนนี้ เราจะเพิ่มการอ้างอิงใน pubspec
:
ขั้นตอนที่ 2: การตั้งค่า DI ด้วย GetIt และ Injectable
คุณสามารถดูการดำเนินการขั้นตอนนี้เสร็จสิ้นได้ "ที่นี่"
แนวคิดที่นี่คือการส่งออกวิธีการที่ใช้อินสแตนซ์ get_it
และตั้งค่าทุกสิ่งที่ต้องการ
อันดับแรก แพ็คเกจทั้งหมดของเราต้องมีการพึ่งพากันเล็กน้อย มาเพิ่มด้วยสคริปต์แฟนซีด้านล่าง:
# foreach dir for d in */; do cd "$d" echo "$d" dart pub add get_it injectable dart pub add --dev injectable_generator build_runner echo "\n" cd .. done
ไม่ต้องกังวล นี่จะไม่แฮ็กคอมพิวเตอร์ของคุณ (อาจจะ) มันจะติดตั้งการขึ้นต่อกันที่จำเป็นสำหรับโซลูชัน DI ของเรากับแต่ละแพ็คเกจของเรา ซึ่งได้แก่ get_it
, injectable
, injectable_generator
และ build_runner
จากนั้น สำหรับแต่ละแพ็คเกจ counter
, counter_storage
และ logger
เราจะเพิ่มสิ่งต่อไปนี้ (ไม่ต้องสนใจข้อผิดพลาดจากโปรแกรมแก้ไขของคุณในตอนนี้):
จากนั้น เราจะส่งออกฟังก์ชัน configureDependencies
นี้ในแต่ละ counter/lib/counter.dart
, counter_storage/lib/counter_storage.dart
และ logger/lib/logger.dart
:
ในที่สุด เราก็สร้างทุกอย่างมารวมกันใน app
:
ในบรรทัดที่ 21 - 23 คุณจะเห็นว่าเรากำลังเรียกใช้ฟังก์ชัน configureDependencies
ของแพ็คเกจแต่ละฟังก์ชันที่เราเพิ่งส่งออก เรากำลังสร้างนามแฝงของการนำเข้าด้วยคำหลัก as
เพื่อหลีกเลี่ยงความขัดแย้งในการตั้งชื่อ
ขั้นตอนสุดท้ายคือเราเรียกฟังก์ชัน app
's configureDependencies
ใน app/lib/main.dart
:
เราทำการตั้งค่าที่ต้องการไปแล้ว 95% แต่ยังคงมีข้อผิดพลาดที่น่ารำคาญในไฟล์ di.dart
หากต้องการกำจัดสิ่งเหล่านั้น ให้เรียกใช้สคริปต์ต่อไปนี้:
# foreach dir for d in */; do cd "$d" echo "$d" dart run build_runner build echo "\n" cd .. done
คุณจะต้องเรียกใช้สคริปต์นี้ทุกครั้งที่คุณอัปเดตการอ้างอิงของคุณไม่ว่าด้วยวิธีใดก็ตาม ตัวอย่างนี้คือการเพิ่มแท็ก @Injectable
การเพิ่มการพึ่งพาใหม่ให้กับคลาส ฯลฯ
ฉันจะแสดงให้คุณเห็นว่าคุณสามารถเพิ่มสคริปต์นี้เป็นฟังก์ชันที่ใช้งานง่ายใน bash หรือ zsh config ของคุณได้อย่างไร ที่นี่
การเรียกใช้สคริปต์นี้จะทำให้ไฟล์ใหม่ปรากฏในไดเรกทอรี di/
ของแต่ละแพ็คเกจที่เรียกว่า di.config.dart
ไฟล์นี้มีความสัมพันธ์ที่แท้จริงระหว่างคลาสที่อนุญาต DI ข้อผิดพลาดควรจะหายไปแล้ว แต่เรายังไม่เสร็จสิ้น
คุณควรละเว้นไฟล์นี้ใน .gitignore
ของคุณ ดังนั้นอย่าลืมเพิ่ม **/di/*.config.dart
หรืออะไรสักอย่างในบรรทัดเหล่านั้น ฉันสร้างไฟล์ .gitignore
ขึ้นมาหนึ่งไฟล์ภายใต้ dart-easy-di/
เป็นการส่วนตัว (ซึ่งมีแพ็คเกจทั้งหมดของเรา) และเพิ่มคำสั่งละเว้นที่นั่นเท่านั้น
ดังนั้นสิ่งเดียวที่เหลือ ณ จุดนี้คือการใส่คำอธิบายประกอบไฟล์ของเรา
ขั้นตอนที่ 3: การอธิบายประกอบด้วย Injectable
คุณสามารถดูการดำเนินการที่เสร็จสิ้นแล้วของสิ่งนี้ได้ "ที่นี่"
เราใส่คำอธิบายประกอบการใช้งานของเราด้วยแท็กที่เกี่ยวข้อง ซึ่งรวมถึง CounterImpl
, CounterStorageImpl
และ LoggerImpl
:
ด้วยเหตุนี้ เราได้เชื่อมโยงการใช้งานของเรากับอินเทอร์เฟซของพวกเขา เราไม่จำเป็นต้องส่งออกการใช้งานด้วยซ้ำ!
เราจำเป็นต้องเรียกใช้สคริปต์ build_runner
อีกครั้งเนื่องจากเราเปลี่ยนการอ้างอิง:
# foreach dir for d in */; do cd "$d" echo "$d" dart run build_runner build echo "\n" cd .. done
หมายเหตุ: คุณอาจได้รับคำเตือนขณะเรียกใช้สคริปต์นี้เกี่ยวกับการขึ้นต่อกันที่ไม่ได้ลงทะเบียนบางอย่าง คุณสามารถเพิกเฉยต่อสิ่งเหล่านั้นได้อย่างปลอดภัย
ตอนนี้เราเสร็จสิ้นการเตรียมการอย่างเป็นทางการแล้ว เราสามารถใช้สิ่งเหล่านี้ทั้งหมดใน app
ของเราได้
การใช้แพ็คเกจของเราในแอพ Flutter
คุณสามารถดูการใช้งานที่เสร็จสิ้นแล้วได้ "ที่นี่"
ใน app/lib/main.dart
เราทำสิ่งต่อไปนี้:
บรรทัดที่ 3 — 11 เป็นที่ที่เราเพิ่มการนำเข้าสำหรับแพ็คเกจของเรา คุณจะสังเกตเห็นว่าการนำเข้าไฟล์ di.dart
มีนามแฝงว่า app
ทั้งนี้เพื่อหลีกเลี่ยงความขัดแย้งของเนมสเปซ
ในบรรทัด 16 -› 18 เราใช้อินสแตนซ์ DI ของเราเพื่อรับตัวนับและตัวบันทึก จากนั้นเราจะใช้บรรทัดที่ 22, 23 และ 41 ลองดูว่าตัวบันทึกใช้งานได้จริงหรือไม่!
หากคุณพบข้อผิดพลาดใดๆ กับอินสแตนซ์ get_it
การเรียกใช้สคริปต์ build_runner
อีกครั้งอาจช่วยแก้ปัญหาได้
สถานการณ์ที่สมจริงยิ่งขึ้น
คุณสามารถดูการดำเนินการขั้นสุดท้ายของขั้นตอนนี้ได้ "ที่นี่"
แม้ว่าจะใช้งานได้ แต่ก็ไม่ถูกต้องทั้งหมด เราได้ทำให้แพ็คเกจ app
ขึ้นอยู่กับ counter_storage
โดยตรง แม้ว่า app
จะไม่โต้ตอบกับแพ็คเกจเลย (นอกเหนือจากการเรียกแพ็คเกจ configureDependencies
เราจะทำอย่างไรเพื่อที่เราจะไม่ทำลายกราฟการพึ่งพาของเราในขณะที่ยังคงรักษา DI ในแบบที่เป็นอยู่
คำตอบอยู่ที่เลเยอร์ di
นี่คือแพ็คเกจที่มีวัตถุประสงค์เพียงอย่างเดียวในการรับอินสแตนซ์ get_it
และส่งต่อไปยังแพ็คเกจทั้งหมดที่ต้องการ
ด้วยวิธีนี้ app
เลเยอร์ของคุณจะขึ้นอยู่กับ di
เท่านั้น และ di
ขึ้นอยู่กับเลเยอร์อื่นๆ และกระจายโซลูชัน DI ของคุณ
แน่นอนว่า app
จะยังคงขึ้นอยู่กับ counter
และ logger
เนื่องจากต้องการอินเทอร์เฟซที่ส่งออก
หากต้องการใช้ di like ของเราในแผนภาพด้านบน ให้ทำดังต่อไปนี้ (เปิดเทอร์มินัลใน dart-easy-di
:
# Create the package dart create di # Install its dependencies cd di dart pub add get_it injectable dart pub add --dev injectable_generator build_runner
เพิ่มการอ้างอิงต่อไปนี้ใน pubspec.yaml
และตอนนี้เราสามารถลบการพึ่งพา counter_storage
ออกจาก app/pubspec.yaml
ได้แล้ว
จากนั้น เราตั้งค่าแพ็กเกจ di
ด้วยไฟล์ di.dart
ของตัวเอง ซึ่งมีลักษณะดังนี้:
นี่ดูคล้ายกับสิ่งที่เราทำใน app/lib/di/di.dart
ใช่ไหม ขั้นตอนต่อไปคือการส่งออกฟังก์ชัน configureDependencies
นี้จาก di
นำเข้าสู่ app
แล้วจึงเรียกฟังก์ชันดังกล่าว นี่คือขั้นตอนในการทำเช่นนั้น:
ส่งออกจากแพ็คเกจ di
:
นำเข้าสู่ app
(คุณอาจต้องเรียกใช้ dart pub get
หลังจากนั้น)
อัปเดต app/lib/di/di.dart
และสุดท้าย ให้รันสคริปต์ build_runner
อีกครั้ง
# foreach dir for d in */; do cd "$d" echo "$d" dart run build_runner build echo "\n" cd .. done
หากคุณทำทุกอย่างถูกต้องแล้ว (และแน่นอนว่าคุณเป็นคนฉลาดอย่างไม่น่าเชื่อ) แสดงว่าคุณใช้งาน Interpackage Dependency Injection แล้ว
โบนัส
นี่คือคำสั่งในการตั้งค่าฟังก์ชัน build_runner
ในการกำหนดค่าเชลล์ของคุณ มันจะทำให้การรันสคริปต์ build_runner
ง่ายขึ้น
echo " function build_all { for d in */; do cd "$d" echo "$d" dart run build_runner build echo "\n" cd .. done } " >> ~/.zshrc
แทนที่ .zshrc
ด้วยไฟล์กำหนดค่าเชลล์ของคุณเอง หลังจากที่คุณเรียกใช้สิ่งนี้ในเทอร์มินัลของคุณ ให้รีสตาร์ทเทอร์มินัลของคุณหรือรัน source ~/.zshrc
ตอนนี้คุณสามารถพิมพ์ build_all
ในเทอร์มินัลของคุณเมื่อคุณอยู่ในไดเร็กทอรี dart-easy-di
และมันจะรันขั้นตอนการสร้างสำหรับแพ็คเกจทั้งหมดให้คุณ
คุณคิดอย่างไรเกี่ยวกับโซลูชันนี้
ตรวจสอบ repo ของฉัน เพื่อการใช้งานที่สมบูรณ์
อ้างอิง
- การอ้างอิงแพ็คเกจ Dart อย่างเป็นทางการ
- แพ็คเกจ GetIt
- แพ็คเกจ Injectable และ Injectable Generator