Làm thế nào là các giao diện cơ sở dữ liệu trừu tượng được viết để hỗ trợ nhiều loại cơ sở dữ liệu?


12

Làm thế nào để một người bắt đầu thiết kế một lớp trừu tượng trong ứng dụng lớn hơn của họ có thể giao tiếp với một số loại cơ sở dữ liệu, chẳng hạn như MySQL, SQLLite, MSSQL, v.v?

Mẫu thiết kế này được gọi là gì và chính xác nó bắt đầu từ đâu?

Giả sử bạn cần viết một lớp có các phương thức sau

public class Database {
   public DatabaseType databaseType;
   public Database (DatabaseType databaseType){
      this.databaseType = databaseType;
   }

   public void SaveToDatabase(){
       // Save some data to the db
   }
   public void ReadFromDatabase(){
      // Read some data from db
   }
}

//Application
public class Foo {
    public Database db = new Database (DatabaseType.MySQL);
    public void SaveData(){
        db.SaveToDatabase();
    }
}

Điều duy nhất tôi có thể nghĩ là một câu lệnh if trong mọi Databasephương thức

public void SaveToDatabase(){
   if(databaseType == DatabaseType.MySQL){

   }
   else if(databaseType == DatabaseType.SQLLite){

   }
}

Câu trả lời:


11

Những gì bạn muốn là nhiều triển khai cho giao diện mà ứng dụng của bạn sử dụng.

như vậy

public interface IDatabase
{
    void SaveToDatabase();
    void ReadFromDatabase();
}

public class MySQLDatabase : IDatabase
{
   public MySQLDatabase ()
   {
      //init stuff
   }

   public void SaveToDatabase(){
       //MySql implementation
   }
   public void ReadFromDatabase(){
      //MySql implementation
   }
}

public class SQLLiteDatabase : IDatabase
{
   public SQLLiteDatabase ()
   {
      //init stuff
   }

   public void SaveToDatabase(){
       //SQLLite implementation
   }
   public void ReadFromDatabase(){
      //SQLLite implementation
   }
}

//Application
public class Foo {
    public IDatabase db = GetDatabase();

    public void SaveData(){
        db.SaveToDatabase();
    }

    private IDatabase GetDatabase()
    {
        if(/*some way to tell if should use MySql*/)
            return new MySQLDatabase();
        else if(/*some way to tell if should use MySql*/)
            return new SQLLiteDatabase();

        throw new Exception("You forgot to configure the database!");
    }
}

Theo cách tốt hơn để thiết lập IDatabasetriển khai chính xác trong thời gian chạy trong ứng dụng của bạn, bạn nên xem xét những thứ như " Phương pháp nhà máy " và " Tiêm phụ thuộc ".


25

Câu trả lời của Caleb, trong khi anh ta đang đi đúng hướng, thực sự là sai. FooLớ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 MyMySQLDatabasemộ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 MyMySQLDatabasethấ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 MyMySQLDatabasebằ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ì SecretMethodmong đợ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ờ SecretMethodsử 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 BeginTransactionCommitTransactioncác phương thức trên databasebiế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 BeginTransactionCommitTransaction.

Sau đó, bạn tiếp tục và thay đổi khai báo SecretMethodsau đây.

public void SecretMethod(MyMSSQLDatabase database)
{
    // use the database here
}

Và bởi vì các lớp MyMSSQLDatabaseMyMySQLDatabasecó 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 Databasegiao diện, mà MyMySQLDatabasethực hiện, bạn cũng có MyMSSQLDatabaselớ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 Databasegiao 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 MyMSSQLDatabasenữ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 đó MyMSSQLDatabaseMyMySQLDatabasecó cùng phương thức và cả hai đều thực hiện Databasegiao 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 SecretMethodkhô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 SecretMethodgì cả. Bạn sẽ tạo một MyPostgreSQLDatabase, làm cho nó thực hiện Databasegiao 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 Databasegiao 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, Configlớp có thể là do bạn thực hiện).

Lý tưởng nhất, bạn sẽ có DatabaseFactorybê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 GetDatabasephươ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 ( _dibiế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 DatabaseDrivertrường từ DatabaseEnum.PostgreSQLsang 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.


Yêu câu trả lời của bạn, David. Nhưng giống như tất cả các câu trả lời như vậy, nó không thể mô tả làm thế nào người ta có thể đưa nó vào thực tế. Vấn đề thực sự không trừu tượng hóa khả năng gọi vào các công cụ cơ sở dữ liệu khác nhau, vấn đề là cú pháp SQL thực tế. Lấy 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 đó?
DonBoitnott

1
@DonBoitnott Tôi không nghĩ bạn sẽ cần mọi thứ chung chung. Bạn thường muốn giới thiệu sự trừu tượng giữa các lớp ứng dụng (tên miền, dịch vụ, tính bền vững), bạn cũng có thể muốn giới thiệu sự trừu tượng hóa cho các mô-đun, bạn có thể muốn giới thiệu trừu tượng cho một thư viện nhỏ nhưng có thể sử dụng lại và tùy biến cao mà bạn đang phát triển cho một dự án lớn hơn, v.v. Bạn chỉ có thể trừu tượng mọi thứ với các giao diện, nhưng điều đó hiếm khi cần thiết. Thật sự rất khó để đưa ra một câu trả lời một cho tất cả mọi thứ, bởi vì, thật đáng buồn, thực sự không có một và nó xuất phát từ yêu cầu.
Andy

2
Hiểu. Nhưng tôi thực sự có nghĩa là theo nghĩa đen. Khi bạn đã có lớp trừu tượng hóa của mình và bạn đến điểm mà bạn muốn gọi, _secret.SecretMethod(database);làm thế nào để điều hòa tất cả những thứ đó với thực tế là bây giờ tôi SecretMethodvẫn phải biết DB tôi đang làm việc với cái gì để sử dụng phương ngữ SQL thích hợp ? Bạn đã làm việc rất chăm chỉ để giữ phần lớn mã không biết gì về thực tế đó, nhưng sau đó vào giờ thứ 11, bạn lại phải biết. Bây giờ tôi đang ở trong tình huống này và cố gắng tìm hiểu xem người khác đã giải quyết vấn đề này như thế nào.
DonBoitnott

@DonBoitnott Tôi không biết ý của bạn là gì, tôi hiểu rồi. Bạn có thể sử dụng một giao diện thay vì các triển khai cụ thể của DbQuerylớp, cung cấp các triển khai của giao diện đã nói và sử dụng giao diện đó thay vào đó, có một nhà máy để xây dựng IDbQuerythể hiện. Tôi không nghĩ rằng bạn sẽ cần một loại chung cho DatabaseResultlớp, bạn luôn có thể mong đợi kết quả từ cơ sở dữ liệu được định dạng theo cách tương tự. Vấn đề ở đây là, khi làm việc với cơ sở dữ liệu và SQL thô, bạn đã ở mức độ thấp như vậy trên ứng dụng của mình (đằng sau DAL và Kho lưu trữ), rằng không cần ...
Andy

... cách tiếp cận chung chung nữa.
Andy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.