Problemleri çözmek için çözmede iyi oldukları problemleri çözmek için teknikleri kullanmalısınız. Bağımlılık inversiyonu ve enjeksiyonu farklı değildir.
Bağımlılık inversiyonu veya enjeksiyonu, kodunuzun bir yöntemin hangi uygulamada çalıştırma sırasında çağrılacağına karar vermesini sağlayan bir tekniktir. Bu geç bağlanma avantajlarını en üst düzeye çıkarır. Teknik, dil, örnek olmayan işlevlerin çalışma zamanı değişimini desteklemediğinde gereklidir. Örneğin, Java, farklı bir uygulamaya yapılan çağrılarla statik bir yönteme yapılan çağrıları değiştirme mekanizmasından yoksundur; işlev çağrısını değiştirmek için gereken tek şey, adı farklı bir işleve bağlamaktır (işlevi tutan değişkeni yeniden atayın).
Neden fonksiyonun uygulanmasını değiştirmek isteyelim? İki ana sebep var:
- Sahte sınama amacıyla kullanmak istiyoruz. Bu, aslında veritabanına bağlanmadan, veritabanı alımına bağlı bir sınıfı sınamamızı sağlar.
- Birden fazla uygulamayı desteklememiz gerekiyor. Örneğin, hem MySQL hem de PostgreSQL veritabanlarını destekleyen bir sistem kurmamız gerekebilir.
Ayrıca kontrol konteynırlarının inversiyonunu not etmek isteyebilirsiniz. Bu, sözde kod gibi görünen devasa, karışık yapı ağaçlarından kaçınmanıza yardımcı olacak bir tekniktir:
thing5 = new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());
myApp = new MyApp(
new MyAppDependency1(thing5, thing3),
new MyAppDependency2(
new Thing1(),
new Thing2(new Thing3(thing5, new Thing4(thing5)))
),
...
new MyAppDependency15(thing5)
);
Sınıflarınızı kaydetmenizi sağlar ve ardından sizin için inşaatı yapar:
injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);
myApp = injector.create(MyApp); // The injector fills in all the construction parameters.
Kayıtlı sınıfların vatansız tekil olmak olması en basitidir .
Dikkatli kelime
Bağımlılık inversiyon gerektiğini unutmayın değil senin olmak go-mantık koparma için cevap. Bunun yerine parametreleştirme kullanmak için fırsatları arayın . Örneğin bu sözde kod yöntemini göz önünde bulundurun:
myAverageAboveMin()
{
dbConn = new DbConnection("my connection string");
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", 5);
dbQuery.Execute();
myData = dbQuery.getAll();
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
Bu yöntemin bazı bölümleri için bağımlılık inversiyonunu kullanabiliriz:
class MyQuerier
{
private _dbConn;
MyQueries(dbConn) { this._dbConn = dbConn; }
fetchAboveMin(min)
{
dbQuery = this._dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
private _querier;
Averager(querier) { this._querier = querier; }
myAverageAboveMin(min)
{
myData = this._querier.fetchAboveMin(min);
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
Ama yapmamalıyız, en azından tamamen değil. Bununla birlikte durum bilgisi olan bir sınıf yarattığımıza dikkat edin Querier
. Şimdi esas olarak bazı küresel bağlantı nesnelerine referansta bulunuyor. Bu, programın genel durumunu anlamada zorluk ve farklı sınıfların birbirleriyle nasıl koordine edilmesi gibi sorunlar yaratır. Ayrıca, ortalama mantığı test etmek istiyorsak, sorgulayıcıyı veya bağlantıyı taklit etmemiz gerektiğine dikkat edin. Ayrıca Parametreyi arttırmak için daha iyi bir yaklaşım olacaktır :
class MyQuerier
{
fetchAboveMin(dbConn, min)
{
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
averageData(myData)
{
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
class StuffDoer
{
private _querier;
private _averager;
StuffDoer(querier, averager)
{
this._querier = querier;
this._averager = averager;
}
myAverageAboveMin(dbConn, min)
{
myData = this._querier.fetchAboveMin(dbConn, min);
return this._averager.averageData(myData);
}
}
Bağlantı, operasyondan bir bütün olarak sorumlu olan ve bu çıktıyla ne yapacağını bilen daha da yüksek bir seviyede yönetilecekti.
Artık ortalama mantığı sorgulamadan tamamen bağımsız olarak test edebiliyoruz ve daha geniş bir yelpazede kullanabiliyoruz. MyQuerier
Ve Averager
nesnelere ihtiyaç duyup duymadığımızı bile sorgulayabiliriz ve belki de cevabı, birim testini yapmak niyetinde değilsek ve birim testini StuffDoer
yapmamak, StuffDoer
makul bir şekilde veritabanına çok sıkı bir şekilde bağlandığından dolayı makul olamayacağıdır. Entegrasyon testlerinin onu kapsamasına izin vermek daha mantıklı olabilir. Bu durumda, ince yapım fetchAboveMin
ve averageData
statik yöntemlerde olabiliriz.