Câu trả lời của Caleb, trong khi anh ta đang đi đúng hướng, thực sự là sai. Foo
Lớp học của ông hoạt động như một mặt tiền cơ sở dữ liệu và nhà máy. Đó là hai trách nhiệm và không nên đưa vào một lớp duy nhất.
Câu hỏi này, đặc biệt là trong bối cảnh cơ sở dữ liệu, đã được hỏi quá nhiều lần. Ở đây tôi sẽ cố gắng chỉ cho bạn thấy lợi ích của việc sử dụng tính trừu tượng (sử dụng giao diện) để làm cho ứng dụng của bạn ít kết nối và linh hoạt hơn.
Trước khi đọc thêm, tôi khuyên bạn nên đọc và hiểu cơ bản về tiêm Dependency , nếu bạn chưa biết. Bạn cũng có thể muốn kiểm tra mẫu thiết kế Bộ điều hợp , về cơ bản là ẩn các chi tiết triển khai đằng sau các phương thức công khai của giao diện.
Việc tiêm phụ thuộc, kết hợp với mẫu thiết kế Factory , là nền tảng và là cách dễ dàng để mã hóa mẫu thiết kế Chiến lược , là một phần của nguyên tắc IoC .
Đừng gọi cho chúng tôi, chúng tôi sẽ gọi cho bạn . (AKA nguyên tắc Hollywood ).
Phân tách một ứng dụng bằng cách sử dụng trừu tượng hóa
1. Tạo lớp trừu tượng
Bạn tạo một giao diện - hoặc lớp trừu tượng, nếu bạn đang mã hóa bằng một ngôn ngữ như C ++ - và thêm các phương thức chung vào giao diện này. Bởi vì cả giao diện và lớp trừu tượng đều có hành vi bạn không thể sử dụng chúng trực tiếp, nhưng bạn phải thực hiện (trong trường hợp giao diện) hoặc mở rộng (trong trường hợp lớp trừu tượng), nên chính mã đã gợi ý, bạn sẽ cần phải có các triển khai cụ thể để hoàn thành hợp đồng được cung cấp bởi giao diện hoặc lớp trừu tượng.
Giao diện cơ sở dữ liệu (ví dụ rất đơn giản) của bạn có thể trông như thế này (các lớp DatabaseResult hoặc DbQuery tương ứng sẽ là các triển khai của riêng bạn đại diện cho các hoạt động cơ sở dữ liệu):
public interface Database
{
DatabaseResult DoQuery(DbQuery query);
void BeginTransaction();
void RollbackTransaction();
void CommitTransaction();
bool IsInTransaction();
}
Bởi vì đây là một giao diện, bản thân nó không thực sự làm gì cả. Vì vậy, bạn cần một lớp để thực hiện giao diện này.
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
}
}
Bây giờ bạn có một lớp thực hiện Database
, giao diện trở nên hữu ích.
2. Sử dụng lớp trừu tượng
Ở đâu đó trong ứng dụng của bạn, bạn có một phương thức, hãy gọi phương thức đó SecretMethod
, chỉ để giải trí và bên trong phương thức này bạn phải sử dụng cơ sở dữ liệu, vì bạn muốn tìm nạp một số dữ liệu.
Bây giờ bạn có một giao diện mà bạn không thể tạo trực tiếp (uh, làm thế nào để tôi sử dụng nó), nhưng bạn có một lớp MyMySQLDatabase
, có thể được xây dựng bằng cách sử dụngnew
từ khóa.
TUYỆT QUÁ! Tôi muốn sử dụng cơ sở dữ liệu, vì vậy tôi sẽ sử dụngMyMySQLDatabase
.
Phương pháp của bạn có thể trông như thế này:
public void SecretMethod()
{
var database = new MyMySQLDatabase(new CSharpMySQLDriver());
// you will use the database here, which has the DoQuery,
// BeginTransaction, RollbackTransaction and CommitTransaction methods
}
Điều này không tốt. Bạn đang trực tiếp tạo một lớp bên trong phương thức này và nếu bạn đang thực hiện nó bên trong SecretMethod
, thì có thể giả định rằng bạn sẽ làm tương tự trong 30 phương thức khác. Nếu bạn muốn thay đổi MyMySQLDatabase
một lớp khác, chẳng hạn nhưMyPostgreSQLDatabase
, bạn sẽ phải thay đổi nó trong tất cả 30 phương thức của bạn.
Một vấn đề khác là, nếu việc tạo ra MyMySQLDatabase
thất bại, phương thức sẽ không bao giờ kết thúc và do đó sẽ không hợp lệ.
Chúng tôi bắt đầu bằng cách tái cấu trúc việc tạo ra MyMySQLDatabase
bằng cách chuyển nó dưới dạng tham số cho phương thức (điều này được gọi là tiêm phụ thuộc).
public void SecretMethod(MyMySQLDatabase database)
{
// use the database here
}
Điều này giải quyết cho bạn vấn đề, rằng MyMySQLDatabase
đối tượng không bao giờ có thể được tạo ra. Bởi vì SecretMethod
mong đợi một MyMySQLDatabase
đối tượng hợp lệ , nếu một cái gì đó đã xảy ra và đối tượng sẽ không bao giờ được truyền cho nó, phương thức sẽ không bao giờ chạy. Và điều đó là hoàn toàn tốt.
Trong một số ứng dụng, điều này có thể là đủ. Bạn có thể hài lòng, nhưng hãy cấu trúc lại nó để tốt hơn nữa.
Mục đích của tái cấu trúc khác
Bạn có thể thấy, ngay bây giờ SecretMethod
sử dụng một MyMySQLDatabase
đối tượng. Giả sử bạn đã chuyển từ MySQL sang MSSQL. Bạn không thực sự cảm thấy muốn thay đổi tất cả logic bên trong của mình SecretMethod
, một phương thức gọi a BeginTransaction
và CommitTransaction
các phương thức trên database
biến được truyền dưới dạng tham số, vì vậy bạn tạo một lớp mới MyMSSQLDatabase
, cũng sẽ có các phương thức BeginTransaction
và CommitTransaction
.
Sau đó, bạn tiếp tục và thay đổi khai báo SecretMethod
sau đây.
public void SecretMethod(MyMSSQLDatabase database)
{
// use the database here
}
Và bởi vì các lớp MyMSSQLDatabase
và MyMySQLDatabase
có cùng phương thức, bạn không cần thay đổi bất cứ điều gì khác và nó vẫn sẽ hoạt động.
Đợi đã!
Bạn có một Database
giao diện, mà MyMySQLDatabase
thực hiện, bạn cũng có MyMSSQLDatabase
lớp, có các phương thức chính xác giống như MyMySQLDatabase
, có lẽ trình điều khiển MSSQL cũng có thể thực hiện Database
giao diện, vì vậy bạn thêm nó vào định nghĩa.
public class MyMSSQLDatabase : Database { }
Nhưng điều gì sẽ xảy ra nếu trong tương lai tôi không muốn sử dụng MyMSSQLDatabase
nữa vì tôi đã chuyển sang PostgreQuery? Tôi sẽ phải, một lần nữa, thay thế định nghĩa của SecretMethod
?
Vâng, bạn sẽ Và điều đó có vẻ không đúng. Ngay bây giờ chúng tôi biết, điều đó MyMSSQLDatabase
và MyMySQLDatabase
có cùng phương thức và cả hai đều thực hiện Database
giao diện. Vì vậy, bạn tái cấu trúc SecretMethod
để trông như thế này.
public void SecretMethod(Database database)
{
// use the database here
}
Lưu ý, làm thế nào SecretMethod
không còn biết, cho dù bạn đang sử dụng MySQL, MSSQL hay PotgreSQL. Nó biết nó sử dụng một cơ sở dữ liệu, nhưng không quan tâm đến việc thực hiện cụ thể.
Bây giờ nếu bạn muốn tạo trình điều khiển cơ sở dữ liệu mới của mình, ví dụ như PostgreSQL, bạn sẽ không cần phải thay đổi SecretMethod
gì cả. Bạn sẽ tạo một MyPostgreSQLDatabase
, làm cho nó thực hiện Database
giao diện và sau khi bạn hoàn thành mã hóa trình điều khiển PostgreSQL và nó hoạt động, bạn sẽ tạo cá thể của nó và đưa nó vào SecretMethod
.
3. Đạt được việc thực hiện mong muốn của Database
Bạn vẫn phải quyết định, trước khi gọi SecretMethod
, việc triển khai Database
giao diện nào bạn muốn (cho dù đó là MySQL, MSSQL hay PostgreQuery). Đối với điều này, bạn có thể sử dụng các mẫu thiết kế nhà máy.
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;
}
}
Nhà máy, như bạn có thể thấy, biết loại cơ sở dữ liệu nào sẽ sử dụng từ tệp cấu hình (một lần nữa, Config
lớp có thể là do bạn thực hiện).
Lý tưởng nhất, bạn sẽ có DatabaseFactory
bên trong thùng chứa thuốc tiêm phụ thuộc của bạn. Quá trình của bạn sau đó có thể trông như thế này.
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);
}
}
Hãy nhìn xem, làm thế nào không nơi nào trong quá trình bạn đang tạo một loại cơ sở dữ liệu cụ thể. Không chỉ vậy, bạn không tạo ra bất cứ điều gì cả. Bạn đang gọi một GetDatabase
phương thức trên DatabaseFactory
đối tượng được lưu trữ bên trong thùng chứa phụ thuộc của bạn ( _di
biến), một phương thức, sẽ trả về cho bạn thể hiện đúng củaDatabase
giao diện , dựa trên cấu hình của bạn.
Nếu sau 3 tuần sử dụng PostgreSQL, bạn muốn quay lại MySQL, bạn mở một tệp cấu hình duy nhất và thay đổi giá trị của DatabaseDriver
trường từ DatabaseEnum.PostgreSQL
sang DatabaseEnum.MySQL
. Và bạn đã hoàn thành. Đột nhiên phần còn lại của ứng dụng của bạn sử dụng lại chính xác MySQL, bằng cách thay đổi một dòng duy nhất.
Nếu bạn vẫn không ngạc nhiên, tôi khuyên bạn nên tìm hiểu thêm một chút về IoC. Làm thế nào bạn có thể đưa ra quyết định nhất định không phải từ một cấu hình, mà từ đầu vào của người dùng. Aproach này được gọi là mẫu chiến lược và mặc dù có thể và được sử dụng trong các ứng dụng doanh nghiệp, nó được sử dụng thường xuyên hơn nhiều khi phát triển trò chơi máy tính.
DbQuery
đối tượng của bạn , ví dụ. Giả sử đối tượng đó chứa một thành viên cho chuỗi truy vấn SQL được thực thi, làm thế nào người ta có thể tạo ra chung chung đó?