खोज…


दृश्य नियंत्रकों के साथ निर्भरता इंजेक्शन

निर्भर इंजेक्शन इंजेक्शन

एक एप्लिकेशन कई वस्तुओं से बना होता है जो एक दूसरे के साथ सहयोग करते हैं। ऑब्जेक्ट आमतौर पर कुछ कार्य करने के लिए अन्य वस्तुओं पर निर्भर करते हैं। जब कोई वस्तु अपनी स्वयं की निर्भरता को संदर्भित करने के लिए जिम्मेदार होती है, तो यह एक उच्च युग्मित, कठिन-से-परीक्षण और हार्ड-टू-चेंज कोड की ओर जाता है।

निर्भरता इंजेक्शन एक सॉफ्टवेयर डिजाइन पैटर्न है जो निर्भरता को हल करने के लिए नियंत्रण के व्युत्क्रम को लागू करता है। एक इंजेक्शन एक निर्भर वस्तु के लिए निर्भरता से गुजर रहा है जो इसका उपयोग करेगा। यह क्लाइंट के व्यवहार से क्लाइंट की निर्भरता को अलग करने की अनुमति देता है, जो एप्लिकेशन को शिथिल रूप से युग्मित करने की अनुमति देता है।

उपरोक्त परिभाषा के साथ भ्रमित नहीं होना - एक निर्भरता इंजेक्शन का मतलब बस एक वस्तु को इसका उदाहरण चर देना है।

यह इतना आसान है, लेकिन यह बहुत सारे लाभ प्रदान करता है:

  • अपने कोड का परीक्षण करना आसान है (इकाई और UI परीक्षण जैसे स्वचालित परीक्षणों का उपयोग करके)
  • जब प्रोटोकॉल-उन्मुख प्रोग्रामिंग के साथ अग्रानुक्रम में उपयोग किया जाता है, तो एक निश्चित वर्ग के कार्यान्वयन को बदलना आसान हो जाता है - रिफ्लैक्टर के लिए आसान
  • यह कोड को अधिक मॉड्यूलर और पुन: प्रयोज्य बनाता है

आमतौर पर उपयोग किए जाने वाले तीन तरीके डिपेंडेंसी इंजेक्शन (DI) एक अनुप्रयोग में लागू किए जा सकते हैं:

  1. प्रारंभिक इंजेक्शन
  2. संपत्ति इंजेक्शन
  3. थर्ड पार्टी DI फ्रेमवर्क (जैसे स्विंजे, क्लीन, डिप या टाइफून) का उपयोग करना

डिपेंडेंसी इंजेक्शन के बारे में अधिक लेखों के लिंक के साथ एक दिलचस्प लेख है इसलिए यह जांचें कि क्या आप डीआई और नियंत्रण के व्युत्क्रम में गहरी खुदाई करना चाहते हैं।

आइए दिखाते हैं कि डीआई को व्यू कंट्रोलर्स के साथ कैसे उपयोग किया जाए - एक औसत आईओएस डेवलपर के लिए हर दिन का काम।

बिना डि का उदाहरण

हमारे पास दो दृश्य नियंत्रक होंगे: LoginViewController और TimelineViewController । LoginViewController का उपयोग लॉगिन करने के लिए किया जाता है और सफल छोर पर, यह टाइमलाइन व्यू कॉन्ट्रोलर पर स्विच हो जाएगा। दोनों व्यू कंट्रोलर FirebaseNetworkService पर निर्भर हैं।

LoginViewController

class LoginViewController: UIViewController {

    var networkService = FirebaseNetworkService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

TimelineViewController

class TimelineViewController: UIViewController {

    var networkService = FirebaseNetworkService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func logoutButtonPressed(_ sender: UIButton) {
        networkService.logutCurrentUser()
    }
}

FirebaseNetworkService

class FirebaseNetworkService {

    func loginUser(username: String, passwordHash: String) {
        // Implementation not important for this example
    }
    
    func logutCurrentUser() {
        // Implementation not important for this example
    }
}

यह उदाहरण बहुत सरल है, लेकिन मान लें कि आपके पास 10 या 15 विभिन्न दृश्य नियंत्रक हैं और उनमें से कुछ भी FirebaseNetworkervice पर निर्भर हैं। कुछ पल में आप Firebase को अपनी बैकएंड सेवा के रूप में अपनी कंपनी की इन-हाउस बैकएंड सेवा के साथ बदलना चाहते हैं। ऐसा करने के लिए आपको हर दृश्य नियंत्रक से गुजरना होगा और FireNaseNetworkService को CompanyNetworkService के साथ बदलना होगा। और अगर CompanyNetworkService में कुछ तरीके बदल गए हैं, तो आपके पास बहुत काम करना होगा।

यूनिट और यूआई परीक्षण इस उदाहरण की गुंजाइश नहीं है, लेकिन यदि आप कसकर युग्मित निर्भरता के साथ यूनिट व्यू कंट्रोलर की जांच करना चाहते हैं, तो आपको ऐसा करने में वास्तव में कठिन समय होगा।

आइए इस उदाहरण को फिर से लिखें और नेटवर्क सेवा को हमारे दृश्य नियंत्रकों में इंजेक्ट करें।

डिपेंडेंसी इंजेक्शन के साथ उदाहरण

डिपेंडेंसी इंजेक्शन से सर्वश्रेष्ठ बनाने के लिए, आइए एक प्रोटोकॉल में नेटवर्क सेवा की कार्यक्षमता को परिभाषित करें। इस तरह, नेटवर्क सेवा पर निर्भर दृश्य नियंत्रकों को इसके वास्तविक क्रियान्वयन के बारे में जानना भी नहीं होगा।

protocol NetworkService {
    func loginUser(username: String, passwordHash: String)
    func logutCurrentUser()
}

NetworkService प्रोटोकॉल का कार्यान्वयन जोड़ें:

class FirebaseNetworkServiceImpl: NetworkService {
    func loginUser(username: String, passwordHash: String) {
        // Firebase implementation
    }
    
    func logutCurrentUser() {
        // Firebase implementation
    }
}

चलिए FirebaseNetworkService के बजाय नए NetworkService प्रोटोकॉल का उपयोग करने के लिए LoginViewController और TimelineViewController बदलें।

LoginViewController

class LoginViewController: UIViewController {

    // No need to initialize it here since an implementation
    // of the NetworkService protocol will be injected
    var networkService: NetworkService?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

TimelineViewController

class TimelineViewController: UIViewController {

    var networkService: NetworkService?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func logoutButtonPressed(_ sender: UIButton) {
        networkService?.logutCurrentUser()
    }
}

अब, सवाल यह है: हम LoginViewController और TimelineViewController में सही NetworkService कार्यान्वयन को कैसे इंजेक्ट करते हैं?

चूंकि LoginViewController शुरुआती व्यू कंट्रोलर है और एप्लिकेशन के शुरू होने पर हर बार दिखाएगा, हम AppDelegate में सभी निर्भरताओं को इंजेक्ट कर सकते हैं

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // This logic will be different based on your project's structure or whether
    // you have a navigation controller or tab bar controller for your starting view controller
    if let loginVC = window?.rootViewController as? LoginViewController {
        loginVC.networkService = FirebaseNetworkServiceImpl()
    }
    return true
}

AppDelegate में हम केवल प्रथम दृश्य नियंत्रक (LoginViewController) का संदर्भ ले रहे हैं और संपत्ति इंजेक्शन विधि का उपयोग करके NetworkService कार्यान्वयन को इंजेक्ट कर रहे हैं।

अब, अगला कार्य टाइमलाइन व्यू कॉन्ट्रोलर में NetworkService कार्यान्वयन को इंजेक्ट करना है। सबसे आसान तरीका यह है कि जब LoginViewController TimlineViewController में संक्रमण कर रहा हो।

हम LoginViewController में (यदि आप व्यू कंट्रोलर के माध्यम से नेविगेट करने के लिए एक अलग दृष्टिकोण का उपयोग कर रहे हैं, तो इंजेक्शन कोड को वहां पर तैयार करें) इंजेक्शन इंजेक्शन कोड जोड़ देंगे।

हमारा LoginViewController वर्ग अब इस तरह दिखता है:

class LoginViewController: UIViewController {
    // No need to initialize it here since an implementation
    // of the NetworkService protocol will be injected
    var networkService: NetworkService?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "TimelineViewController" {
            if let timelineVC = segue.destination as? TimelineViewController {
                // Injecting the NetworkService implementation
                timelineVC.networkService = networkService
            }
        }
    }
}

हम कर रहे हैं और यह इतना आसान है।

अब कल्पना करें कि हम अपने नेटवर्क्स के बैकएंड कार्यान्वयन के लिए फायरबेस से अपने नेटवर्क सेवा कार्यान्वयन को स्विच करना चाहते हैं। हमें बस इतना करना होगा:

नया नेटवर्क सेवा कार्यान्वयन वर्ग जोड़ें:

class CompanyNetworkServiceImpl: NetworkService {
    func loginUser(username: String, passwordHash: String) {
        // Company API implementation
    }
    
    func logutCurrentUser() {
        // Company API implementation
    }
}

AppDelegate में नए कार्यान्वयन के साथ FirebaseNetworkServiceImpl स्विच करें:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // This logic will be different based on your project's structure or whether
        // you have a navigation controller or tab bar controller for your starting view controller
        if let loginVC = window?.rootViewController as? LoginViewController {
            loginVC.networkService = CompanyNetworkServiceImpl()
        }
        return true
    }

यही है, हमने NetworkService प्रोटोकॉल के संपूर्ण अंडरलेक्टिंग कार्यान्वयन को भी देखा है, जिसमें भी LoginViewController या TimelineViewController को छूने के बिना।

जैसा कि यह एक सरल उदाहरण है, आप अभी सभी लाभों को नहीं देख सकते हैं, लेकिन यदि आप अपनी परियोजनाओं में DI का उपयोग करने का प्रयास करते हैं, तो आप लाभ देखेंगे और हमेशा डिपेंडेंसी इंजेक्शन का उपयोग करेंगे।

निर्भरता इंजेक्शन के प्रकार

यह उदाहरण इन तरीकों का उपयोग करके स्विफ्ट में डिपेंडेंसी इंजेक्शन ( DI ) डिज़ाइन पैटर्न का उपयोग करने का तरीका प्रदर्शित करेगा:

  1. प्रारंभिक इंजेक्शन (उचित शब्द कंस्ट्रक्टर इंजेक्शन है, लेकिन चूंकि स्विफ्ट में इनिशियलाइज़र है, इसे इनिलाइज़र इंजेक्शन कहा जाता है)
  2. संपत्ति इंजेक्शन
  3. विधि इंजेक्शन

DI के बिना उदाहरण सेटअप

    protocol Engine {
        func startEngine()
        func stopEngine()
    }
    
    class TrainEngine: Engine {
        func startEngine() {
            print("Engine started")
        }
        
        func stopEngine() {
            print("Engine stopped")
        }
    }
    
    protocol TrainCar {
    var numberOfSeats: Int { get }
    func attachCar(attach: Bool)
}

class RestaurantCar: TrainCar {
    var numberOfSeats: Int {
        get {
            return 30
        }
    }
    func attachCar(attach: Bool) {
        print("Attach car")
    }
}

class PassengerCar: TrainCar {
    var numberOfSeats: Int {
        get {
            return 50
        }
    }
    func attachCar(attach: Bool) {
        print("Attach car")
    }
}
    
class Train {
    let engine: Engine?
    var mainCar: TrainCar?
}

प्रारंभिक निर्भरता इंजेक्शन

जैसा कि नाम से पता चलता है, सभी आश्रितों को क्लास इनिशियलाइज़र के माध्यम से इंजेक्ट किया जाता है। इनिशलाइज़र के माध्यम से निर्भरता को इंजेक्ट करने के लिए, हम इनिशलाइज़र को Train क्लास में जोड़ देंगे।

ट्रेन वर्ग अब इस तरह दिखता है:

class Train {
    let engine: Engine?
    var mainCar: TrainCar?
    
    init(engine: Engine) {
        self.engine = engine
    }
}

जब हम ट्रेन वर्ग का एक उदाहरण बनाना चाहते हैं, तो हम एक विशिष्ट इंजन कार्यान्वयन को इंजेक्ट करने के लिए इनिलाइज़र का उपयोग करेंगे:

let train = Train(engine: TrainEngine())

नोट: प्रारंभिक इंजेक्शन बनाम संपत्ति इंजेक्शन का मुख्य लाभ यह है कि हम चर को निजी चर के रूप में सेट कर सकते हैं या let कीवर्ड के साथ इसे स्थिर बना सकते हैं (जैसा कि हमने अपने उदाहरण में किया था)। इस तरह हम यह सुनिश्चित कर सकते हैं कि कोई भी इसे एक्सेस नहीं कर सकता और न ही इसे बदल सकता है।

गुण निर्भरता इंजेक्शन

DI गुणों का उपयोग करना और भी सरल है जो एक इनिशलाइज़र का उपयोग कर रहा है। चलिए एक यात्रीकार निर्भरता को इंजेक्ट करते हैं जिस ट्रेन ऑब्जेक्ट को हमने पहले ही गुण डीआई का उपयोग करके बनाया है:

train.mainCar = PassengerCar()

बस। हमारी गाड़ी के mainCar अब एक है PassengerCar उदाहरण।

विधि निर्भरता इंजेक्शन

इस प्रकार की निर्भरता इंजेक्शन थोड़ा अलग है कि पिछले दो क्योंकि यह पूरी वस्तु को प्रभावित नहीं करेगा, लेकिन यह केवल एक विशिष्ट विधि के दायरे में उपयोग की जाने वाली निर्भरता को इंजेक्ट करेगा। जब एक निर्भरता केवल एक विधि में उपयोग की जाती है, तो आमतौर पर पूरी वस्तु को इस पर निर्भर करना अच्छा नहीं होता है। आइए ट्रेन वर्ग में एक नई विधि जोड़ें:

func reparkCar(trainCar: TrainCar) {
    trainCar.attachCar(attach: true)
    engine?.startEngine()
    engine?.stopEngine()
    trainCar.attachCar(attach: false)
}

अब, यदि हम नई ट्रेन की क्लास विधि को कॉल करते हैं, तो हम विधि निर्भरता इंजेक्शन का उपयोग करके TrainCar इंजेक्ट करेंगे।

train.reparkCar(trainCar: RestaurantCar())


Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow