Caleb'in cevabı, doğru yolda iken aslında yanlış. Onun Foo
sınıfı bir veritabanı cephesi ve fabrika gibi hem davranır. Bunlar iki sorumluluktur ve tek bir sınıfa konulmamalıdır.
Bu soru, özellikle veritabanı bağlamında, birçok kez sorulmuştur. Burada, uygulamanızı daha az eşleştirilmiş ve daha çok yönlü hale getirmek için soyutlama (arayüzler kullanarak) kullanmanın faydasını iyice göstermeye çalışacağım.
Daha fazla okumadan önce, henüz bilmiyorsanız, Bağımlılık enjeksiyonunu okumanızı ve temel bir anlayışa sahip olmanızı öneririm . Ayrıca , arayüzün genel yöntemlerinin arkasındaki uygulama ayrıntılarını gizlemenin anlamı olan Adaptör tasarım desenini de kontrol etmek isteyebilirsiniz .
Bağımlılık enjeksiyonu, Fabrika tasarım deseni ile birleştiğinde , temel taştır ve IoC prensibinin bir parçası olan Strateji tasarım desenini kodlamanın kolay bir yoludur .
Bizi arama, seni arayacağız . (AKA Hollywood prensibi ).
Bir uygulamayı soyutlama kullanarak ayırma
1. soyutlama katmanı Yapma
C ++ gibi bir dilde kodlama yapıyorsanız bir arabirim (veya soyut sınıf) oluşturursunuz ve bu arabirime genel yöntemler eklersiniz. Hem arabirimler hem de soyut sınıflar, bunları doğrudan kullanamayacağınız davranışına sahip olduklarından, ancak bunları uygulamanız (arabirim durumunda) veya genişletmeniz (soyut sınıf durumunda), kodun zaten önerdiğini, arabirim veya soyut sınıf tarafından verilen sözleşmeyi tamamlamak için özel uygulamalara sahip olmaları gerekir.
(Çok basit bir örnek) veritabanı arayüzünüz aşağıdaki gibi görünebilir (sırasıyla DatabaseResult veya DbQuery sınıfları, veritabanı işlemlerini temsil eden kendi uygulamalarınız olacaktır):
public interface Database
{
DatabaseResult DoQuery(DbQuery query);
void BeginTransaction();
void RollbackTransaction();
void CommitTransaction();
bool IsInTransaction();
}
Bu bir arayüz olduğundan, kendisi gerçekten hiçbir şey yapmaz. Yani bu arayüzü uygulamak için bir sınıfa ihtiyacınız var.
public class MyMySQLDatabase : Database
{
private readonly CSharpMySQLDriver _mySQLDriver;
public MyMySQLDatabase(CSharpMySQLDriver mySQLDriver)
{
_mySQLDriver = mySQLDriver;
}
public DatabaseResult DoQuery(DbQuery query)
{
// This is a place where you will use _mySQLDriver to handle the DbQuery
}
public void BeginTransaction()
{
// This is a place where you will use _mySQLDriver to begin transaction
}
public void RollbackTransaction()
{
// This is a place where you will use _mySQLDriver to rollback transaction
}
public void CommitTransaction()
{
// This is a place where you will use _mySQLDriver to commit transaction
}
public bool IsInTransaction()
{
// This is a place where you will use _mySQLDriver to check, whether you are in a transaction
}
}
Şimdi uygulayan bir sınıfınız var Database
, arayüz artık kullanışlı oldu.
2. Soyutlama katmanını kullanma
Uygulamanızın bir yerinde, bir yönteminiz var SecretMethod
, sadece eğlence için yöntemi çağıralım ve bu yöntemin içinde veritabanını kullanmanız gerekiyor, çünkü bazı verileri almak istiyorsunuz.
Şimdi doğrudan oluşturamadığınız bir arayüzünüz var (uh, o zaman nasıl kullanırım), ancak anahtar kelime MyMySQLDatabase
kullanılarak oluşturulabilecek bir sınıfınız var new
.
HARİKA! Bir veritabanı kullanmak istiyorum, bu yüzden kullanacağım MyMySQLDatabase
.
Metodunuz şöyle görünebilir:
public void SecretMethod()
{
var database = new MyMySQLDatabase(new CSharpMySQLDriver());
// you will use the database here, which has the DoQuery,
// BeginTransaction, RollbackTransaction and CommitTransaction methods
}
Bu iyi değil. Doğrudan bu yöntemin içinde bir sınıf oluşturuyorsunuz ve bunu içinde SecretMethod
yapıyorsanız, diğer 30 yöntemde de aynısını yapacağınızı varsaymak güvenlidir. Eğer MyMySQLDatabase
farklı bir sınıfa MyPostgreSQLDatabase
geçmek isterseniz , örneğin 30 metodunuzun tümünde bunu değiştirmeniz gerekir.
Başka bir sorun, MyMySQLDatabase
başarısız oluşturma , yöntem asla bitmez ve bu nedenle geçersiz olacaktır.
Biz MyMySQLDatabase
yöntemini bir parametre olarak geçirerek (bu bağımlılık enjeksiyon denir) oluşturulması yeniden düzenleyerek başlar .
public void SecretMethod(MyMySQLDatabase database)
{
// use the database here
}
Bu, MyMySQLDatabase
nesnenin asla yaratılamayacağı sorunu çözer . Çünkü SecretMethod
geçerli bir beklediğini MyMySQLDatabase
nesneyi bir şey oldu ve nesne kendisine geçirilen asla olsaydı, yöntem çalıştırmak asla. Ve bu tamamen iyi.
Bazı uygulamalarda bu yeterli olabilir. Memnun olabilirsiniz, ama daha da iyi olmak için yeniden düzenleyelim.
Başka bir yeniden düzenlemenin amacı
Gördüğünüz gibi, şu anda SecretMethod
bir MyMySQLDatabase
nesne kullanıyor . Diyelim ki MySQL'den MSSQL'e geçtiniz. Gerçekten, içindeki tüm mantığı SecretMethod
, parametre olarak geçirilen değişken üzerinde a BeginTransaction
ve CommitTransaction
yöntemleri çağıran bir yöntem değiştirmek gibi hissetmezsiniz database
, böylece ve yöntemlerine MyMSSQLDatabase
sahip olacak yeni bir sınıf oluşturursunuz .BeginTransaction
CommitTransaction
Sonra devam edip beyanını SecretMethod
aşağıdaki şekilde değiştirirsiniz.
public void SecretMethod(MyMSSQLDatabase database)
{
// use the database here
}
Ve sınıflar MyMSSQLDatabase
ve MyMySQLDatabase
aynı yöntemlere sahip olduğundan, başka bir şeyi değiştirmenize gerek yoktur ve yine de çalışır.
Bekle!
Uygulayan bir Database
arayüze MyMySQLDatabase
sahipsiniz, MyMSSQLDatabase
sınıfla da tamamen aynı yöntemlere sahipsiniz MyMySQLDatabase
, belki de MSSQL sürücüsü Database
arayüzü de uygulayabilir , böylece tanıma eklersiniz.
public class MyMSSQLDatabase : Database { }
Ama gelecekte MyMSSQLDatabase
PostgreSQL'e geçtiğim için artık kullanmak istemezsem ne olur ? Tekrar tanımını değiştirmek zorunda kalırdım SecretMethod
?
Evet, istersiniz. Ve bu doğru gelmiyor. Şu anda biliyoruz, bunu MyMSSQLDatabase
ve MyMySQLDatabase
aynı yöntemlere sahip ve her ikisi de Database
arayüzü uyguluyor . Yani böyle SecretMethod
görünmek için refactor .
public void SecretMethod(Database database)
{
// use the database here
}
Bildirim, nasıl SecretMethod
artık MySQL, MSSQL veya PotgreSQL kullanıp kullanmadığınızı, bilir. Bir veritabanı kullandığını bilir, ancak belirli uygulamaları umursamaz.
Şimdi yeni veritabanı sürücünüzü oluşturmak istiyorsanız, örneğin PostgreSQL için, hiç değiştirmenize gerek kalmayacak SecretMethod
. Bir yapacak MyPostgreSQLDatabase
, Database
arayüzü uygulayacak ve PostgreSQL sürücüsünü kodladıktan ve çalıştıktan sonra, örneğini oluşturacak ve içine enjekte edeceksiniz SecretMethod
.
3. İstenilen uygulamaların elde edilmesi Database
Aramaya başlamadan önce SecretMethod
, hangi Database
arayüzün uygulanmasını istediğinize karar vermelisiniz (MySQL, MSSQL veya PostgreSQL olsun). Bunun için fabrika tasarım desenini kullanabilirsiniz.
public class DatabaseFactory
{
private Config _config;
public DatabaseFactory(Config config)
{
_config = config;
}
public Database getDatabase()
{
var databaseType = _config.GetDatabaseType();
Database database = null;
switch (databaseType)
{
case DatabaseEnum.MySQL:
database = new MyMySQLDatabase(new CSharpMySQLDriver());
break;
case DatabaseEnum.MSSQL:
database = new MyMSSQLDatabase(new CSharpMSSQLDriver());
break;
case DatabaseEnum.PostgreSQL:
database = new MyPostgreSQLDatabase(new CSharpPostgreSQLDriver());
break;
default:
throw new DatabaseDriverNotImplementedException();
break;
}
return database;
}
}
Gördüğünüz gibi fabrika, bir yapılandırma dosyasından hangi veritabanı türünün kullanılacağını bilir (yine Config
sınıf kendi uygulamanız olabilir).
İdeal olarak, DatabaseFactory
bağımlılık enjeksiyon kabınızın içine sahip olacaksınız . İşleminiz bu şekilde görünebilir.
public class ProcessWhichCallsTheSecretMethod
{
private DIContainer _di;
private ClassWithSecretMethod _secret;
public ProcessWhichCallsTheSecretMethod(DIContainer di, ClassWithSecretMethod secret)
{
_di = di;
_secret = secret;
}
public void TheProcessMethod()
{
Database database = _di.Factories.DatabaseFactory.getDatabase();
_secret.SecretMethod(database);
}
}
Bakın, süreçte hiçbir yerde belirli bir veritabanı türü oluşturmuyorsunuz. Sadece bu da değil, hiçbir şey yaratmıyorsunuz. Bağımlılık enjeksiyon kabınızın ( değişken) içinde depolanan nesne üzerinde, yapılandırmanıza bağlı olarak size doğru arabirim örneğini döndürecek bir GetDatabase
yöntem çağırıyorsunuz .DatabaseFactory
_di
Database
PostgreSQL kullandıktan 3 hafta sonra MySQL'e geri dönmek isterseniz, tek bir yapılandırma dosyası açar ve DatabaseDriver
alanın değerini olarak olarak DatabaseEnum.PostgreSQL
değiştirirsiniz DatabaseEnum.MySQL
. Ve işiniz bitti. Aniden uygulamanızın geri kalanı MySQL'i tek bir satırı değiştirerek tekrar doğru kullanır.
Hala şaşkın değilseniz, IoC'ye biraz daha dalmanızı tavsiye ederim. Bir yapılandırmadan değil, bir kullanıcı girdisinden nasıl belirli kararlar alabileceğiniz. Bu yaklaşıma strateji modeli denir ve kurumsal uygulamalarda kullanılabilir ve kullanılmasına rağmen, bilgisayar oyunları geliştirilirken çok daha sık kullanılır.
DbQuery
nesnenizi ele alalım . Bu nesnenin yürütülecek bir SQL sorgu dizesi için bir üye içerdiği varsayılarak, bu jenerik nasıl yapılabilir?