Feragatname: Henüz harika bir cevap olmadığından, bir süre önce okuduğum harika bir blog gönderisinin bir bölümünü neredeyse kelimesi kelimesine kopyalamaya karar verdim. Blog gönderisinin tamamını burada bulabilirsiniz . İşte burada:
Aşağıdaki iki arayüzü tanımlayabiliriz:
public interface IQuery<TResult>
{
}
public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
IQuery<TResult>Belirtir o kullanıyor döndüren verilerle belirli sorgu tanımlayan bir mesaj TResultgenel tür. Önceden tanımlanmış arayüz ile aşağıdaki gibi bir sorgu mesajı tanımlayabiliriz:
public class FindUsersBySearchTextQuery : IQuery<User[]>
{
public string SearchText { get; set; }
public bool IncludeInactiveUsers { get; set; }
}
Bu sınıf, iki parametreye sahip bir sorgu işlemini tanımlar ve bu, bir dizi Usernesne ile sonuçlanır . Bu mesajı işleyen sınıf şu şekilde tanımlanabilir:
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();
}
}
Artık tüketicilerin genel IQueryHandlerarayüze güvenmesine izin verebiliriz :
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);
}
}
Hemen bu model bize çok fazla esneklik sağlar, çünkü artık neyin enjekte edileceğine karar verebiliriz UserController. Tamamen farklı bir uygulama veya gerçek uygulamayı tamamlayan bir uygulamayı UserController(ve bu arayüzün diğer tüm tüketicilerinde) değişiklik yapmak zorunda kalmadan enjekte edebiliriz .
IQuery<TResult>Arayüz belirterek veya enjekte zaman bize destek zamanı derleme verir IQueryHandlersbizim kodunda. Biz değiştirdiğinizde FindUsersBySearchTextQuerydönmek için UserInfo[](uygulayarak yerine IQuery<UserInfo[]>), UserControllerüzerinde genel tür kısıtlaması beri, derlemek başarısız olur IQueryHandler<TQuery, TResult>haritaya mümkün olmayacaktır FindUsersBySearchTextQueryiçin User[].
IQueryHandlerArayüzü bir tüketiciye enjekte etmek , yine de ele alınması gereken daha az belirgin sorunlara sahiptir. Tüketicilerimizin bağımlılıklarının sayısı çok artabilir ve bir yapıcı çok fazla argüman aldığında, yapıcıya aşırı enjeksiyon yapılmasına neden olabilir. Bir sınıfın yürüttüğü sorguların sayısı sık sık değişebilir ve bu da yapıcı bağımsız değişkenlerinin sayısında sürekli değişiklikler gerektirir.
IQueryHandlersFazladan bir soyutlama katmanı ile çok fazla enjekte etmek zorunda kalma sorununu çözebiliriz . Tüketiciler ve sorgu işleyicileri arasında oturan bir aracı oluşturuyoruz:
public interface IQueryProcessor
{
TResult Process<TResult>(IQuery<TResult> query);
}
IQueryProcessorBir genel yöntemi ile bir genel olmayan bir arabirimdir. Arayüz tanımında görebileceğiniz gibi, arayüze IQueryProcessorbağlıdır IQuery<TResult>. Bu, tüketicilerimizde IQueryProcessor. Yeniyi UserControllerkullanmak için yeniden yazalım 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);
}
}
UserControllerArtık bağlıdır IQueryProcessoreden tüm sorgular işleyebileceği. UserControllerBireyin SearchUsersYöntem için, IQueryProcessor.Processbaşlatılan bir sorgu nesnesinde geçen yöntem. Yana FindUsersBySearchTextQueryuygulayan IQuery<User[]>arayüzü, biz jenerik için iletebilirsiniz Execute<TResult>(IQuery<TResult> query)yöntemle. C # tür çıkarımı sayesinde, derleyici genel türü belirleyebilir ve bu, türü açıkça belirtmek zorunda kalmamızı sağlar. ProcessYöntemin dönüş türü de bilinmektedir.
IQueryProcessorDoğru bulmak artık uygulamanın sorumluluğundadır IQueryHandler. Bu, biraz dinamik yazım ve isteğe bağlı olarak Bağımlılık Enjeksiyon çerçevesinin kullanılmasını gerektirir ve tümü yalnızca birkaç satır kodla yapılabilir:
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);
}
}
QueryProcessorSınıf, belirli yapıları IQueryHandler<TQuery, TResult>sağlanan sorgu örneğinin türüne göre türü. Bu tür, sağlanan kapsayıcı sınıfından bu türden bir örnek almasını istemek için kullanılır. Maalesef, Handleyöntemi yansıma kullanarak çağırmamız gerekiyor (bu durumda C # 4.0 dymamic anahtar sözcüğünü kullanarak), çünkü bu noktada eylemci örneğini çevirmek imkansız çünkü genel TQueryargüman derleme zamanında mevcut değil. Ancak, Handleyöntem yeniden adlandırılmadıkça veya başka argümanlar almadıkça , bu çağrı asla başarısız olmaz ve isterseniz bu sınıf için bir birim testi yazmak çok kolaydır. Yansıma kullanmak hafif bir düşüş sağlar, ancak gerçekten endişelenecek bir şey değildir.
Endişelerinizden birini yanıtlamak için:
Bu yüzden, tüm sorguyu kapsayan alternatifler arıyorum, ancak yine de yeterince esnek bir komut sınıfları patlaması için spagetti Depolarını değiştirmeyeceksiniz.
Bu tasarımı kullanmanın bir sonucu, sistemde çok sayıda küçük sınıf olacağıdır, ancak çok sayıda küçük / odaklanmış sınıfa (açık isimlerle) sahip olmak iyi bir şeydir. Bu yaklaşım, bir sorgu sınıfında bunları gruplayabileceğinizden, bir depodaki aynı yöntem için farklı parametrelere sahip çok sayıda aşırı yüklemeye sahip olmaktan çok daha iyidir. Dolayısıyla, bir depodaki yöntemlerden çok daha az sorgu sınıfı elde edersiniz.