দাবি অস্বীকার: যেহেতু এখনও পর্যন্ত কোনও দুর্দান্ত উত্তর নেই, তাই আমি বেশ কিছু কাল পূর্বে পড়া একটি দুর্দান্ত ব্লগ পোস্ট থেকে একটি অংশ পোস্ট করার সিদ্ধান্ত নিয়েছিলাম, প্রায় ভার্বামটি অনুলিপি করেছি। আপনি এখানে সম্পূর্ণ ব্লগ পোস্ট পেতে পারেন । সুতরাং এটি এখানে:
আমরা নিম্নলিখিত দুটি ইন্টারফেস সংজ্ঞায়িত করতে পারি:
public interface IQuery<TResult>
{
}
public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
IQuery<TResult>
নির্দিষ্ট করে একটি বার্তা যে ডেটা এটি ব্যবহার ফেরৎ একটি নির্দিষ্ট প্রশ্নের সাথে সংজ্ঞায়িত TResult
জেনেরিক প্রকার। পূর্বনির্ধারিত ইন্টারফেসের সাহায্যে আমরা একটি কোয়েরি বার্তাটিকে এভাবে সংজ্ঞায়িত করতে পারি:
public class FindUsersBySearchTextQuery : IQuery<User[]>
{
public string SearchText { get; set; }
public bool IncludeInactiveUsers { get; set; }
}
এই শ্রেণিটি দুটি পরামিতি সহ একটি ক্যোয়ারী অপারেশন সংজ্ঞায়িত করে, যার ফলে User
অবজেক্টগুলির অ্যারে হবে in এই বার্তাটি পরিচালনা করে এমন শ্রেণিটি নীচে সংজ্ঞায়িত করা যেতে পারে:
public class FindUsersBySearchTextQueryHandler
: IQueryHandler<FindUsersBySearchTextQuery, User[]>
{
private readonly NorthwindUnitOfWork db;
public FindUsersBySearchTextQueryHandler(NorthwindUnitOfWork db)
{
this.db = db;
}
public User[] Handle(FindUsersBySearchTextQuery query)
{
return db.Users.Where(x => x.Name.Contains(query.SearchText)).ToArray();
}
}
আমরা এখন গ্রাহকদের জেনেরিক IQueryHandler
ইন্টারফেসের উপর নির্ভর করতে পারি :
public class UserController : Controller
{
IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler;
public UserController(
IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler)
{
this.findUsersBySearchTextHandler = findUsersBySearchTextHandler;
}
public View SearchUsers(string searchString)
{
var query = new FindUsersBySearchTextQuery
{
SearchText = searchString,
IncludeInactiveUsers = false
};
User[] users = this.findUsersBySearchTextHandler.Handle(query);
return View(users);
}
}
অবিলম্বে এই মডেল আমাদের, নমনীয়তা অনেক দেয়, কারণ আমরা এখন সিদ্ধান্ত নিতে পারেন কি মধ্যে উদ্বুদ্ধ করতে UserController
। আমরা UserController
(এবং সেই ইন্টারফেসের অন্যান্য সমস্ত গ্রাহক) পরিবর্তন না করেই সম্পূর্ণ ভিন্ন বাস্তবায়ন বা বাস্তব বাস্তবায়নকে গুটিয়ে ফেলা একটি ইনজেক্ট করতে পারি ।
IQuery<TResult>
ইন্টারফেস আমাদের কম্পাইল-টাইম সমর্থন যখন নির্দিষ্ট বা ইনজেকশনের দেয় IQueryHandlers
আমাদের কোডে। আমরা যখন পরিবর্তন FindUsersBySearchTextQuery
ফিরতে UserInfo[]
পরিবর্তে (প্রয়োগ করে IQuery<UserInfo[]>
), UserController
, কম্পাইল করতে ব্যর্থ থেকে জেনেরিক টাইপ বাধ্যতা হবে IQueryHandler<TQuery, TResult>
ম্যাপ পারবে না FindUsersBySearchTextQuery
করতে User[]
।
IQueryHandler
ইন্টারফেসটি গ্রাহক হিসাবে ইনজেক্ট করার ক্ষেত্রে কিছু কম স্পষ্ট সমস্যা রয়েছে যা এখনও সমাধান করা দরকার। আমাদের ভোক্তাদের নির্ভরতার সংখ্যা খুব বেশি বেড়ে যেতে পারে এবং কনস্ট্রাক্টরকে ওভার-ইনজেকশন দিতে পারে - যখন কোনও কনস্ট্রাক্টর অনেক বেশি যুক্তি নেন। কোনও শ্রেণি নির্বাহ করে এমন প্রশ্নের সংখ্যা প্রায়শই বদলে যেতে পারে, যার জন্য কনস্ট্রাক্টর আর্গুমেন্টের সংখ্যায় ধ্রুবক পরিবর্তন প্রয়োজন।
IQueryHandlers
বিমূর্ততার অতিরিক্ত স্তর দিয়ে আমরা অনেকগুলি ইনজেকশনের সমস্যাটি সমাধান করতে পারি । আমরা একটি মধ্যস্থতাকারী তৈরি করি যা গ্রাহক এবং ক্যোয়ারি হ্যান্ডলারের মধ্যে বসে থাকে:
public interface IQueryProcessor
{
TResult Process<TResult>(IQuery<TResult> query);
}
IQueryProcessor
এক generic পদ্ধতি সম্পর্কে একটি অ-জেনেরিক ইন্টারফেস। আপনি ইন্টারফেস সংজ্ঞা দেখতে পারেন, ইন্টারফেস IQueryProcessor
উপর নির্ভর করে IQuery<TResult>
। এটি আমাদের উপর নির্ভর করে আমাদের গ্রাহকদের মধ্যে সংকলন সময় সমর্থন করতে দেয় IQueryProcessor
। নতুনটি UserController
ব্যবহার করতে পুনরায় লিখুন IQueryProcessor
:
public class UserController : Controller
{
private IQueryProcessor queryProcessor;
public UserController(IQueryProcessor queryProcessor)
{
this.queryProcessor = queryProcessor;
}
public View SearchUsers(string searchString)
{
var query = new FindUsersBySearchTextQuery
{
SearchText = searchString,
IncludeInactiveUsers = false
};
User[] users = this.queryProcessor.Process(query);
return this.View(users);
}
}
UserController
এখন একটি উপর নির্ভর করে IQueryProcessor
আমাদের প্রশ্নের সব ব্যবস্থা করতে সক্ষম। UserController
এর SearchUsers
পদ্ধতি কল IQueryProcessor.Process
পদ্ধতি একটি সক্রিয়া ক্যোয়ারী বস্তু ক্ষণস্থায়ী। যেহেতু ইন্টারফেসটি FindUsersBySearchTextQuery
প্রয়োগ করে IQuery<User[]>
, তাই আমরা এটিকে জেনেরিক Execute<TResult>(IQuery<TResult> query)
পদ্ধতিতে প্রেরণ করতে পারি । সি # টাইপ অনুমানের জন্য ধন্যবাদ, সংকলক জেনেরিক টাইপ নির্ধারণ করতে সক্ষম হয় এবং এটি আমাদের স্পষ্টভাবে টাইপটি জানাতে বাঁচায়। Process
পদ্ধতির রিটার্ন টাইপও জানা যায়।
এটা এখন বাস্তবায়ন দায়িত্ব IQueryProcessor
ডান খুঁজে পেতে IQueryHandler
। এর জন্য কিছু গতিশীল টাইপিং প্রয়োজন হয় এবং allyচ্ছিকভাবে নির্ভরতা ইনজেকশন ফ্রেমওয়ার্ক ব্যবহার করা হয় এবং কোডের কয়েকটি লাইন দিয়ে সমস্ত করা যেতে পারে:
sealed class QueryProcessor : IQueryProcessor
{
private readonly Container container;
public QueryProcessor(Container container)
{
this.container = container;
}
[DebuggerStepThrough]
public TResult Process<TResult>(IQuery<TResult> query)
{
var handlerType = typeof(IQueryHandler<,>)
.MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = container.GetInstance(handlerType);
return handler.Handle((dynamic)query);
}
}
QueryProcessor
বর্গ একটি নির্দিষ্ট নির্মান IQueryHandler<TQuery, TResult>
সরবরাহকৃত ক্যোয়ারী উদাহরণস্বরূপ ধরনের উপর ভিত্তি করে প্রকার। এই ধরণের সরবরাহকৃত ধারক শ্রেণীর কাছে এই ধরণের উদাহরণ পেতে জিজ্ঞাসা করা হয়। দুর্ভাগ্যক্রমে Handle
প্রতিবিম্বটি ব্যবহার করে আমাদের পদ্ধতিটি কল করতে হবে (এই ক্ষেত্রে সি # 4.0 ডাইমমিক কীওয়ার্ডটি ব্যবহার করে), কারণ জেনেরিক TQuery
যুক্তি সংকলনের সময় উপলব্ধ না হওয়ায় এই মুহুর্তে হ্যান্ডলার উদাহরণটি কাস্ট করা অসম্ভব । তবে, Handle
পদ্ধতিটির নাম পরিবর্তন না করা বা অন্যান্য যুক্তি না পাওয়া পর্যন্ত এই কলটি কখনই ব্যর্থ হবে না এবং আপনি যদি চান, এই শ্রেণীর জন্য একটি ইউনিট পরীক্ষা লেখা খুব সহজ। প্রতিবিম্ব ব্যবহার করে সামান্য ড্রপ দেবে, তবে সত্যিই উদ্বিগ্ন হওয়ার মতো কিছু নয়।
আপনার উদ্বেগের একটি উত্তর দিতে:
সুতরাং আমি এমন বিকল্পগুলির সন্ধান করছি যা পুরো ক্যোয়ারিকে encapsulate করে, তবে এখনও যথেষ্ট নমনীয় যে আপনি কেবল কমান্ড ক্লাসগুলির বিস্ফোরণের জন্য স্প্যাগেটি রিপোজিটরিগুলি অদলবদল করছেন না।
এই নকশাটি ব্যবহারের একটি পরিণতি হ'ল সিস্টেমে প্রচুর ছোট ক্লাস থাকবে তবে প্রচুর ছোট / কেন্দ্রীভূত ক্লাস (স্পষ্ট নাম সহ) রাখা ভাল জিনিস। এই পদ্ধতির স্পষ্টতই আরও ভাল তবে একই পদ্ধতিতে বিভিন্ন প্যারামিটারগুলির সাথে অনেকগুলি একটি রিপোজিটরিতে বিভিন্ন পরামিতি রয়েছে, কারণ আপনি তাদের একটি ক্লাস শ্রেণিতে গ্রুপ করতে পারেন। সুতরাং আপনি এখনও একটি সংগ্রহস্থলের পদ্ধতির তুলনায় অনেক কম ক্যোয়ারী ক্লাস পান।