ਜਾਣੋ ਕਿ ਡਿਪੈਂਡੈਂਸੀ ਇੰਜੈਕਸ਼ਨ ਕੋਡ ਨੂੰ ਟੈਸਟ, ਰੀਫੈਕਟਰ ਅਤੇ ਵਧਾਉਣਾ ਕਿਵੇਂ ਆਸਾਨ ਬਣਾਉਂਦਾ ਹੈ। ਪ੍ਰਯੋਗਿਕ ਪੈਟਰਨ, ਉਦਾਹਰਣਾਂ ਅਤੇ ਆਮ ਗਲਤੀਆਂ ਜਿਨ੍ਹਾਂ ਤੋਂ ਬਚਣਾ ਚਾਹੀਦਾ ਹੈ ਵੇਖੋ।

Dependency Injection (DI) ਇੱਕ ਸਧਾਰਣ ਵਿਚਾਰ ਹੈ: ਜਦੋਂ ਕਿਸੇ ਕੋਡ ਟੁਕੜੇ ਨੂੰ ਉਸਨੂੰ ਲੋੜੀਂਦੀ ਚੀਜ਼ਾਂ ਖੁਦ ਬਣਾਉਣ ਦੀ ਜਗ੍ਹਾ ਬਾਹਰੋਂ ਦਿੱਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ।
ਇਹ "ਲੋੜੀਂਦੀਆਂ ਚੀਜ਼ਾਂ" ਉਸਦੇ ਡਿਪੈਂਡੈਂਸੀਜ਼ ਹੁੰਦੀਆਂ ਹਨ — ਉਦਾਹਰਣ ਲਈ, ਡੇਟਾਬੇਸ ਕਨੈਕਸ਼ਨ, ਪੇਮੈਂਟ ਸਰਵਿਸ, ਘੜੀ, ਲੌਗਰ, ਜਾਂ ਈਮੇਲ ਸੈਂਡਰ. ਜੇ ਤੁਹਾਡਾ ਕੋਡ ਇਹਨਾਂ ਨੂੰ ਆਪਣੇ ਅੰਦਰੋਂ ਬਣਾਉਂਦਾ ਹੈ, ਤਾਂ ਉਹ ਚੁੱਪਚਾਪ ਇਹ ਫੈਸਲਾ ਕਰ ਲੈਂਦਾ ਹੈ ਕਿ ਇਹ ਡਿਪੈਂਡੈਂਸੀਜ਼ ਕਿਵੇਂ ਕੰਮ ਕਰਣਗੀਆਂ.
ਦਫ਼ਤਰ ਦਾ ਕੌਫੀ ਮਸ਼ੀਨ ਸੋਚੋ। ਇਸਨੂੰ ਪਾਣੀ, ਕੌਫੀ ਬੀਨ, ਅਤੇ ਬਿਜਲੀ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ।
DI ਉਸ ਦੂਜੇ ਤਰੀਕੇ ਵਰਗਾ ਹੈ: “ਕੌਫੀ ਮਸ਼ੀਨ” (ਤੁਹਾਡੀ ਕਲਾਸ/ਫੰਕਸ਼ਨ) ਆਪਣਾ ਕੰਮ ਕਰਦੀ ਹੈ, ਜਦਕਿ “ਸਪਲਾਈਜ਼” (ਡਿਪੈਂਡੈਂਸੀਜ਼) ਉਹਨਾਂ ਨੂੰ ਸੈਟਅੱਪ ਕਰਨ ਵਾਲਾ ਵਿਅਕਤੀ ਉਪਲੱਬਧ ਕਰਵਾਉਂਦਾ ਹੈ।
DI ਕਿਸੇ ਖਾਸ ਫਰੇਮਵਰਕ ਵਰਤਣ ਦੀ ਲੋੜ ਨਹੀਂ ਹੈ, ਅਤੇ ਇਹ DI ਕੰਟੇਨਰ ਦੇ ਸਮਾਨ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ DI ਨੂੰ ਮੈਨੂਅਲੀ ਤੌਰ 'ਤੇ ਵੀ ਕਰ ਸਕਦੇ ਹੋ ਜਿਵੇਂ ਕਿ ਡਿਪੈਂਡੈਂਸੀਜ਼ ਨੂੰ ਪੈਰਾਮੀਟਰ ਜਾਂ constructor ਰਾਹੀਂ ਪਾਸ ਕਰਕੇ।
DI "ਮੌਕਿੰਗ" ਨਹੀਂ ਹੈ। ਮੌਕਿੰਗ ਇੱਕ ਢੰਗ ਹੈ ਜੋ ਤੁਸੀਂ ਟੈਸਟਾਂ ਵਿੱਚ DI ਨਾਲ ਵਰਤ ਸਕਦੇ ਹੋ, ਪਰ DI ਖੁਦ ਇਹ ਡਿਜ਼ਾਈਨ ਚੋਣ ਹੈ ਕਿ ਡਿਪੈਂਡੈਂਸੀਜ਼ ਕਿੱਥੇ ਬਣਦੀਆਂ ਹਨ।
ਜਦੋਂ ਡਿਪੈਂਡੈਂਸੀਜ਼ ਬਾਹਰੋਂ ਦਿੱਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ, ਤਾਂ ਤੁਹਾਡਾ ਕੋਡ ਵੱਖ-ਵੱਖ ਸੰਦਰਭਾਂ (production, unit tests, demos, future features) ਵਿੱਚ ਆਸਾਨੀ ਨਾਲ ਚੱਲ ਸਕਦਾ ਹੈ।
ਇਹੀ ਲਚਕੀਲਾਪਣ ਮਾਡਿਊਲਾਂ ਨੂੰ ਵੀ ਸਾਫ਼ ਰੱਖਦਾ ਹੈ: ਹਿੱਸੇ ਬਦਲੇ ਜਾ ਸਕਦੇ ਹਨ ਬਿਨਾਂ ਸਿਸਟਮ ਨੂੰ ਦੁਬਾਰਾ ਵਾਇਰ ਕਰਨ ਦੇ। ਨਤੀਜੇ ਵਜੋਂ, ਟੈਸਟ ਤੇਜ਼ ਅਤੇ ਸਪਸ਼ਟ ਹੋ ਜਾਂਦੇ ਹਨ (ਕਿਉਂਕਿ ਤੁਸੀਂ सरल stand-ins ਬਦਲ ਸਕਦੇ ਹੋ), ਅਤੇ ਕੋਡਬੇਸ ਬਦਲਾਉਂਦੇ ਸਮੇਂ ਘੱਟ ਜਟਿਲ ਹੋ ਜਾਂਦਾ ਹੈ।
Tight coupling ਉਸ ਵੇਲੇ ਹੁੰਦੀ ਹੈ ਜਦੋਂ ਇੱਕ ਹਿੱਸਾ ਆਪਣੇ ਆਪ ਨਿਰਧਾਰਤ ਕਰਦਾ ਹੈ ਕਿ ਦੂਜੇ ਹਿੱਸੇ ਕਿਸ ਤਰ੍ਹਾਂ ਹੋਣੇ ਚਾਹੀਦਿਆਂ ਹਨ। ਸਭ ਤੋਂ ਆਮ ਰੂਪ ਹੈ: ਬਿਜਨਸ ਲੌਜਿਕ ਦੇ ਅੰਦਰ new ਕਰਨਾ।
ਇੱਕ checkout ਫੰਕਸ਼ਨ ਸੋਚੋ ਜੋ ਅੰਦਰੋਂ new StripeClient() ਅਤੇ new SmtpEmailSender() ਕਰਦਾ ਹੈ। ਸ਼ੁਰੂ ਵਿੱਚ ਇਹ ਸੁਵਿਧਾਜਨਕ ਲੱਗਦਾ ਹੈ — ਪਰ ਇਹ checkout ਨੂੰ ਇਨ੍ਹਾਂ ਖਾਸ ਇੰਪਲੀਮੇੰਟੇਸ਼ਨਾਂ ਨਾਲ ਲਾਕ ਕਰ ਦਿੰਦਾ ਹੈ, ਉਨ੍ਹਾਂ ਦੀਆਂ ਕਨਫ਼ਿਗਰੇਸ਼ਨ ਲੋੜਾਂ ਅਤੇ ਬਣਾਉਣ ਦੇ ਨਿਯਮਾਂ ਨਾਲ।
ਇਹ coupling "ਛੁਪਾ" ਹੁੰਦਾ ਹੈ ਕਿਉਂਕਿ ਇਹ method signature ਤੋਂ ਸਪਸ਼ਟ ਨਹੀਂ ਹੋਦਾ। ਫੰਕਸ਼ਨ ਸਿਰਫ ਇੱਕ ਆਰਡਰ ਪ੍ਰੋਸੈਸ ਕਰਨ ਦਾ ਕੰਮ ਲੱਗਦਾ ਹੈ, ਪਰ ਉਹ ਚੁੱਪਚਾਪ payment gateways, email providers, ਅਤੇ ਸ਼ਾਇਦ ਡੇਟਾਬੇਸ ਨਾਲ ਵੀ ਨਿਰਭਰ ਹੁੰਦਾ ਹੈ।
ਜਦੋਂ ਡਿਪੈਂਡੈਂਸੀਜ਼ hard-coded ਹੁੰਦੀਆਂ ਹਨ:
Hard-coded dependencies ਕਾਰਨ unit tests ਵਾਸਤੇ ਸੱਚੀ ਕਾਰਵਾਈ ਚਲਾਉਣੀਆਂ ਪੈਂਦੀਆਂ ਹਨ: ਨੈਟਵਰਕ ਕਾਲਾਂ, ਫਾਈਲ I/O, clocks, random IDs, ਜਾਂ ਸ਼ੇਅਰ ਕੀਤੇ ਸਰੋਤ। ਟੈਸਟ ਵੱਖ-ਵੱਖ ਹੋਕੇ ਹੌਲੀ ਹੋ ਜਾਂਦੇ ਹਨ ਅਤੇ flaky ਵੀ ਕਈ ਵਾਰੀ ਬਣ ਜਾਂਦੇ ਹਨ।
ਜੇ ਤੁਸੀਂ ਇਹ ਪੈਟਰਨ ਵੇਖਦੇ ਹੋ, ਤਾਂ tight coupling ਪਹਿਲਾਂ ਹੀ ਤੁਹਾਨੂੰ ਸਮਾਂ ਖਰਚਾ ਕਰਾ ਰਿਹਾ ਹੋ ਸਕਦਾ ਹੈ:
new ਹਰ ਥਾਂ ਤੇ ਫੈਲਿਆ ਹੋਇਆDependency Injection ਇਸਦੀ ਸਥਿਤੀ ਦਾ ਉਪਾਯ ਹੈ: ਡਿਪੈਂਡੈਂਸੀਜ਼ ਨੂੰ ਸਪਸ਼ਟ ਅਤੇ ਬਦਲਣ ਯੋਗ ਬਣਾਉਣਾ — ਬਿਨਾਂ ਹਰ ਵਾਰੀ ਬਿਜਨਸ ਨਿਯਮਾਂ ਨੂੰ ਦੁਬਾਰਾ ਲਿਖੇ।
Inversion of Control (IoC) ਜ਼ਿੰਮੇਵਾਰੀ ਵਿੱਚ ਇੱਕ ਸਧਾਰਣ ਤਬਦੀਲੀ ਹੈ: ਇੱਕ ਕਲਾਸ ਨੂੰ ਇਹ ਦੱਸਣਾ ਚਾਹੀਦਾ ਹੈ ਕਿ ਉਸਦਾ ਕੰਮ ਕੀ ਹੈ, ਨ ਕਿ ਉਹ ਆਪਣੇ ਲੋੜੀਂਦੇ ਚੀਜ਼ਾਂ ਕਿਵੇਂ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ।
ਜਦੋਂ ਇੱਕ ਕਲਾਸ ਆਪਣੀਆਂ ਡਿਪੈਂਡੈਂਸੀਜ਼ (ਜਿਵੇਂ new EmailService() ਜਾਂ ਡਾਈਰੈਕਟ DB ਕਨੈਕਸ਼ਨ) ਬਣਾਉਂਦੀ ਹੈ, ਤਾਂ ਉਹ ਚੁੱਪਚਾਪ ਦੋ ਕੰਮ ਕਰ ਰਹੀ ਹੁੰਦੀ ਹੈ: ਬਿਜਨਸ ਲੌਜਿਕ ਅਤੇ ਸੈਟਅੱਪ। ਇਸ ਨਾਲ ਕਲਾਸ ਬਦਲਣਾ, ਰੀ-ਯੂਜ਼ ਅਤੇ ਟੈਸਟ ਕਰਨਾ ਔਖਾ ਹੋ ਜਾਂਦਾ ਹੈ।
IoC ਨਾਲ, ਤੁਹਾਡਾ ਕੋਡ ਅਬਸਟ੍ਰੈਕਸ਼ਨਾਂ (interfaces ਜਾਂ ਛੋਟੇ contract types) 'ਤੇ ਨਿਰਭਰ ਕਰਦਾ ਹੈ, ਨਾ ਕਿ ਖਾਸ ਇੰਪਲੀਮੇੰਟੇਸ਼ਨਾਂ 'ਤੇ।
ਉਦਾਹਰਣ ਵੱਜੋਂ, CheckoutService ਨੂੰ ਇਹ ਜਾਣਨ ਦੀ ਲੋੜ ਨਹੀਂ ਕਿ payments Stripe, PayPal, ਜਾਂ fake processor ਦੁਆਰਾ ਕੀਤੇ ਜਾਂਦੇ ਹਨ—ਉਸਨੂੰ ਸਿਰਫ "ਕੋਈ ਚੀਜ਼ ਜੋ ਕਾਰਡ ਚਾਰਜ ਕਰ ਸਕਦੀ ਹੈ" ਚਾਹੀਦੀ ਹੈ। ਜੇ CheckoutService ਇੱਕ IPaymentProcessor ਸਵੀਕਾਰ ਕਰਦਾ ਹੈ, ਤਾਂ ਉਹ ਕਿਸੇ ਵੀ implementation ਨਾਲ ਕੰਮ ਕਰ ਸਕਦਾ ਹੈ ਜੋ ਉਸ contract ਨੂੰ ਪੂਰਾ ਕਰੇ।
IoC ਦਾ ਕਾਰਜਕਾਰੀ ਹਿੱਸਾ ਇਹ ਹੈ ਕਿ ਡਿਪੈਂਡੈਂਸੀ ਬਣਾਉਣ ਨੂੰ ਕਲਾਸ ਤੋਂ ਬਾਹਰ ਲੈ ਜਾਇਆ ਜਾਵੇ ਅਤੇ ਉਹਨਾਂ ਨੂੰ inject ਕੀਤਾ ਜਾਵੇ (ਅਕਸਰ constructor ਰਾਹੀਂ). ਇਹੀ ਜਗ੍ਹਾ ਹੈ ਜਿੱਥੇ DI ਦੀ ਵਰਤੋਂ ਆਉਂਦੀ ਹੈ।
ਉਸਦੀ ਬਜਾਏ ਕਿ:
ਤੁਸੀਂ ਲੈਂਦੇ ਹੋ:
ਨਤੀਜਾ ਇਹ ਹੁੰਦਾ ਹੈ ਕਿ ਵਿਵਹਾਰ swap ਕਰਨਾ ਇੱਕ configuration ਫੈਸਲਾ ਬਣ ਜਾਂਦਾ ਹੈ, rewrite ਨਹੀਂ।
ਜੇ ਕਲਾਸਾਂ ਆਪਣੀਆਂ ਡਿਪੈਂਡੈਂਸੀਜ਼ ਨਹੀਂ ਬਣਾਉਂਦੀਆਂ, ਤਾਂ ਕਿਸੇ ਹੋਰ ਨੂੰ ਬਣਾਉਣਾ ਪੈਣਾ ਚਾਹੀਦਾ ਹੈ। ਉਹ "ਕਿਸੇ ਹੋਰ" composition root ਹੈ: ਓਹ ਥਾਂ ਜਿੱਥੇ ਤੁਹਾਡੀ ਐਪਲੀਕੇਸ਼ਨ ਇਕੱਠੀ ਕੀਤੀ ਜਾਂਦੀ ਹੈ—ਆਮ ਤੌਰ 'ਤੇ startup ਕੋਡ।
Composition root ਉਹ ਥਾਂ ਹੈ ਜਿੱਥੇ ਤੁਸੀਂ ਫੈਸਲਾ ਕਰਦੇ ਹੋ, "Production ਵਿੱਚ RealPaymentProcessor; tests ਵਿੱਚ FakePaymentProcessor." ਇਕੱਠੀ ਵਾਇਰਿੰਗ ਇੱਕ ਥਾਂ ਤੇ ਰੱਖਣ ਨਾਲ ਹੈਰਾਨੀ ਘਟਦੀ ਹੈ ਅਤੇ ਕੋਡਬੇਸ ਸਾਫ਼ ਰਹਿੰਦੀ ਹੈ।
IoC unit tests ਨੂੰ ਸਧਾਰਨ ਬਣਾਉਂਦਾ ਹੈ ਕਿਉਂਕਿ ਤੁਸੀਂ ਛੋਟੇ, ਤੇਜ਼ test doubles ਪ੍ਰਦਾਨ ਕਰ ਸਕਦੇ ਹੋ ਨ ਕਿ ਰੀਅਲ ਨੈਟਵਰਕ ਜਾਂ ਡੇਟਾਬੇਸ।
ਇਹ ਰੀਫੈਕਟਰਾਂ ਨੂੰ ਵੀ ਸੁਰੱਖਿਅਤ ਬਣਾਉਂਦਾ ਹੈ: ਜਦੋਂ ਜ਼ਿੰਮੇਵਾਰੀਆਂ ਵੱਖ ਹੋਂਦੀਆਂ ਹਨ, implementation ਬਦਲਣ 'ਤੇ ਆਮ ਤੌਰ 'ਤੇ classes ਜੋ ਉਹ ਵਰਤ ਰਹੇ ਹਨ, ਉਨ੍ਹਾਂ ਨੂੰ ਬਦਲਣਾ ਪੈਂਦਾ ਨਹੀਂ—ਜੇਕਰ abstraction ਇੱਕੋ ਹੀ ਰਹੇ।
Dependency Injection (DI) ਇੱਕ ਤਕਨਿਕ ਨਹੀਂ—ਇਹ ਕਲਾਸ ਨੂੰ ਉਸ ਦੀਆਂ ਲੋੜੀਂਦੀਆਂ ਚੀਜ਼ਾਂ "ਪ੍ਰਦਾਨ ਕਰਨ" ਦੇ ਮੂਲ ਤਰੀਕਿਆਂ ਦਾ ਛੋਟਾ ਸੈੱਟ ਹੈ (ਜਿਵੇਂ logger, database client, ਜਾਂ payment gateway). ਤੁਹਾਨੂੰ ਜੋ ਸਟਾਈਲ ਚੁਣਨੀ ਹੈ ਉਹ ਸਪੱਸ਼ਟਤਾ, ਟੈਸਟਬਿਲਟੀ, ਅਤੇ misuse ਹੋਣ ਦੀ ਸੰਭਾਵਨਾ ਨੂੰ ਪ੍ਰਭਾਵਿਤ ਕਰਦੀ ਹੈ।
Constructor injection ਵਿੱਚ ਡਿਪੈਂਡੈਂਸੀਜ਼ ਆਬਜੈਕਟ ਬਣਾਉਣ ਵੇਲੇ ਲਾਜ਼ਮੀ ਹੁੰਦੀਆਂ ਹਨ। ਵੱਡਾ ਫਾਇਦਾ ਇਹ ਹੈ ਕਿ ਤੁਸੀਂ ਉਨ੍ਹਾਂ ਨੂੰ ਭੁੱਲ ਨਹੀਂ ਸਕਦੇ।
ਇਹ ਉਹਨਾਂ ਲਈ ਬਿਹਤਰ ਹੈ ਜਦੋਂ dependency:
Constructor injection ਆਮ ਤੌਰ 'ਤੇ ਸਭ ਤੋਂ ਸਪਸ਼ਟ ਕੋਡ ਅਤੇ ਸਿੱਧਾ unit tests ਦਿੰਦਾ ਹੈ, ਕਿਉਂਕਿ ਟੈਸਟ ਉਸ ਸਮੇਂ ਹੀ fake ਜਾਂ mock ਪਾਸ ਕਰ ਸਕਦਾ ਹੈ।
ਕਈ ਵਾਰੀ dependency ਸਿਰਫ ਇਕ ਓਪਰੇਸ਼ਨ ਲਈ ਚਾਹੀਦੀ ਹੁੰਦੀ ਹੈ—ਇੱਕ ਤਿਆਰੀ ਫਾਰਮੈਟਰ, ਵਿਸ਼ੇਸ਼ ਸਟ੍ਰੈਟਜੀ, ਜਾਂ request-scoped value.
ਉਹਨਾਂ ਕੇਸਾਂ ਵਿੱਚ, ਇਸ ਨੂੰ method ਪੈਰਾਮੀਟਰ ਵਜੋਂ ਪਾਸ ਕਰੋ। ਇਸ ਨਾਲ ਆਬਜੈਕਟ ਛੋਟਾ ਰਹਿੰਦਾ ਹੈ ਅਤੇ ਇਕ-ਵਾਰੀ ਲੋੜ ਨੂੰ ਸਥਾਈ ਫੀਲਡ ਬਣਾਉਣ ਤੋਂ ਬਚਾਇਆ ਜਾਂਦਾ ਹੈ।
Setter injection ਸੁਵਿਧਾਜਨਕ ਹੋ ਸਕਦੀ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਬਣਾਉਣ ਸਮੇ dependency ਪ੍ਰਦਾਨ ਨਹੀਂ ਕਰ ਸਕਦੇ (ਕੁਝ ਫਰੇਮਵਰਕ ਜਾਂ ਲੈਗੇਸੀ ਕੋਡ)। ਵਪਾਰ ਇਹ ਹੈ ਕਿ ਇਹ ਲਾਜ਼ਮੀ ਤੌਰ 'ਤੇ ਲੋੜਾਂ ਨੂੰ ਛੁਪਾ ਸਕਦੀ ਹੈ: ਕਲਾਸ ਵਰਤਣਯੋਗ ਦਿਖਾਈ ਦੇ ਸਕਦੀ ਹੈ ਭਾਵੇਂ ਉਹ ਪੂਰੀ ਤਰ੍ਹਾਂ ਕਨਫ਼ਿਗਰ ਨਹੀਂ ਹੋਈ।
ਇਸ ਨਾਲ runtime surprises ਆ ਸਕਦੇ ਹਨ ("ਇਹ ਕਿਉਂ undefined ਹੈ?") ਅਤੇ ਟੈਸਟ fragile ਬਣ ਸਕਦੇ ਹਨ ਕਿਉਂਕਿ ਸੈਟਅੱਪ ਚੁੱਕਣਾ ਆਸਾਨ ਹੈ।
ਯੂਨਿਟ ਟੈਸਟ ਸਭ ਤੋਂ ਉਪਯੋਗੀ ਹੁੰਦੇ ਹਨ ਜਦੋਂ ਉਹ ਤੇਜ਼, ਦੋਹਰਾਏ ਜਾਣ ਯੋਗ, ਅਤੇ ਇੱਕ ਵਿਵਹਾਰ 'ਤੇ ਕੇਂਦ੍ਰਿਤ ਹੋਣ। ਜਦੋਂ ਕੋਈ "ਯੂਨਿਟ" ਟੈਸਟ ਅਸਲੀ ਡੇਟਾਬੇਸ, ਨੈਟਵਰਕ ਕਾਲ, ਫਾਈਲਸਿਸਟਮ ਜਾਂ ਸਮਾਂ 'ਤੇ ਨਿਰਭਰ ਕਰਦਾ ਹੈ, ਤਾਂ ਉਹ ਹੌਲਾ ਹੋ ਜਾਂਦਾ ਹੈ ਅਤੇ flaky ਬਣ ਸਕਦਾ ਹੈ।
Dependency Injection (DI) ਇਸਨੂੰ ਇਹ ਰਾਹ ਦਿੰਦਾ ਹੈ ਕਿ ਤੁਸੀਂ ਟੈਸਟਾਂ ਵਿੱਚ database access, HTTP clients, time providers ਆਦਿ ਦੀ ਥਾਂ ਬਦਲ ਸਕਦੇ ਹੋ।
ਅਸਲੀ DB ਜਾਂ API ਕਾਲ ਸੈਟਅੱਪ ਸਮਾਂ ਅਤੇ ਲੇਟੇੰਸੀ ਵਧਾਉਂਦੀ ਹੈ। DI ਨਾਲ ਤੁਸੀਂ in-memory repository ਜਾਂ fake client inject ਕਰ ਸਕਦੇ ਹੋ ਜੋ ਤੁਰੰਤ ਤਿਆਰ responses ਦੇ ਦਿੰਦਾ ਹੈ। ਇਸ ਨਾਲ:
ਬਿਨਾਂ DI ਦੇ, ਕੋਡ ਅਕਸਰ ਆਪਣੇ dependencies "new()" ਕਰਦਾ ਹੈ, ਜਿਸ ਨਾਲ ਟੈਸਟ ਪੂਰੇ ਸਟੈਕ ਨੂੰ ਇਕਸਾਰ ਐਗਜ਼ਰਸਾਈਜ਼ ਕਰਨ ਲਈ ਮਜਬੂਰ ਹੋ ਜਾਂਦੇ ਹਨ। DI ਨਾਲ ਤੁਸੀਂ:
ਕੋਈ ਹੈਕਸ ਨਹੀਂ, ਕੋਈ ਗਲੋਬਲ ਸਵਿੱਚ ਨਹੀਂ—ਸਿਰਫ ਇੱਕ ਵੱਖਰਾ implementation ਪਾਸ ਕਰੋ।
DI ਨਾਲ ਸੈਟਅੱਪ ਸਪਸ਼ਟ ਹੁੰਦਾ ਹੈ। ਬਦਲੇ ਵਿੱਚ configuration, connection strings, ਜਾਂ test-only environment variables ਵਿੱਚ ਖੋਜਣ ਦੀ ਲੋੜ ਨਹੀਂ ਰਹਿੰਦੀ; ਤੁਸੀਂ ਟੈਸਟ ਵਿਚ ਤੁਰੰਤ ਦੇਖ ਲੈ ਸਕਦੇ ਹੋ ਕਿ ਕੀ ਅਸਲ ਹੈ ਅਤੇ ਕੀ ਬਦਲਿਆ ਗਿਆ ਹੈ।
ਇੱਕ ਆਮ DI-friendly ਟੈਸਟ ਇਸ ਤਰ੍ਹਾਂ ਜਾਪਦਾ ਹੈ:
ਇਹ ਸਪਸ਼ਟਤਾ ਸ਼ੋਰ ਘਟਾਉਂਦੀ ਹੈ ਅਤੇ ਫੇਲਯਰ ਨੂੰ ਡਾਇਗਨੋਜ਼ ਕਰਨਾ ਆਸਾਨ ਬਣਾਉਂਦੀ ਹੈ—ਜੋ ਕਿ unit tests ਲਈ ਚਾਹੀਦਾ ਹੈ।
A test seam ਇੱਕ ਇਰਾਦੇ ਨਾਲ ਬਣੀ ਖੁੱਲ੍ਹੀ ਥਾਂ ਹੈ ਜਿੱਥੇ ਤੁਸੀਂ ਇੱਕ ਵਿਵਹਾਰ ਨੂੰ ਦੂਜੇ ਨਾਲ ਬਦਲ ਸਕਦੇ ਹੋ। ਪ੍ਰੋਡਕਸ਼ਨ ਵਿੱਚ ਤੁਸੀਂ ਅਸਲੀ implementation plug ਕਰਦੇ ਹੋ; ਟੈਸਟਾਂ ਵਿੱਚ ਤੁਸੀਂ ਤੇਜ਼, ਸੁਰੱਖਿਅਤ substitute plug ਕਰਦੇ ਹੋ। DI ਇਹ seams ਬਿਨਾਂ ਕਿਸੇ ਹੈਕ ਦੇ ਸਭ ਤੋਂ ਸਧਾਰਨ ਤਰੀਕੇ ਨਾਲ ਬਣਾਉਂਦਾ ਹੈ।
ਸੀਮ ਉਹਨਾਂ ਹਿੱਸਿਆਂ ਦੇ ਆਲੇ-ਦੁਆਲੇ ਸਭ ਤੋਂ ਲਾਭਦਾਇਕ ਹੁੰਦੀਆਂ ਹਨ ਜੋ ਟੈਸਟ ਵਿੱਚ ਕੰਟਰੋਲ ਕਰਨਾ ਮੁਸ਼ਕਲ ਬਣਾਉਂਦੇ ਹਨ:
ਜੇ ਤੁਹਾਡੀ ਬਿਜਨਸ ਲੌਜਿਕ ਇਹਨਾਂ ਨੂੰ ਸਿੱਧਾ ਕਾਲ ਕਰਦੀ ਹੈ, ਤਾਂ ਟੈਸਟ brittle ਹੋ ਜਾਂਦੇ ਹਨ: ਉਹ environment ਨਾਲ ਜੁੜੇ ਕਾਰਨਾਂ ਕਰਕੇ fail ਕਰਨਗੇ ਬਜਾਏ ਕਿ ਵਿਵਹਾਰ ਦੀ ਗਲਤੀ ਦੇ।
ਸੀਮ ਅਕਸਰ ਇੱਕ interface ਦੀ ਸ਼ਕਲ ਵਿੱਚ ਹੁੰਦੀ ਹੈ—ਜਾਂ dynamic ਭਾਸ਼ਾਵਾਂ ਵਿੱਚ ਇੱਕ ਸਧਾਰਨ "contract" ਜਿਵੇਂ "ਇਸ αντικੂਜੇ ਨੂੰ now() method ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ"। ਮੁੱਖ ਵਿਚਾਰ ਇਹ ਹੈ ਕਿ ਤੁਸੀਂ ਉਸਦੀ ਲੋੜ ਤੇ ਨਿਰਭਰ ਕਰੋ, ਨਾ ਕਿ ਉਹ ਕਿੱਥੋਂ ਆਉਂਦੀ ਹੈ.
ਉਦਾਹਰਣ ਲਈ, order service ਦੇ ਅੰਦਰ ਸਿਧਾ system clock ਕਾਲ ਕਰਨ ਦੀ ਥਾਂ, ਤੁਸੀਂ Clock 'ਤੇ ਨਿਰਭਰ ਕਰ ਸਕਦੇ ਹੋ:
SystemClock.now()FakeClock.now() ਇੱਕ ਫਿਕਸ ਟਾਈਮ ਵਾਪਸ ਕਰਦਾ ਹੈਇਹੀ ਤਰੀਕਾ file reads (FileStore), sending email (Mailer), ਜਾਂ charging cards (PaymentGateway) ਲਈ ਵੀ ਕੰਮ ਕਰਦੀ ਹੈ। ਤੁਹਾਡੀ ਕੋਰ ਲੌਜਿਕ ਇੱਕੋ ਰਹਿੰਦੀ ਹੈ; ਸਿਰਫ plugged-in implementation ਬਦਲਦਾ ਹੈ।
ਜਦੋਂ ਤੁਸੀਂ ਵਿਵਹਾਰ ਨੂੰ ਉਦੇਸ਼ਪੂਰਵਕ ਬਦਲ ਸਕਦੇ ਹੋ:
ਵਧੀਆ ਥਾਵਾਂ ਤੇ ਰੱਖੀਆਂ ਸੀਮਾਂ ਭਾਰਪੂਰ mocking ਦੀ ਲੋੜ ਘਟਾਉਂਦੀਆਂ ਹਨ। ਅਦਲ ਬਦਲ ਲਈ ਕੁਝ ਸਾਫ਼ substitution points ਮਿਲਦੇ ਹਨ ਜੋ ਯੂਨਿਟ ਟੈਸਟ ਨੂੰ ਤੇਜ਼, ਕੇਂਦ੍ਰਿਤ ਅਤੇ ਪੇਸ਼ਗੀਯੋਗ ਰੱਖਦੇ ਹਨ।
ਮਾਡਯੂਲਰਿਟੀ ਦਾ ਮਤਲਬ ਹੈ ਕਿ ਤੁਹਾਡਾ ਸੌਫਟਵੇਅਰ ਸੁਤੰਤਰ ਹਿੱਸਿਆਂ (ਮਾਡਿਊਲਾਂ) ਤੋਂ ਬਣਿਆ ਹੋਵੇ ਜਿਨ੍ਹਾਂ ਦੀਆਂ ਸਪਸ਼ਟ ਬਾਰਡਰਾਂ ਹੋਣ: ਹਰ ਮਾਡਿਊਲ ਦਾ ਇੱਕ ਕੇਂਦਰਿਤ ਜ਼ਿੰਮੇਵਾਰ ਹੋਵੇ ਅਤੇ ਬਾਕੀ ਸਿਸਟਮ ਨਾਲ ਵੱਧ-ਤੁੱਟ ਸਪਸ਼ਟ ਤਰੀਕੇ ਨਾਲ ਗੱਲ-ਬਾਤ ਕਰੇ।
Dependency injection (DI) ਇਹ ਬਾਰਡਰਾਂ ਸਪਸ਼ਟ ਬਣਾਕੇ ਇਸਨੂੰ ਸਹਾਰਦੀ ਹੈ। ਮਾਡਿਊਲ ਆਪਣੀਆਂ ਲੋੜੀਂਦੀਆਂ ਚੀਜ਼ਾਂ ਬਣਾਉਣ ਜਾਂ ਲੱਭਣ ਦੀ ਜਗ੍ਹਾ, ਉਹਨਾਂ ਨੂੰ ਬਾਹਰੋਂ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ। ਇਹ ਛੋਟਾ ਬਦਲਾਅ ਇਸ ਗੱਲ ਨੂੰ ਘਟਾਉਂਦਾ ਹੈ ਕਿ ਇਕ ਮਾਡਿਊਲ ਹੋਰ ਬਾਰੇ ਕਿੰਨਾ ਜਾਣਦਾ ਹੈ।
ਜਦੋਂ ਕੋਡ ਅੰਦਰੋਂ dependencies ਬਣਾਉਂਦਾ ਹੈ (ਉਦਾਹਰਣ ਲਈ, service ਵਿੱਚ database client new ਕਰਨਾ), ਤਾਂ caller ਅਤੇ dependency ਇਕ-ਦੂਜੇ ਨਾਲ ਕਮਜ਼ੋਰ ਤੌਰ 'ਤੇ ਜੁੜ ਜਾਂਦੇ ਹਨ। DI ਤੁਹਾਨੂੰ interface (ਜਾਂ simple contract) 'ਤੇ ਨਿਰਭਰ ਕਰਨ ਦੀ ਸਲਾਹ ਦਿੰਦਾ ਹੈ, ਨਾ ਕਿ ਕਿਸੇ ਖਾਸ ਇੰਪਲੀਮੇੰਟੇਸ਼ਨ 'ਤੇ।
ਇਸਦਾ ਅਰਥ ਇਹ ਹੋ ਜਾਂਦਾ ਹੈ ਕਿ ਇੱਕ ਮਾਡਿਊਲ ਆਮਤੌਰ 'ਤੇ ਸਿਰਫ ਜਾਣਦਾ ਹੈ:
PaymentGateway.charge())ਜਿਸ ਨਾਲ modules ਅਕਸਰ ਇਕੱਠੇ ਬਦਲਦੇ ਨਹੀਂ; ਅੰਦਰੂਨੀ ਵੇਰਵੇ ਬਾਰਡਰਾਂ 'ਚੋਂ ਲੀਕ ਨਹੀਂ ਹੁੰਦੇ।
ਇੱਕ ਮਾਡਯੂਲਰ ਕੋਡਬੇਸ ਤੁਹਾਨੂੰ ਇਕ ਕੰਪੋਨੈਂਟ ਬਦਲਣ ਦੀ ਆਜ਼ਾਦੀ ਦੇਣਾ ਚਾਹੀਦਾ ਹੈ ਬਿਨਾਂ ਸਭ ਨੂੰ ਦੁਬਾਰਾ ਲਿਖਣ ਦੀ। DI ਇਸਨੂੰ ਪ੍ਰਯੋਗਿਕ ਬਣਾਉਂਦਾ ਹੈ:
ਹਰ ਕੇਸ ਵਿੱਚ, callers ਇਹੋ contract ਵਰਤਦੇ ਰਹਿੰਦੇ ਹਨ। "ਵਾਇਰਿੰਗ" ਇਕ ਹੀ ਥਾਂ (composition root) 'ਤੇ ਬਦਲਦੀ ਹੈ, ਨਾ ਕਿ ਕੋਡ ਪੂਰੇ ਬੇਹਦ ਫੈਲਾਉਂਦਾ ਹੈ।
ਸਪਸ਼ਟ dependency ਬਾਰਡਰ ਟੀਮਾਂ ਲਈ ਇਕੱਠੇ ਕੰਮ ਕਰਨਾ ਆਸਾਨ ਬਣਾਉਂਦੇ ਹਨ। ਇੱਕ ਟੀਮ ਨਿਰਧਾਰਤ interface ਦੇ ਪਿੱਛੇ ਨਵੀਂ implementation ਤਿਆਰ ਕਰ ਸਕਦੀ ਹੈ ਜਦਕਿ ਦੂਜੀ ਟੀਮ ਉਸ interface 'ਤੇ ਨਿਰਭਰ features ਬਣਾਉਂਦੀ ਰਹਿੰਦੀ ਹੈ।
DI incremental refactoring ਨੂੰ ਵੀ ਸਹਾਰਦਾ ਹੈ: ਤੁਸੀਂ ਇੱਕ ਮਾਡਿਊਲ ਕੱਢ ਸਕਦੇ ਹੋ, inject ਕਰ ਸਕਦੇ ਹੋ, ਅਤੇ ਧੀਰੇ-ਧੀਰੇ ਬਦਲ ਸਕਦੇ ਹੋ—ਬਿਨਾਂ ਬੜੀ rewrite ਦੀ ਲੋੜ ਦੇ।
ਕੋਡ ਵਿੱਚ DI ਦੇਖਣਾ ਕਿਸੇ ਵੀ ਪਰਿਭਾਸ਼ਾ ਤੋਂ ਤੇਜ਼ ਬਣਾਉਂਦਾ ਹੈ। ਇੱਥੇ ਇੱਕ ਛੋਟੀ notification ਫੀਚਰ ਦੀ "before and after" ਉਦਾਹਰਣ ਹੈ।
ਜਦੋਂ ਕਲਾਸ ਅੰਦਰੋਂ new ਕਾਲ ਕਰਦੀ ਹੈ, ਉਹ ਇਹ ਫੈਸਲਾ ਕਰਦੀ ਹੈ ਕਿ ਕਿਹੜੀ implementation ਵਰਤੀ ਜਾਵੇਗੀ ਅਤੇ ਕਿਵੇਂ ਬਣਾਈ ਜਾਵੇਗੀ।
class EmailService {
send(to, message) {
// talks to real SMTP provider
}
}
class WelcomeNotifier {
notify(user) {
const email = new EmailService();
email.send(user.email, "Welcome!");
}
}
ਟੈਸਟ ਕਰਨ ਵਿੱਚ ਦਰਦ: ਇਕ ਯੂਨਿਟ ਟੈਸਟ ਅਸਲੀ ਈਮੇਲ ਬਿਹੇਵਿਯਰ ਨੂੰ trigger ਕਰਨ ਦਾ ਖ਼ਤਰਾ ਰੱਖਦਾ ਹੈ (ਜਾਂ awkward global stubbing ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ)।
test("sends welcome email", () => {
const notifier = new WelcomeNotifier();
notifier.notify({ email: "[email protected]" });
// Hard to assert without patching EmailService globally
});
ਹੁਣ WelcomeNotifier ਕਿਸੇ ਵੀ ਆਬਜੈਕਟ ਨੂੰ ਸਵੀਕਾਰ ਕਰ ਸਕਦਾ ਹੈ ਜੋ ਲੋੜੀਂਦਾ ਵਿਵਹਾਰ ਦਿੰਦਾ ਹੈ।
class WelcomeNotifier {
constructor(emailService) {
this.emailService = emailService;
}
notify(user) {
this.emailService.send(user.email, "Welcome!");
}
}
ਟੈਸਟ ਛੋਟਾ, ਤੇਜ਼, ਅਤੇ ਸਪਸ਼ਟ ਬਣ ਜਾਂਦਾ ਹੈ।
test("sends welcome email", () => {
const fakeEmail = { send: vi.fn() };
const notifier = new WelcomeNotifier(fakeEmail);
notifier.notify({ email: "[email protected]" });
expect(fakeEmail.send).toHaveBeenCalledWith("[email protected]", "Welcome!");
});
ਚਾਹੁੰਦੇ ਹੋ SMS ਬਾਅਦ ਵਿੱਚ? WelcomeNotifier ਨੂੰ ਟਚ ਕਰੋ ਨਹੀਂ—ਸਿਰਫ ਵੱਖਰੀ implementation ਪਾਸ ਕਰੋ:
const smsService = { send: (to, msg) => {/* SMS provider */} };
const notifier = new WelcomeNotifier(smsService);
ਇਹੀ ਪ੍ਰਯੋਗਿਕ ਨਤੀਜਾ ਹੈ: ਟੈਸਟ ਰਚਨਾ ਵਿਸਥਾਰਕ ਵੇਰਵਿਆਂ ਨਾਲ ਨਹੀਂ ਲੜਦੇ, ਅਤੇ ਨਵਾਂ ਵਿਵਹਾਰ dependecies ਬਦਲ ਕੇ ਜੋੜਿਆ ਜਾਂਦਾ ਹੈ।
Dependency Injection ਸਧਾਰਨ ਹੀ ਹੋ ਸਕਦੀ ਹੈ: "ਜੋ ਚੀਜ਼ ਚਾਹੀਦੀ ਹੈ ਉਹ ਦਿਓ।" ਇਹ manual DI ਹੈ। DI container ਇਕ ਔਜ਼ਾਰ ਹੈ ਜੋ ਉਹ wiring ਆਟੋਮੈਟ ਕਰਦਾ ਹੈ। ਦੋਹਾਂ ਚੰਗੇ ਹੋ ਸਕਦੇ ਹਨ—ਮੁੱਦਾ ਇਹ ਹੈ ਕਿ automation ਦਾ ਉਨ੍ਹਾ ਪੱਧਰ ਚੁਣੋ ਜੋ ਤੁਹਾਡੇ ਐਪ ਲਈ ਮਿਲਦਾ ਹੋਵੇ।
Manual DI ਵਿੱਚ ਤੁਸੀਂ objects ਖੁਦ ਬਣਾਉਂਦੇ ਹੋ ਅਤੇ constructors/ਪੈਰਾਮੀਟਰ ਰਾਹੀਂ dependencies ਪਾਸ ਕਰਦੇ ਹੋ। ਇਹ ਸਿੱਧਾ ਹੈ:
Manual wiring ਵਧੀਆ ਡਿਜ਼ਾਈਨ ਆਦਤਾਂ ਨੂੰ ਵੀ ਮਜ਼ਬੂਤ ਕਰਦਾ ਹੈ। ਜੇ ਇੱਕ object ਨੂੰ 7 dependencies ਚਾਹੀਦੀਆਂ ਹਨ, ਤਾਂ ਤੁਸੀਂ ਤੁਰੰਤ ਦਰਦ ਮਹਿਸੂਸ ਕਰਦੇ ਹੋ—ਅਕਸਰ ਇਹ ਕੁਝ ਵੰਡਣ ਦੀ ਸੰਕੇਤ ਹੁੰਦੀ ਹੈ।
ਜਿਵੇਂ-जਿਵੇਂ ਕੰਪੋਨੈਂਟਾਂ ਦੀ ਗਿਣਤੀ ਵਧਦੀ ਹੈ, manual wiring ਬਹੁਤ ਰੀਪਟੀਟਿਵ ਹੋ ਸਕਦੀ ਹੈ। DI container ਮਦਦ ਕਰ ਸਕਦਾ ਹੈ:
Containers ਉਹਨਾਂ ਐਪਾਂ ਵਿੱਚ ਚਮਕਦੇ ਹਨ ਜਿਨ੍ਹਾਂ ਵਿੱਚ ਸਪਸ਼ਟ ਬਾਰਡਰ ਅਤੇ ਲਾਈਫਸਾਈਕਲ ਹੁੰਦੇ ਹਨ—web apps, long-running services, ਜਾਂ ਉਹ ਸਿਸਟਮ ਜਿੱਥੇ ਕਈ ਫੀਚਰ ਸਾਂਝੇ ਇੰਫ੍ਰਾਸਟਰੱਕਚਰ 'ਤੇ ਨਿਰਭਰ ਹੁੰਦੇ ਹਨ।
ਇੱਕ container ਭਾਰਪੂਰ coupled ਡਿਜ਼ਾਈਨ ਨੂੰ tidy ਮਹਿਸੂਸ ਕਰਾ ਸਕਦਾ ਹੈ ਕਿਉਂਕਿ wiring ਦਿੱਖ ਤੋਂ ਹਟ ਜਾਂਦੀ ਹੈ। ਪਰ underlying issues ਰਹਿੰਦੀਆਂ ਹਨ:
ਜੇ container ਜੋੜਨ ਨਾਲ ਕੋਡ ਘੱਟ ਪੜ੍ਹਨਯੋਗ ਬਣਦਾ ਹੈ, ਜਾਂ developer ਪਤਾ ਨਹੀਂ ਕਰ ਰਹੇ ਕਿ ਕਿਸਨੇ ਕੀ depend ਕੀਤੀ ਹੈ, ਤਾਂ ਤੁਸੀਂ ਸ਼ਾਇਦ ਜ਼ਿਆਦਾ ਅੱਗੇ ਵਧ ਗਏ ਹੋ।
ਸ਼ੁਰੂਆਤ manual DI ਨਾਲ ਕਰੋ ਤਾਂ ਜੋ ਚੀਜ਼ਾਂ ਵਾਜिब ਰਹਿਣ। ਜਦੋਂ wiring repetitive ਹੋ ਜਾਂੇ ਜਾਂ lifecycle ਮੈਨੇਜਮੈਂਟ ਜਰੂਰੀ ਹੋ ਜਾਵੇ, container ਸ਼ਾਮਲ ਕਰੋ।
ਇੱਕ عملي ਨਿਯਮ: applifecycle boundary ਤੇ container ਵਰਤੋਂ (composition root) ਅਤੇ core/business code ਵਿੱਚ manual DI ਰੱਖੋ। ਇਸ ਨਾਲ ਡਿਜ਼ਾਈਨ ਸਪਸ਼ਟ ਰਹਿੰਦੀ ਹੈ ਅਤੇ ਜਦੋਂ project ਵਧਦਾ ਹੈ ਤਾਂ boilerplate ਘਟਦਾ ਹੈ।
Dependency injection (DI) ਕੋਡ ਨੂੰ ਟੈਸਟ ਅਤੇ ਬਦਲਣ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ—ਪਰ सिरਫ਼ ਜੇ ਇਹ ਅਨੁਸ਼ਾਸਨ ਨਾਲ ਵਰਤਿਆ ਜਾਵੇ। ਇੱਥੇ ਆਮ ਤਰੀਕਿਆਂ ਦੀ ਗਲਤੀਆਂ ਅਤੇ ਉਨ੍ਹਾਂ ਦੀ ਰੋਕਥਾਮ ਦਿੱਤੀ ਗਈ ਹੈ।
ਜੇ ਇੱਕ ਕਲਾਸ ਨੂੰ ਲੰਮੀ ਡਿਪੈਂਡੈਂਸੀ ਲਿਸਟ ਦੀ ਲੋੜ ਹੈ, ਤਾਂ ਅਕਸਰ ਉਹ ਬਹੁਤ ਕੁਝ ਕਰ ਰਹੀ ਹੁੰਦੀ ਹੈ। ਇਹ DI ਦੀ ਫੇਲ੍ਹ ਨਹੀਂ—ਇਹ DI ਇੱਕ ਡਿਜ਼ਾਈਨ smell ਨੂੰ ਖ਼ੋਲ ਰਿਹਾ ਹੈ।
ਅਮਲ ਵਿਚ: ਜੇ ਤੁਸੀਂ ਕਲਾਸ ਦਾ ਕੰਮ ਇਕ ਵਾਕ ਵਿੱਚ ਨਹੀਂ ਬਿਆਨ ਕਰ ਸਕਦੇ, ਜਾਂ constructor ਲਗਾਤਾਰ ਵੱਧ ਰਿਹਾ ਹੈ, ਤਾਂ ਕਲਾਸ ਨੂੰ ਵੰਡੋ, ਇੱਕ ਛੋਟਾ collaborator ਨਿਕਾਲੋ, ਜਾਂ ਨੇੜੇ-ਨੇੜੇ ਸੰਬੰਧਿਤ ਓਪਰੇਸ਼ਨਾਂ ਨੂੰ ਇੱਕ interface ਦੇ ਪਿੱਛੇ ਜੋੜੋ (ਧਿਆਨ ਨਾਲ—"god services" ਨਾ ਬਣਾਓ)।
Service Locator pattern ਆਮ ਤੌਰ 'ਤੇ container.get(Foo) ਕਾਲ ਕਰਨਾ ਹੁੰਦਾ ਹੈ ਬਿਜਨਸ ਕੋਡ ਦੇ ਅੰਦਰ। ਇਹ ਸੁਵਿਧਾਜਨਕ ਲੱਗਦਾ ਹੈ, ਪਰ dependencies ਨੂੰ ਅਦ੍ਰਿਸ਼ਯ ਬਣਾਉਂਦਾ ਹੈ: constructor pਛੋਂ ਪੜ੍ਹ ਕੇ ਪਤਾ ਨਹੀਂ ਲੱਗਦਾ ਕਿ ਕਲਾਸ ਨੂੰ ਕੀ ਚਾਹੀਦਾ ਹੈ।
ਟੈਸਟਿੰਗ ਔਖੀ ਬਣ ਜਾਂਦੀ ਹੈ ਕਿਉਂਕਿ ਤੁਹਾਨੂੰ ਗਲੋਬਲ state (locator) ਸੈਟ ਕਰਨੀ ਪੈਂਦੀ ਹੈ। explicit parameters ਪREFER ਕਰੋ (constructor injection) ਤਾਂ ਕਿ ਟੈਸਟ ਯੋਜਨਾਬੱਧ ਤਰੀਕੇ ਨਾਲ objects ਬਣਾਵੇ।
DI containers runtime 'ਤੇ fail ਕਰ ਸਕਦੇ ਹਨ ਜਦੋਂ:
ਇਹ ਮੁੱਦੇ ਗ਼ੁੱਸੇਵਧਕ ਹੁੰਦੇ ਹਨ ਕਿਉਂਕਿ ਇਹਨਾਂ ਦਾ ਪਤਾ ਸਿਰਫ ਜਦੋਂ wiring execute ਹੁੰਦੀ ਹੈ, ਮਿਲਦਾ ਹੈ।
Constructors ਛੋਟੇ ਅਤੇ ਕੇਂਦ੍ਰਿਤ ਰੱਖੋ। ਜੇ ਇੱਕ class ਦੀ dependency list ਵੱਧ ਰਿਹੈ, ਤਾਂ ਇਹ ਰਿਫੈਕਟਰ ਦਾ ਇਸ਼ਾਰਾ ਹੈ।
wiring ਲਈ integration tests ਰੱਖੋ। ਇੱਕ ਹਲਕਾ "composition root" ਟੈਸਟ ਜੋ ਤੁਹਾਡਾ application container (ਜਾਂ manual wiring) ਬਣਾਉਂਦਾ ਹੈ, ਉਹ missing registrations ਅਤੇ cycles ਨੂੰ ਪਹਿਲਾਂ ਹੀ ਪਕੜ ਸਕਦਾ ਹੈ।
ਅਖੀਰ ਵਿੱਚ, object creation ਇਕ ਥਾਂ 'ਤੇ ਰੱਖੋ (ਆਮ ਤੌਰ 'ਤੇ app startup/composition root) ਅਤੇ DI container calls ਨੂੰ business logic ਤੋਂ ਬਾਹਰ ਰੱਖੋ। ਇਹ ਵੱਖਰਾ ਰੱਖਣਾ DI ਦਾ ਮੁੱਖ ਫਾਇਦਾ—ਕੀ ਨਿਰਭਰ ਕੀ ਕਰਦਾ ਹੈ, ਇਹ ਸਪਸ਼ਟ ਰਹੇ—ਕਾਇਮ ਰੱਖਦਾ ਹੈ।
Dependency Injection ਨੂੰ ਲਾਗੂ ਕਰਨਾ ਸਭ ਤੋਂ ਆਸਾਨ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਇਸਨੂੰ ਛੋਟੇ, ਘੱਟ-ਖ਼ਤਰੇ ਵਾਲੀਆਂ ਰਿਫੈਕਟੋਰਿੰਗਾਂ ਵਜੋਂ ਲੈਂਦੇ ਹੋ। ਸ਼ੁਰੂ ਕਰੋ ਉਥੇ ਜਿੱਥੇ ਟੈਸਟ ਲੰਬੇ ਜਾਂ flaky ਹਨ, ਅਤੇ ਜਿੱਥੇ ਛੋਟੇ ਬਦਲਾਅ ਅਣ-ਸੰਬੰਧਿਤ ਕੋਡ ਨੂੰ ਪ੍ਰਭਾਵਿਤ ਕਰ ਰਹੇ ਹਨ।
ਉਹ dependencies ਲੱਭੋ ਜੋ ਕੋਡ ਨੂੰ ਟੈਸਟ ਕਰਨ ਜਾਂ ਸਮਝਣ ਵਿੱਚ ਔਖਾ ਬਣਾਉਂਦੀਆਂ ਹਨ:
ਜੇ ਕੋਈ function ਪ੍ਰੋਸੈਸ ਰਨ ਕਰਨ ਲਈ process ਤੋਂ ਬਾਹਰ ਜਾਣਾ ਪੈਂਦਾ ਹੈ, ਤਾਂ ਉਹ ਆਮ ਤੌਰ 'ਤੇ ਇੱਕ ਵਧੀਆ ਉਮੀਦਵਾਰ ਹੈ।
ਇਹ ਢੰਗ ਹਰ ਬਦਲਾਅ ਨੂੰ ਸਮੀਖਿਆਯੋਗ ਰੱਖਦਾ ਹੈ ਅਤੇ ਕਿਸੇ ਵੀ ਕਦਮ 'ਤੇ ਰੁਕ ਕੇ ਬਿਨਾਂ ਸਿਸਟਮ ਟੁੱਟੇ ਰੋਕਿਆ ਜਾ ਸਕਦਾ ਹੈ।
DI ਅਕਸਰ ਇਹ ਗਲਤੀ ਕਰ ਸਕਦਾ ਹੈ ਕਿ code "ਸਭ ਕੁਝ-ਹਰੇਕ-ਉੱਪਰ ਨਿਰਭਰ" ਬਣ ਜਾਵੇ ਜੇ ਤੁਸੀਂ ਬੇਹਦ inject ਕਰਦੇ ਹੋ।
ਇੱਕ ਵਧੀਆ ਨਿਯਮ: capabilities inject ਕਰੋ, details ਨਹੀਂ। ਉਦਾਹਰਣ ਵੱਜੋਂ, Clock inject ਕਰੋ ਨਾ ਕਿ "SystemTime + TimeZoneResolver + NtpClient"। ਜੇ ਇਕ class ਨੂੰ ਪੰਜ ਅਸੰਬੰਧਿਤ ਸੇਵਾਵਾਂ ਦੀ ਲੋੜ ਹੈ, ਤਾਂ ਉਹ ਹੌਲਤੋਂ-ਹੌਲਤੋਂ ਬਹੁਤ ਕੁਝ ਕਰ ਰਹੀ ਹੈ—ਵੰਡ ਬਾਰੇ ਸੋਚੋ।
ਇਸਦੇ ਨਾਲ-ਨਾਲ, dependencies ਨੂੰ ਬਹੁ-ਸਤਰਾਂ ਵਿੱਚ "ਜਸਟ ਇਨ ਕੇਸ" ਪਾਸ ਨਾ ਕਰੋ। ਜਿੱਥੇ ਵਰਤੀ ਜਾ ਰਹੀ ਹੈ ਓਥੇ inject ਕਰੋ; wiring ਨੂੰ ਇੱਕ ਜਗ੍ਹਾ ਕੇਂਦ੍ਰਿਤ ਕਰੋ।
ਜੇ ਤੁਸੀਂ code generator ਜਾਂ "vibe-coding" ਵਰਕਫ਼ਲੋ ਵਰਤ ਰਹੇ ਹੋ features ਨੂੰ ਤੇਜ਼ੀ ਨਾਲ ਬਣਾਉਣ ਲਈ, DI ਹੋਰ ਵੀ ਜ਼ਰੂਰੀ ਬਣ ਜਾਂਦਾ ਹੈ ਕਿਉਂਕਿ ਉਹ ਬਣਤਰ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਦਾ ਹੈ ਜਿਵੇਂ ਪਰੋਜੈਕਟ ਵਧਦਾ ਹੈ। ਉਦਾਹਰਣ ਲਈ, ਜਦੋਂ ਟੀਮਾਂ Koder.ai ਵਰਤ ਕੇ React frontends, Go services, ਅਤੇ PostgreSQL-backed backends chat-driven spec ਤੋਂ ਬਣਾਉਂਦੀਆਂ ਹਨ, composition root ਅਤੇ DI-friendly interfaces ਰਖਣ ਨਾਲ generated code ਟੈਸਟ, ਰੀਫੈਕਟਰ, ਅਤੇ ਇੰਟੀਗਰੇਸ਼ਨ swap ਕਰਨਯੋਗ ਰਹਿੰਦਾ ਹੈ ਬਿਨਾਂ ਬਿਜਨਸ ਲੌਜਿਕ ਨੂੰ ਦੁਬਾਰਾ ਲਿਖੇ।
ਮੁਢਲਾ ਨਿਯਮ ਇੱਕੋ ਹੀ ਹੈ: object creation ਅਤੇ environment-specific wiring ਨੂੰ ਬਾਊਂਡਰੀ 'ਤੇ ਰੱਖੋ, ਅਤੇ business code ਨੂੰ ਵਿਵਹਾਰ 'ਤੇ ਕੇਂਦ੍ਰਿਤ ਰੱਖੋ।
ਤੁਹਾਨੂੰ ਈ-ਜਹਾਂ ਨਤੀਜੇ ਦਿਖਾਉਣ ਯੋਗ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ:
ਅਗਲਾ ਕਦਮ: ਆਪਣਾ "composition root" ਡੌਕਯੂਮੇਂਟ ਕਰੋ ਅਤੇ ਇਸਨੂੰ ਇੱਕ ਫ਼ਾਈਲ ਵਿੱਚ ਰੱਖੋ: ਇੱਕ ਜਗ੍ਹਾ ਜੋ dependencies ਨੂੰ ਵਾਇਰ ਕਰਦੀ ਹੈ, ਬਾਕੀ ਕੋਡ ਵਿਵਹਾਰ ਉੱਪਰ ਕੇਂਦ੍ਰਿਤ ਰਹਿੰਦਾ ਹੈ।
Dependency Injection (DI) ਦਾ ਮਤਲਬ ਹੈ ਕਿ ਤੁਹਾਡਾ ਕੋਡ ਉਹ ਚੀਜ਼ਾਂ ਬਾਹਰੋਂ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ ਜਿਨ੍ਹਾਂ ਦੀ ਉਸਨੂੰ ਲੋੜ ਹੁੰਦੀ ਹੈ (ਡੇਟਾਬੇਸ, ਲੌਗਰ, ਘੜੀ, ਪੇਮੈਂਟ ਕਲਾਇੰਟ) ਨਾ ਕਿ ਉਹਨਾਂ ਨੂੰ ਅੰਦਰੋਂ ਬਣਾਉਂਦਾ ਹੋਵੇ।
ਆਮ ਤੌਰ 'ਤੇ ਇਹ ਕੁਝ ਇਸ ਤਰ੍ਹਾਂ ਦਿਖਦਾ ਹੈ: ਡਿਪੈਂਡੈਂਸੀ ਨੂੰ constructor ਜਾਂ function ਪੈਰਾਮੀਟਰ ਦੁਆਰਾ ਪਾਸ ਕਰਨਾ ਤਾਂ ਜੋ ਉਹ ਸਪਸ਼ਟ ਅਤੇ ਬਦਲਣ ਯੋਗ ਹੋਣ।
Inversion of Control (IoC) ਵੱਡਾ ਆਈਡੀਆ ਹੈ: ਇੱਕ ਕਲਾਸ ਨੂੰ ਇਹਦੇ ਕੰਮ 'ਤੇ ਧਿਆਨ ਦੇਣਾ ਚਾਹੀਦਾ ਹੈ, ਨਾ ਕਿ ਇਹਦੀਆਂ ਸਹਾਇਕ ਚੀਜ਼ਾਂ ਕਿਵੇਂ ਪ੍ਰਾਪਤ ਹੋਂਦੀਆਂ ਹਨ।
DI ਇੱਕ ਆਮ ਤਕਨੀਕ ਹੈ ਜੋ ਇਹ ਯਕੀਨੀ ਬਣਾਉਂਦੀ ਹੈ ਕਿ ਡਿਪੈਂਡੈਂਸੀ ਬਣਾਉਣ ਦੀ ਜ਼ਿੰਮੇਵਾਰੀ ਬਾਹਰ ਵਲ ਰਹੇ ਅਤੇ ਉਹਨਾਂ ਨੂੰ ਇੰਜੈਕਟ ਕੀਤਾ ਜਾਵੇ।
ਜਦੋਂ ਬਿਜਨਸ ਲੌਜਿਕ ਦੇ ਅੰਦਰ ਹੀ new ਨਾਲ ਕੋਈ ਡਿਪੈਂਡੈਂਸੀ ਬਣਾਈ ਜਾਂਦੀ ਹੈ, ਤਾਂ ਉਸਨੂੰ ਬਦਲਣਾ ਮੁਸ਼ਕਲ ਹੋ ਜਾਂਦਾ ਹੈ।
ਇਸਦੇ ਨਤੀਜੇ:
DI ਟੈਸਟਾਂ ਨੂੰ ਤੇਜ਼ ਅਤੇ ਡਿਟਰਮਿਨਿਸਟਿਕ ਬਣਾਉਂਦਾ ਹੈ ਕਿਉਂਕਿ ਤੁਸੀਂ ਅਸਲ ਬਾਹਰੀ ਸਿਸਟਮਾਂ ਦੀ ਥਾਂ ਟੈਸਟ ਡਬਲ inject ਕਰ ਸਕਦੇ ਹੋ।
ਆਮ ਤਬਦੀਲੀਆਂ:
DI ਕੰਟੇਨਰ ਵਿਕਲਪਿਕ ਹੈ। ਸ਼ੁਰੂਆਤ manual DI ਨਾਲ ਕਰੋ (ਡਿਪੈਂਡੈਂਸੀ ਨੂੰ ਖੁੱਲ੍ਹ ਕੇ ਪਾਸ ਕਰਨਾ) ਜਦੋਂ:
ਜਦੋਂ ਵਾਇਰਿੰਗ ਰਿਪੀਟੀਟਿਵ ਹੋ ਜਾਂਦੀ ਹੈ ਜਾਂ ਲਾਈਫਸਾਈਕਲ ਮੈਨੇਜਮੈਂਟ ਲੋੜੀਂਦਾ ਹੈ, ਤਾਂ DI ਕੰਟੇਨਰ 'ਤੇ ਵਿਚਾਰ ਕਰੋ।
ਜਦੋਂ dependency ਆਬਜੈਕਟ ਦੇ ਕੰਮ ਲਈ ਜ਼ਰੂਰੀ ਹੋਵੇ ਅਤੇ ਕਈ ਮੈਥਡਾਂ ਵਿੱਚ ਵਰਤੀ ਜਾਂਦੀ ਹੋਵੇ — ਤਾਂ constructor injection ਵਰਤੋਂ।
ਜੇ dependency ਸਿਰਫ ਇਕ ਕਾਲ ਲਈ ਚਾਹੀਦੀ ਹੋਵੇ — ਤਾਂ method/parameter injection ਵਰਤੋਂ।
setter/property injection ਤਦੋਂ ਵਰਤੋ ਜਦੋਂ late wiring ਲਾਜ਼ਮੀ ਹੋਵੇ; ਫੇਲ-ਫਾਸਟ ਵੈਲੀਡੇਸ਼ਨ ਜੋੜੋ ਤਾਂ ਕਿ ਗਲਤੀ ਆਸਾਨੀ ਨਾਲ ਪਤਾ ਲੱਗੇ।
Composition root ਉਹ ਥਾਂ ਹੈ ਜਿੱਥੇ ਤੁਸੀਂ ਐਪਲੀਕੇਸ਼ਨ ਨੂੰ ਇਕਠਾ ਕਰਦੇ ਹੋ: ਅਸਲ implementations ਬਣਾਉਣ ਤੇ ਉਹਨਾਂ ਨੂੰ ਸੇਵਾ ਵਿੱਚ ਪਾਸ ਕਰਨ ਦੀ ਜ਼ਿੰਮੇਵਾਰੀ।
ਇਹ ਆਮ ਤੌਰ 'ਤੇ ਐਪਲੀਕੇਸ਼ਨ ਦੇ startup (entry point) ਕੋਡ ਕੋਲ ਰੱਖੋ ਤਾਂ ਕਿ ਬਾਕੀ ਕੋਡ ਵਿਹਾਰ 'ਤੇ ਕੇਂਦ੍ਰਿਤ ਰਹੇ, ਵਾਇਰਿੰਗ 'ਤੇ ਨਹੀਂ।
ਟੈਸਟ ਸੀਮ (test seam) ਉਹ ਨਿਆਰਾ ਬਿੰਦੂ ਹੈ ਜਿੱਥੇ ਵਿਵਹਾਰ ਨੂੰ बदलਿਆ ਜਾ ਸਕਦਾ ਹੈ।
ਚੰਗੀਆਂ ਜਗ੍ਹਾਂ ਜਿੱਥੇ ਸੀਮ ਬਣਾਉਣੀ ਚਾਹੀਦੀ ਹੈ:
Clock.now())DI ਇਨਿ੍ਹਾਂ ਸੀਮਾਂ ਨੂੰ ਸਾਦਾ ਬਣਾਉਂਦਾ ਹੈ ਤਾਂ ਕਿ ਟੈਸਟਾਂ ਵਿੱਚ ਇੱਕ ਬਦਲਣ ਯੋਗ implementation inject ਕੀਤੀ ਜਾਵੇ।
ਆਮ ਗਲਤੀਆਂ:
container.get() ਨੂੰ ਬਿਜਨਸ ਕੋਡ ਵਿੱਚ ਕਾਲ ਕਰਨਾ ਡਿਪੈਂਡੈਂਸੀ ਨੂੰ ਅਦ੍ਰਿਸ਼ਯ ਕਰਦਾ ਹੈ; explicit constructor injection ਚੰਗਾ ਹੈ।ਛੋਟੀ, ਦੁਹਰਾਏ ਜਾ ਸਕਣ ਵਾਲੀ ਰਿਫੈਕਟਰਿੰਗ ਨਾਲ DI ਨੂੰ ਧੀਰੇ-ਧੀਰੇ ਲੈ ਆਓ:
ਇਹ ਤਰੀਕਾ ਹਰ ਕਦਮ 'ਤੇ ਰਿਵਰਟ ਕਰਨਯੋਗ ਹੈ ਅਤੇ ਕਿਸੇ ਵੱਡੇ ਰੀਰਾਈਟ ਦੀ ਲੋੜ ਨਹੀਂ ਪੈਂਦੀ।