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 TResult
genel 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 User
nesne 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 IQueryHandler
arayü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 IQueryHandlers
bizim kodunda. Biz değiştirdiğinizde FindUsersBySearchTextQuery
dö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 FindUsersBySearchTextQuery
için User[]
.
IQueryHandler
Arayü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.
IQueryHandlers
Fazladan 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);
}
IQueryProcessor
Bir genel yöntemi ile bir genel olmayan bir arabirimdir. Arayüz tanımında görebileceğiniz gibi, arayüze IQueryProcessor
bağlıdır IQuery<TResult>
. Bu, tüketicilerimizde IQueryProcessor
. Yeniyi UserController
kullanmak 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);
}
}
UserController
Artık bağlıdır IQueryProcessor
eden tüm sorgular işleyebileceği. UserController
Bireyin SearchUsers
Yöntem için, IQueryProcessor.Process
başlatılan bir sorgu nesnesinde geçen yöntem. Yana FindUsersBySearchTextQuery
uygulayan 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. Process
Yöntemin dönüş türü de bilinmektedir.
IQueryProcessor
Doğ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);
}
}
QueryProcessor
Sı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, Handle
yö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 TQuery
argüman derleme zamanında mevcut değil. Ancak, Handle
yö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.