سؤال

أحاول أن أجعل Unity تدير عملية إنشاء الكائنات الخاصة بي وأريد الحصول على بعض معلمات التهيئة غير المعروفة حتى وقت التشغيل:

في الوقت الحالي، الطريقة الوحيدة التي يمكنني التفكير بها للقيام بذلك هي وجود طريقة Init على الواجهة.

interface IMyIntf {
  void Initialize(string runTimeParam);
  string RunTimeParam { get; }
}

ثم لاستخدامه (في الوحدة) سأفعل هذا:

var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");

في هذا السيناريو runTimeParam يتم تحديد المعلمة في وقت التشغيل بناءً على إدخال المستخدم.الحالة التافهة هنا تُرجع ببساطة قيمة runTimeParam ولكن في الواقع ستكون المعلمة مثل اسم الملف وستقوم طريقة التهيئة بعمل شيء ما مع الملف.

وهذا يخلق عددا من القضايا، وهي أن Initialize الطريقة متاحة على الواجهة ويمكن استدعاؤها عدة مرات.وضع علامة في التنفيذ وطرح استثناء عند الاتصال المتكرر بـ Initialize يبدو عالي الكعب الطريق.

عند النقطة التي أحل فيها واجهتي، لا أريد أن أعرف أي شيء عن تنفيذ IMyIntf.لكن ما أريده هو معرفة أن هذه الواجهة تحتاج إلى معلمات تهيئة معينة لمرة واحدة.هل هناك طريقة لتعليق (السمات؟) بطريقة أو بأخرى الواجهة بهذه المعلومات وتمريرها إلى إطار العمل عند إنشاء الكائن؟

يحرر:وصف الواجهة أكثر قليلاً.

هل كانت مفيدة؟

المحلول

أي مكان تحتاج فيه إلى قيمة وقت التشغيل لبناء تبعية معينة ، مصنع مجردة هو الحل.

بعد تهيئة الطرق على واجهات رائحة أ التجريد المتسرب.

في حالتك ، أود أن أقول أنه يجب عليك تصميم IMyIntf واجهة على كيف تحتاج إلى استخدامه - ليس كيف تنوي إنشاء تطبيقات منها. هذه تفاصيل التنفيذ.

وبالتالي ، يجب أن تكون الواجهة ببساطة:

public interface IMyIntf
{
    string RunTimeParam { get; }
}

حدد الآن المصنع التجريدي:

public interface IMyIntfFactory
{
    IMyIntf Create(string runTimeParam);
}

يمكنك الآن إنشاء تطبيق ملموس IMyIntfFactory الذي يخلق مثيلات ملموسة IMyIntf مثل هذه:

public class MyIntf : IMyIntf
{
    private readonly string runTimeParam;

    public MyIntf(string runTimeParam)
    {
        if(runTimeParam == null)
        {
            throw new ArgumentNullException("runTimeParam");
        }

        this.runTimeParam = runTimeParam;
    }

    public string RunTimeParam
    {
        get { return this.runTimeParam; }
    }
}

لاحظ كيف يسمح هذا لنا حماية الثوابات الطبقية عن طريق استخدام readonly الكلمة الرئيسية. لا توجد طرق تهيئة كريهة الرائحة ضرورية.

و IMyIntfFactory قد يكون التنفيذ بسيطًا مثل هذا:

public class MyIntfFactory : IMyIntfFactory
{
    public IMyIntf Create(string runTimeParam)
    {
        return new MyIntf(runTimeParam);
    }
}

في جميع المستهلكين حيث تحتاج إلى IMyIntf على سبيل المثال ، يمكنك ببساطة الاعتماد عليها IMyIntfFactory عن طريق طلب ذلك من خلال حقن المنشئ.

أي حاوية DI تستحق الملح ستكون قادرة على الحصول على توصيل تلقائي IMyIntfFactory مثيل لك إذا قمت بتسجيله بشكل صحيح.

نصائح أخرى

عادةً عندما تواجه هذا الموقف ، تحتاج إلى إعادة النظر في التصميم وتحديد ما إذا كنت تمزج كائنات البيانات/البيانات الخاصة بك مع خدماتك الخالصة. في معظم الحالات (وليس كل) ، ستحتاج إلى الحفاظ على هذين النوعين من الكائنات منفصلة.

إذا كنت بحاجة إلى معلمة خاصة بالسياق تم تمريرها في المُنشئ ، فإن أحد الخيارات هو إنشاء مصنع يحل تبعيات الخدمة الخاصة بك عبر المُنشئ ، ويأخذ معلمة وقت التشغيل كمعلمة لطريقة إنشاء () (أو إنشاء ( ) ، بناء () أو أي شيء تسمي أساليب المصنع الخاصة بك).

وجود المستقلين أو طريقة التهيئة () عموما يُعتقد أنه تصميم سيء ، حيث تحتاج إلى "تذكر" الاتصال بهم والتأكد من عدم فتح الكثير من حالة تطبيقك (أي ما الذي يمنع شخص ما من إعادة التهيئة أو الضابط؟).

لقد واجهت هذا الموقف أيضًا عدة مرات في البيئات التي أقوم فيها بإنشاء كائنات ViewModel ديناميكيًا استنادًا إلى كائنات النموذج (المحددة جيدًا من خلال هذا الآخر Stackoverflow Post).

أحببت كيف تمديد تسعة الذي يسمح لك بإنشاء مصانع ديناميكية على أساس الواجهات:

Bind<IMyFactory>().ToFactory();

لم أتمكن من العثور على أي وظيفة مماثلة مباشرة في وحدة; ؛ لذلك كتبت امتداد بلدي إلى IunityContainer مما يتيح لك تسجيل المصانع التي ستنشئ كائنات جديدة بناءً على بيانات من الكائنات الموجودة بشكل أساسي من التسلسل الهرمي إلى نوع واحد إلى التسلسل الهرمي نوع مختلف: UnityMappingFactory@github

بهدف البساطة وقابلية القراءة ، انتهى بي الأمر بامتداد يسمح لك بتحديد التعيينات مباشرة دون إعلان فئات المصنع الفردية أو الواجهات (توفير الوقت الحقيقي). يمكنك فقط إضافة التعيينات مباشرة حيث تقوم بتسجيل الفصول أثناء عملية التمهيد العادية ...

//make sure to register the output...
container.RegisterType<IImageWidgetViewModel, ImageWidgetViewModel>();
container.RegisterType<ITextWidgetViewModel, TextWidgetViewModel>();

//define the mapping between different class hierarchies...
container.RegisterFactory<IWidget, IWidgetViewModel>()
.AddMap<IImageWidget, IImageWidgetViewModel>()
.AddMap<ITextWidget, ITextWidgetViewModel>();

ثم تعلن فقط واجهة مصنع رسم الخرائط في مُنشئ CI واستخدامها خلق() طريقة...

public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { }

public TextWidgetViewModel(ITextWidget widget) { }

public ContainerViewModel(object data, IFactory<IWidget, IWidgetViewModel> factory)
{
    IList<IWidgetViewModel> children = new List<IWidgetViewModel>();
    foreach (IWidget w in data.Widgets)
        children.Add(factory.Create(w));
}

كمكافأة إضافية ، سيتم أيضًا حل أي تبعيات إضافية في مُنشئ الفئات المعينة أثناء إنشاء الكائن.

من الواضح أن هذا لن يحل كل مشكلة ، لكنه خدمني جيدًا حتى الآن ، لذا اعتقدت أنني يجب أن أشاركها. هناك المزيد من الوثائق على موقع المشروع على جيثب.

لا يمكنني الإجابة بمصطلحات Unity محددة ولكن يبدو أنك تتعلم للتو عن حقن التبعية.إذا كان الأمر كذلك، فإنني أحثك ​​على قراءة الموجز والواضح والمليء بالمعلومات دليل المستخدم لNinject.

سيوجهك هذا عبر الخيارات المتنوعة المتاحة لك عند استخدام DI، وكيفية مراعاة المشكلات المحددة التي ستواجهها على طول الطريق.في حالتك، قد ترغب على الأرجح في استخدام حاوية DI لإنشاء مثيل للكائنات الخاصة بك، وجعل هذا الكائن يحصل على مرجع لكل من تبعياته من خلال المُنشئ.

توضح الإرشادات التفصيلية أيضًا كيفية إضافة تعليقات توضيحية إلى الأساليب والخصائص وحتى المعلمات باستخدام السمات لتمييزها في وقت التشغيل.

حتى إذا كنت لا تستخدم Ninject، فإن الإرشادات التفصيلية ستمنحك المفاهيم والمصطلحات الخاصة بالوظيفة التي تناسب غرضك، ويجب أن تكون قادرًا على ربط هذه المعرفة بـ Unity أو أطر عمل DI الأخرى (أو إقناعك بتجربة Ninject). .

أعتقد أنني حلتها وأشعر بصحة جيدة ، لذلك يجب أن يكون نصف الحق :))

انقسمت IMyIntf في "getter" واجهات "setter". لذا:

interface IMyIntf {
  string RunTimeParam { get; }
}


interface IMyIntfSetter {
  void Initialize(string runTimeParam);
  IMyIntf MyIntf {get; }
}

ثم التنفيذ:

class MyIntfImpl : IMyIntf, IMyIntfSetter {
  string _runTimeParam;

  void Initialize(string runTimeParam) {
    _runTimeParam = runTimeParam;
  }

  string RunTimeParam { get; }

  IMyIntf MyIntf {get {return this;} }
}

//Unity configuration:
//Only the setter is mapped to the implementation.
container.RegisterType<IMyIntfSetter, MyIntfImpl>();
//To retrieve an instance of IMyIntf:
//1. create the setter
IMyIntfSetter setter = container.Resolve<IMyIntfSetter>();
//2. Init it
setter.Initialize("someparam");
//3. Use the IMyIntf accessor
IMyIntf intf = setter.MyIntf;

IMyIntfSetter.Initialize() لا يزال من الممكن استدعاء عدة مرات ولكن باستخدام أجزاء من نموذج محدد المواقع يمكننا أن نختتم بشكل جيد للغاية IMyIntfSetter هي تقريبا واجهة داخلية متميزة عن IMyIntf.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top