নির্ভরতা ইনজেকশন কীভাবে ব্যবহার করবেন এবং টেম্পোরাল কাপলিং এড়ানো যায়?


11

ধরুন আমার কাছে এটি Serviceকনস্ট্রাক্টরের মাধ্যমে নির্ভরতা পেয়েছে তবে এটি ব্যবহারের আগে কাস্টম ডেটা (প্রসঙ্গ) দিয়েও আরম্ভ করা দরকার:

public interface IService
{
    void Initialize(Context context);
    void DoSomething();
    void DoOtherThing();
}

public class Service : IService
{
    private readonly object dependency1;
    private readonly object dependency2;
    private readonly object dependency3;

    public Service(
        object dependency1,
        object dependency2,
        object dependency3)
    {
        this.dependency1 = dependency1 ?? throw new ArgumentNullException(nameof(dependency1));
        this.dependency2 = dependency2 ?? throw new ArgumentNullException(nameof(dependency2));
        this.dependency3 = dependency3 ?? throw new ArgumentNullException(nameof(dependency3));
    }

    public void Initialize(Context context)
    {
        // Initialize state based on context
        // Heavy, long running operation
    }

    public void DoSomething()
    {
        // ...
    }

    public void DoOtherThing()
    {
        // ...
    }
}

public class Context
{
    public int Value1;
    public string Value2;
    public string Value3;
}

এখন - প্রাসঙ্গিক ডেটা আগে জানা ছিল না তাই আমি এটিকে নির্ভরতা হিসাবে নিবন্ধন করতে পারি না এবং পরিষেবাতে ইনজেক্ট করতে ডিআই ব্যবহার করতে পারি না

উদাহরণ ক্লায়েন্টকে এরকম দেখাচ্ছে:

public class Client
{
    private readonly IService service;

    public Client(IService service)
    {
        this.service = service ?? throw new ArgumentNullException(nameof(service));
    }

    public void OnStartup()
    {
        service.Initialize(new Context
        {
            Value1 = 123,
            Value2 = "my data",
            Value3 = "abcd"
        });
    }

    public void Execute()
    {
        service.DoSomething();
        service.DoOtherThing();
    }
}

আপনি দেখতে পাচ্ছেন - এখানে অস্থায়ী সংযুক্তি রয়েছে এবং পদ্ধতি কোডের সাথে গন্ধ জড়িত রয়েছে, কারণ আমাকে প্রথমে কল service.Initializeকরতে service.DoSomethingএবং service.DoOtherThingতারপরে কল করতে হবে ।

অন্যান্য সমস্যাগুলি যা আমি এই সমস্যাগুলি দূর করতে পারি?

আচরণের অতিরিক্ত ব্যাখ্যা:

ক্লায়েন্টের প্রতিটি উদাহরণের পরিষেবাটির নিজস্ব উদাহরণটি ক্লায়েন্টের নির্দিষ্ট প্রসঙ্গের ডেটা দিয়ে শুরু করা দরকার। সুতরাং, সেই প্রসঙ্গে ডেটা স্থির বা আগে থেকে জানা যায় না তাই এটি ডিআই দ্বারা কনস্ট্রাক্টারে ইনজেকশন দেওয়া যায় না।

উত্তর:


18

ইনিশিয়েশন সমস্যা মোকাবেলা করার বিভিন্ন উপায় রয়েছে:

  • Https://softwareengineering.stackexchange.com/a/334994/301401 তে উত্তর হিসাবে , init () পদ্ধতিগুলি একটি কোড গন্ধ। কোনও অবজেক্টের সূচনা করা কনস্ট্রাক্টরের দায়িত্ব that's এজন্য আমাদের পরেও কনস্ট্রাক্টর রয়েছে।
  • যোগ করুন প্রদত্ত পরিষেবাটি অবশ্যইClient কনস্ট্রাক্টরের ডক মন্তব্যে আরম্ভ করতে হবে এবং পরিষেবাটি আরম্ভ না করা হলে কনস্ট্রাক্টরকে ফেলে দিতে হবে। যিনি আপনাকে IServiceঅবজেক্টটি দেন তার কাছে এই দায়িত্বটি সরিয়ে দেয় ।

তবে, আপনার উদাহরণে, Clientকেবলমাত্র সেই মানগুলিই জানে যেগুলি উত্তীর্ণ হয়েছে Initialize()। আপনি যদি সেভাবেই রাখতে চান তবে আমি নীচের পরামর্শ দেব:

  • একটি যুক্ত করুন IServiceFactoryএবং এটি Clientকনস্ট্রাক্টরে পাস করুন । তারপরে আপনি কল করতে পারেন serviceFactory.createService(new Context(...))যা আপনাকে এমন একটি সূচনা দেয় যা IServiceআপনার ক্লায়েন্ট ব্যবহার করতে পারেন।

কারখানাগুলি খুব সহজ হতে পারে এবং আপনাকে init () পদ্ধতিগুলি এড়াতে এবং পরিবর্তে নির্মাতাদের ব্যবহার করতে দেয়:

public interface IServiceFactory
{
    IService createService(Context context);
}

public class ServiceFactory : IServiceFactory
{
    public Service createService(Context context)
    {
        return new Service(context);
    }
}

ক্লায়েন্টে, OnStartup()এটি একটি সূচনা পদ্ধতিও (এটি কেবল আলাদা নাম ব্যবহার করে)। সুতরাং যদি সম্ভব হয় (আপনি যদি Contextডেটা জানেন ), কারখানাটি সরাসরি Clientকনস্ট্রাক্টরে ডাকা উচিত । যদি এটি সম্ভব না হয় তবে IServiceFactoryআপনার এটি স্টোর করে কল করতে হবে OnStartup()

কখন Serviceতাদের দ্বারা নির্ভরতা সরবরাহ করা হয়নি Clientতা ডিআই দ্বারা সরবরাহ করা হবে ServiceFactory:

public interface IServiceFactory
{
    IService createService(Context context);
}    

public class ServiceFactory : IServiceFactory
{        
    private readonly object dependency1;
    private readonly object dependency2;
    private readonly object dependency3;

    public ServiceFactory(object dependency1, object dependency2, object dependency3)
    {
        this.dependency1 = dependency1;
        this.dependency2 = dependency2;
        this.dependency3 = dependency3;
    }

    public Service createService(Context context)
    {
        return new Service(context, dependency1, dependency2, dependency3);
    }
}

1
আপনাকে ধন্যবাদ, ঠিক যেমনটি আমি শেষ পয়েন্টে ভেবেছিলাম ... এবং সার্ভিস ফ্যাক্টরিতে, আপনি কি কারখানায় কনস্ট্রাক্টর ডিআই ব্যবহার করবেন সার্ভিস কনস্ট্রাক্টরের জন্য প্রয়োজনীয় নির্ভরতার জন্য বা সার্ভিস লোকেটারটি আরও উপযুক্ত হবে?
দুসান

1
@ দুসান সার্ভিস লোকেটার ব্যবহার করবেন না। তাহলে Serviceআর অন্য নির্ভরতা রয়েছে Contextযে দ্বারা উপলব্ধ করা হবে না, Clientকরে, তখন তাদেরকে দ্বি মাধ্যমে প্রদান করা যেতে পারে ServiceFactoryএ পাস করার Serviceযখন createServiceবলা হয়।
মিঃমিন্দর

@ দুসান যদি আপনাকে বিভিন্ন পরিষেবায় বিভিন্ন নির্ভরতা সরবরাহ করতে হয় (যেমন: এটির উপর নির্ভরশীলতা প্রয়োজন 1_1 তবে পরেরটির নির্ভরতা 1_2 প্রয়োজন), তবে যদি এই প্যাটার্নটি অন্যথায় আপনার জন্য কাজ করে, তবে আপনি প্রায়শই বিল্ডার প্যাটার্ন নামে পরিচিত একটি অনুরূপ প্যাটার্ন ব্যবহার করতে পারেন। একজন বিল্ডার আপনাকে প্রয়োজনবোধে সময়ের সাথে একটি অবজেক্ট টুকরোচাল সেট আপ করতে দেয়। তারপরে আপনি এটি করতে পারেন ... ServiceBuilder partial = new ServiceBuilder().dependency1(dependency1_1).dependency2(dependency2_1).dependency3(dependency3_1);এবং আপনার আংশিক সেট আপ করা পরিষেবাটি রেখে যেতে হবে , তারপরে পরে করুনService s = partial.context(context).build()
হারুন

1

Initializeপদ্ধতি থেকে অপসারণ করা উচিত IService, ইন্টারফেস হিসাবে এই একটি বাস্তবায়ন বিস্তারিত হয়। পরিবর্তে, অন্য শ্রেণীর সংজ্ঞা দিন যা পরিষেবার কংক্রিট উদাহরণ গ্রহণ করে এবং এতে আরম্ভ পদ্ধতিটি কল করে calls তারপরে এই নতুন শ্রেণিটি আইএসওয়্যার ইন্টারফেস প্রয়োগ করে:

public class ContextDependentService : IService
{
    public ContextDependentService(Context context, Service service)
    {
        this.service = service;

        service.Initialize(context);
    }

    // Methods in the IService interface
}

এটি ContextDependentServiceক্লাসটি আরম্ভ করা ব্যতীত ক্লায়েন্ট কোডটিকে আরম্ভকরণ প্রক্রিয়া সম্পর্কে অজ্ঞ রাখে । আপনি কমপক্ষে আপনার অ্যাপ্লিকেশনটির যে অংশগুলিকে এই চমকপ্রদ ইনিশিয়ালাইজেশন পদ্ধতি সম্পর্কে জানতে হবে তা সীমাবদ্ধ করুন।


1

আমার কাছে মনে হচ্ছে আপনার এখানে দুটি বিকল্প রয়েছে

  1. প্রারম্ভিকেশন কোডটি প্রসঙ্গে সরিয়ে দিন এবং একটি সূচনা প্রসঙ্গে ইনজেক্ট করুন

যেমন।

public InitialisedContext Initialise()
  1. যদি অলরেডি না হয়ে থাকে তবে কল ইনিশিয়ালিকে এক্সিকিউট করার প্রথম কল করুন

যেমন।

public async Task Execute()
{
     //lock context
     //check context is not initialised
     // init if required
     //execute code...
}
  1. আপনি যখন এক্সিকিউটিভ কল করবেন তখন প্রসঙ্গটি আরম্ভ না করা হলে কেবল ব্যতিক্রমগুলি ছুঁড়ে ফেলুন। এসকিএল সংযোগের মতো।

কারখানাটি ইনজেকশন করা ঠিক আছে যদি আপনি কেবলমাত্র প্যারামিটার হিসাবে প্রসঙ্গটি এড়িয়ে চলতে চান। বলুন কেবলমাত্র এই নির্দিষ্ট প্রয়োগের জন্য একটি প্রসঙ্গ প্রয়োজন এবং আপনি এটি ইন্টারফেসে যুক্ত করতে চান না

তবে আপনার মূলত একই সমস্যা রয়েছে, যদি কারখানাটি এখনও প্রাথমিক সূত্র না পেয়ে থাকে।


0

আপনার ইন্টারফেসটি কোনও ডিবি প্রসঙ্গে নির্ভর করতে হবে না এবং পদ্ধতিটি আরম্ভ করা উচিত নয়। আপনি এটি কংক্রিট বর্গ নির্মাণে করতে পারেন।

public interface IService
{
    void DoSomething();
    void DoOtherThing();
}

public class Service : IService
{
    private readonly object dependency1;
    private readonly object dependency2;
    private readonly object dependency3;
    private readonly object context;

    public Service(
        object dependency1,
        object dependency2,
        object dependency3,
        object context )
    {
        this.dependency1 = dependency1 ?? throw new ArgumentNullException(nameof(dependency1));
        this.dependency2 = dependency2 ?? throw new ArgumentNullException(nameof(dependency2));
        this.dependency3 = dependency3 ?? throw new ArgumentNullException(nameof(dependency3));

        // context is concrete class details not interfaces.
        this.context = context;

        // call init here constructor.
        this.Initialize(context);
    }

    protected void Initialize(Context context)
    {
        // Initialize state based on context
        // Heavy, long running operation
    }

    public void DoSomething()
    {
        // ...
    }

    public void DoOtherThing()
    {
        // ...
    }
}

এবং, আপনার মূল প্রশ্নের উত্তর হবে সম্পত্তি ইনজেকশন

public class Service
    {
        public Service(Context context)
        {
            this.context = context;
        }

        private Dependency1 _dependency1;
        public Dependency1 Dependency1
        {
            get
            {
                if (_dependency1 == null)
                    _dependency1 = Container.Resolve<Dependency1>();

                return _dependency1;
            }
        }

        //...
    }

এইভাবে আপনি সম্পত্তি ইনজেকশন দ্বারা সমস্ত নির্ভরতা কল করতে পারেন । তবে এটি বিশাল সংখ্যা হতে পারে। যদি তা হয় তবে আপনি তাদের জন্য কনস্ট্রাক্টর ইনজেকশন ব্যবহার করতে পারেন, তবে সম্পত্তিটি অনুসারে আপনার প্রসঙ্গটি নাল কিনা তা পরীক্ষা করে সেট করতে পারেন set


ঠিক আছে, দুর্দান্ত, তবে ... ক্লায়েন্টের প্রতিটি উদাহরণের নিজস্ব প্রবন্ধের পরিষেবাটি বিভিন্ন প্রসঙ্গের ডেটা দিয়ে শুরু করা দরকার। সেই প্রসঙ্গে ডেটা স্থির বা আগে থেকে জানা যায়নি তাই এটি ডিআই দ্বারা কনস্ট্রাক্টারে ইনজেকশন দেওয়া যায় না। তারপরে, আমি কীভাবে আমার ক্লায়েন্টগুলির অন্যান্য নির্ভরতার সাথে পরিষেবাটির উদাহরণ পাব / তৈরি করব?
দুসান

আপনি প্রসঙ্গটি নির্ধারণের আগে এই স্থির নির্মাতা চালাবেন না হুম? এবং কনস্ট্রাক্টরের ঝুঁকি ব্যতিক্রমগুলি শুরু করুন
ইওয়ান

আমি ইনজেকশন ফ্যাক্টরির দিকে ঝুঁকছি যা প্রদত্ত প্রসঙ্গ ডেটা (পরিষেবাটি নিজেই ইনজেকশন দেওয়ার চেয়ে) দিয়ে পরিষেবাটি তৈরি এবং সূচনা করতে পারে তবে এর থেকে আরও ভাল সমাধান আছে কিনা তা সম্পর্কে আমি নিশ্চিত নই।
দুসান

এমওয়ান আপনি ঠিক বলেছেন আমি এর সমাধান খুঁজতে চেষ্টা করব। তবে তার আগে, আমি আপাতত এটি সরিয়ে ফেলব।
প্রকৌশলী

0

আপনার মুখোমুখি হওয়া মামলাটি সম্পর্কে মিসকো হেভির একটি খুব সহায়ক ব্লগ পোস্ট রয়েছে। আপনি উভয় প্রয়োজন newable এবং ইঞ্জেকশনভিত্তিক আপনার জন্য Serviceশ্রেণী এবং এই ব্লগ পোস্টে আপনি সাহায্য করতে পারে।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.