ทำให้การพึ่งพาการฉีดระหว่างแพ็คเกจเป็นงานเล็กๆ น้อยๆ ด้วยการตั้งค่านี้

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 ของฉัน เพื่อการใช้งานที่สมบูรณ์

อ้างอิง