อินเทอร์เฟซฐานข้อมูลที่เป็นนามธรรมถูกเขียนขึ้นเพื่อสนับสนุนหลายฐานข้อมูลได้อย่างไร?


12

หนึ่งจะเริ่มออกแบบคลาสนามธรรมในแอปพลิเคชันขนาดใหญ่ของพวกเขาที่สามารถเชื่อมต่อกับฐานข้อมูลหลายประเภทเช่น MySQL, SQLLite, MSSQL ฯลฯ ได้อย่างไร

รูปแบบการออกแบบนี้เรียกว่าอะไรและเริ่มต้นตรงไหน

สมมติว่าคุณต้องเขียนคลาสที่มีวิธีการดังต่อไปนี้

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();
    }
}

สิ่งเดียวที่ฉันคิดได้ก็คือคำสั่ง if ในทุก ๆDatabaseวิธี

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

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

   }
}

คำตอบ:


11

สิ่งที่คุณต้องการคือการนำไปใช้งานหลายอย่างสำหรับอินเทอร์เฟซที่แอปพลิเคชันของคุณใช้

ชอบมาก:

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!");
    }
}

เท่าที่เป็นวิธีที่ดีกว่าในการตั้งค่าการIDatabaseใช้งานที่ถูกต้องในขณะใช้งานในแอปพลิเคชันของคุณคุณควรมองเข้าไปในสิ่งต่าง ๆ เช่น " วิธีการจากโรงงาน " และ "การพึ่งพาการฉีด "


25

คำตอบของ Caleb ในขณะที่เขาอยู่ในเส้นทางที่ถูกต้องจริง ๆ แล้วผิด Fooคลาสของเขาทำหน้าที่เป็นทั้งฐานข้อมูลส่วนหน้าและโรงงาน สิ่งเหล่านี้เป็นความรับผิดชอบสองอย่างและไม่ควรใส่ในชั้นเรียนเดียว


คำถามนี้โดยเฉพาะอย่างยิ่งในบริบทฐานข้อมูลมีการถามหลายครั้งเกินไป ที่นี่ฉันจะพยายามแสดงให้คุณเห็นอย่างชัดเจนถึงประโยชน์ของการใช้นามธรรม (ใช้อินเทอร์เฟซ) เพื่อทำให้แอปพลิเคชันของคุณมีความหลากหลายน้อยลงและหลากหลายมากขึ้น

ก่อนที่จะอ่านเพิ่มเติมฉันขอแนะนำให้คุณอ่านและทำความเข้าใจพื้นฐานของการพึ่งพาการฉีดถ้าคุณยังไม่รู้ คุณอาจต้องการตรวจสอบรูปแบบการออกแบบอะแดปเตอร์ซึ่งโดยพื้นฐานแล้วคือสิ่งที่ซ่อนรายละเอียดการใช้งานไว้เบื้องหลังวิธีการทั่วไปของอินเทอร์เฟซ

พึ่งพาการฉีดคู่กับรูปแบบการออกแบบโรงงานเป็นศิลาฤกษ์และเป็นวิธีที่ง่ายโค้ดรูปแบบการออกแบบกลยุทธ์ซึ่งเป็นส่วนหนึ่งของหลักการ IoC

อย่าโทรหาเราเราจะโทรหาคุณ (AKA หลักการฮอลลีวูด )


Decoupling แอปพลิเคชันโดยใช้นามธรรม

1. สร้างเลเยอร์สิ่งที่เป็นนามธรรม

คุณสร้างอินเทอร์เฟซ - หรือคลาสนามธรรมถ้าคุณกำลังเข้ารหัสในภาษาเช่น C ++ - และเพิ่มวิธีการทั่วไปในอินเทอร์เฟซนี้ เนื่องจากทั้งอินเทอร์เฟซและคลาสนามธรรมมีพฤติกรรมของคุณที่ไม่สามารถใช้งานได้โดยตรง แต่คุณต้องใช้ (ในกรณีของอินเตอร์เฟซ) หรือขยาย (ในกรณีที่เป็นคลาสนามธรรม) พวกมันเองจึงแนะนำให้ใช้โค้ด จำเป็นต้องมีการใช้งานเฉพาะเพื่อ fullfil สัญญาที่กำหนดโดยอินเทอร์เฟซหรือคลาสนามธรรม

อินเทอร์เฟซฐานข้อมูล (ตัวอย่างง่าย ๆ ) ของคุณอาจมีลักษณะเช่นนี้ (คลาส DatabaseResult หรือ DbQuery ตามลำดับจะเป็นการใช้งานของคุณเองซึ่งเป็นตัวแทนของการดำเนินการฐานข้อมูล):

public interface Database
{
    DatabaseResult DoQuery(DbQuery query);
    void BeginTransaction();
    void RollbackTransaction();
    void CommitTransaction();
    bool IsInTransaction();
}

เนื่องจากนี่เป็นอินเทอร์เฟซตัวมันเองจึงไม่ทำอะไรเลย ดังนั้นคุณต้องมีคลาสเพื่อใช้ส่วนต่อประสานนี้

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
    }
}

ตอนนี้คุณมีคลาสที่ใช้งานDatabaseอินเทอร์เฟซก็กลายเป็นประโยชน์

2. ใช้เลเยอร์นามธรรม

บางที่ในแอปพลิเคชันของคุณคุณมีวิธีลองเรียกใช้เมธอดSecretMethodเพื่อความสนุกสนานและภายในเมธอดนี้คุณต้องใช้ฐานข้อมูลเพราะคุณต้องการดึงข้อมูลบางอย่าง

ตอนนี้คุณมีอินเทอร์เฟซซึ่งคุณไม่สามารถสร้างได้โดยตรง (เอ่อฉันจะใช้มันได้อย่างไร) แต่คุณมีคลาสMyMySQLDatabaseซึ่งอาจถูกสร้างขึ้นโดยใช้newคำสำคัญ

ที่ดี! MyMySQLDatabaseฉันต้องการที่จะใช้ฐานข้อมูลดังนั้นฉันจะใช้

วิธีการของคุณอาจมีลักษณะเช่นนี้:

public void SecretMethod()
{
    var database = new MyMySQLDatabase(new CSharpMySQLDriver());

    // you will use the database here, which has the DoQuery,
    // BeginTransaction, RollbackTransaction and CommitTransaction methods
}

มันไม่ดี คุณกำลังสร้างชั้นเรียนด้วยวิธีนี้โดยตรงและถ้าคุณทำในชั้นเรียนSecretMethodมันปลอดภัยที่จะคิดว่าคุณจะทำแบบเดียวกันในอีก 30 วิธี หากคุณต้องการเปลี่ยนMyMySQLDatabaseคลาสเป็นคลาสอื่นMyPostgreSQLDatabaseคุณต้องเปลี่ยนใน 30 วิธี

ปัญหาอื่นคือถ้าการสร้างMyMySQLDatabaseล้มเหลววิธีจะไม่เสร็จสิ้นและดังนั้นจึงจะไม่ถูกต้อง

เราเริ่มต้นด้วยการสร้างการสร้างใหม่MyMySQLDatabaseโดยส่งผ่านเป็นพารามิเตอร์ไปยังเมธอด (ซึ่งเรียกว่าการฉีดพึ่งพา)

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

วิธีนี้จะช่วยแก้ปัญหาที่คุณMyMySQLDatabaseไม่สามารถสร้างวัตถุได้ เพราะSecretMethodคาดว่าMyMySQLDatabaseวัตถุที่ถูกต้องหากมีสิ่งใดเกิดขึ้นและวัตถุจะไม่ถูกส่งผ่านไปยังวิธีการจะไม่ทำงาน และนั่นเป็นสิ่งที่ดีโดยสิ้นเชิง


ในบางแอปพลิเคชั่นนี้อาจเพียงพอ คุณอาจพอใจ แต่ลองปรับปรุงใหม่ให้ดียิ่งขึ้น

วัตถุประสงค์ของการปรับโครงสร้างอีกครั้ง

คุณสามารถดูได้ตอนนี้การSecretMethodใช้MyMySQLDatabaseวัตถุ สมมติว่าคุณย้ายจาก MySQL เป็น MSSQL คุณไม่รู้สึกอยากเปลี่ยนตรรกะทั้งหมดภายในของคุณSecretMethodซึ่งเป็นวิธีการที่เรียกBeginTransactionและCommitTransactionวิธีการเกี่ยวกับdatabaseตัวแปรที่ส่งผ่านเป็นพารามิเตอร์ดังนั้นคุณจึงสร้างคลาสใหม่MyMSSQLDatabaseซึ่งจะมีBeginTransactionและCommitTransactionวิธีการ

จากนั้นคุณไปข้างหน้าและเปลี่ยนการประกาศSecretMethodเป็นดังต่อไปนี้

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

และเนื่องจากชั้นเรียนMyMSSQLDatabaseและMyMySQLDatabaseมีวิธีการเดียวกันคุณไม่จำเป็นต้องเปลี่ยนสิ่งอื่นใดและมันจะยังคงใช้งานได้

โอ้เดี๋ยวก่อน!

คุณมีDatabaseอินเทอร์เฟซที่MyMySQLDatabaseใช้คุณยังมีMyMSSQLDatabaseคลาสซึ่งมีวิธีการเหมือนกับที่MyMySQLDatabaseแน่นอนบางทีไดรเวอร์ MSSQL ยังสามารถใช้Databaseอินเทอร์เฟซได้ดังนั้นคุณจึงเพิ่มมันเข้าไปในคำจำกัดความ

public class MyMSSQLDatabase : Database { }

แต่ถ้าในอนาคตฉันไม่ต้องการใช้MyMSSQLDatabaseอีกต่อไปเพราะฉันเปลี่ยนมาใช้ PostgreSQL ฉันจะต้องเปลี่ยนคำนิยามของSecretMethod?

ใช่คุณต้องการ และนั่นไม่ได้เสียงที่ถูกต้อง ตอนนี้เรารู้แล้วว่าMyMSSQLDatabaseและMyMySQLDatabaseมีวิธีการเดียวกันและทั้งสองใช้Databaseอินเทอร์เฟซ ดังนั้นคุณปรับโครงสร้างSecretMethodให้เป็นแบบนี้

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

แจ้งให้ทราบว่าSecretMethodไม่มีใครรู้อีกต่อไปไม่ว่าคุณจะใช้ MySQL, MSSQL หรือ PotgreSQL มันรู้ว่ามันใช้ฐานข้อมูล แต่ไม่สนใจการใช้งานเฉพาะ

ตอนนี้ถ้าคุณต้องการสร้างโปรแกรมควบคุมฐานข้อมูลใหม่ของคุณตัวอย่างเช่น PostgreSQL คุณไม่จำเป็นต้องเปลี่ยนSecretMethodเลย คุณจะทำให้MyPostgreSQLDatabaseให้มันใช้Databaseอินเตอร์เฟซและเมื่อคุณทำเข้ารหัสไดรเวอร์ PostgreSQL SecretMethodและการทำงานที่คุณจะสร้างอินสแตนซ์และฉีดเข้าไปใน

3. การได้รับการปฏิบัติตามที่ต้องการ Database

คุณยังต้องตัดสินใจก่อนที่จะทำการเรียกSecretMethodใช้Databaseอินเทอร์เฟซที่คุณต้องการ (ไม่ว่าจะเป็น MySQL, MSSQL หรือ PostgreSQL) สำหรับสิ่งนี้คุณสามารถใช้รูปแบบการออกแบบจากโรงงาน

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;
    }
}

โรงงานอย่างที่คุณเห็นรู้ประเภทฐานข้อมูลที่จะใช้จากไฟล์กำหนดค่า (อีกครั้งConfigคลาสอาจเป็นการใช้งานของคุณเอง)

เป็นการดีที่คุณจะมีที่DatabaseFactoryอยู่อาศัยภายในภาชนะฉีดของคุณ กระบวนการของคุณอาจมีลักษณะเช่นนี้

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);
    }
}

ดูว่าไม่มีกระบวนการใดในกระบวนการที่คุณสร้างชนิดฐานข้อมูลเฉพาะ ไม่เพียงแค่นั้นคุณไม่ได้สร้างอะไรเลย คุณกำลังเรียกใช้GetDatabaseเมธอดบนDatabaseFactoryวัตถุที่เก็บไว้ในคอนเทนเนอร์การฉีดของคุณ ( _diตัวแปร) ซึ่งเป็นวิธีการที่จะให้อินสแตนซ์ของDatabaseอินเทอร์เฟซที่ถูกต้องกลับมาตามการกำหนดค่าของคุณ

ถ้าหลังจาก 3 สัปดาห์ของการใช้ PostgreSQL, คุณต้องการที่จะกลับไป MySQL, คุณเปิดแฟ้มการกำหนดค่าเดียวและเปลี่ยนค่าของDatabaseDriverเขตข้อมูลจากการDatabaseEnum.PostgreSQL DatabaseEnum.MySQLและคุณทำเสร็จแล้ว ทันใดที่แอปพลิเคชั่นที่เหลือของคุณใช้ MySQL อย่างถูกต้องอีกครั้งโดยเปลี่ยนหนึ่งบรรทัด


หากคุณยังไม่ประหลาดใจฉันขอแนะนำให้คุณดำดิ่งเข้าสู่ IoC สักเล็กน้อย วิธีที่คุณสามารถตัดสินใจบางอย่างไม่ได้มาจากการตั้งค่า แต่จากการป้อนข้อมูลของผู้ใช้ aproach นี้เรียกว่ารูปแบบกลยุทธ์และแม้ว่าจะเป็นและใช้ในแอปพลิเคชันระดับองค์กร แต่ก็มีการใช้งานบ่อยกว่าเมื่อพัฒนาเกมคอมพิวเตอร์


รักคำตอบของคุณเดวิด แต่เช่นเดียวกับคำตอบทั้งหมดมันขาดการอธิบายว่าจะนำมันไปใช้อย่างไร ปัญหาจริงไม่ได้ทำให้ความสามารถในการเรียกใช้เอ็นจินฐานข้อมูลแตกต่างกันไปปัญหาคือไวยากรณ์ SQL จริง DbQueryยกตัวอย่างวัตถุของคุณ สมมติว่าวัตถุนั้นมีสมาชิกสำหรับสตริงแบบสอบถาม SQL ที่จะดำเนินการวิธีการอย่างใดอย่างหนึ่งอาจทำให้ทั่วไปที่?
DonBoitnott

1
@ DonBoitnott ฉันไม่คิดว่าคุณจะต้องการทุกอย่างที่เป็นเรื่องธรรมดา โดยปกติคุณต้องการแนะนำสิ่งที่เป็นนามธรรมระหว่างชั้นแอปพลิเคชัน (โดเมน, บริการ, ความพยายาม), คุณอาจต้องการแนะนำสิ่งที่เป็นนามธรรมสำหรับโมดูล, คุณอาจต้องการแนะนำสิ่งที่เป็นนามธรรมให้กับห้องสมุดขนาดเล็ก แต่สามารถนำกลับมาใช้ใหม่ได้ คุณสามารถทำให้ทุกอย่างเป็นนามธรรมกับส่วนต่อประสาน แต่นั่นไม่ค่อยจำเป็น มันยากจริงๆที่จะให้คำตอบเดียวสำหรับทุกสิ่งเพราะเศร้าไม่มีจริง ๆ แล้วมันมาจากข้อกำหนด
Andy

2
เข้าใจ แต่ฉันหมายถึงอย่างนั้นจริงๆ เมื่อคุณมีคลาสที่เป็นนามธรรมของคุณและคุณไปยังจุดที่คุณต้องการเรียก_secret.SecretMethod(database);ว่าใครจะคืนดีกับงานทั้งหมดด้วยข้อเท็จจริงที่ว่าตอนนี้ฉันSecretMethodยังต้องรู้ว่า DB ที่ฉันทำงานด้วยเพื่อใช้ภาษา SQL ที่เหมาะสม ? คุณทำงานหนักมากเพื่อรักษารหัสส่วนใหญ่ที่ไม่รู้ความจริงนั้น แต่เมื่อถึงเวลา 11 ชั่วโมงคุณต้องรู้อีกครั้ง ฉันอยู่ในสถานการณ์นี้ในขณะนี้และพยายามหาวิธีอื่น ๆ ได้แก้ไขปัญหานี้
DonBoitnott

@ DonBoitnott ฉันไม่รู้ว่าคุณหมายถึงอะไรฉันเห็นแล้วตอนนี้ คุณสามารถใช้อินเทอร์เฟซแทนการใช้งานที่เป็นรูปธรรมของDbQueryคลาสจัดเตรียมการนำไปปฏิบัติของอินเทอร์เฟซดังกล่าวและใช้อินเทอร์เฟซนั้นแทนโดยมีโรงงานเพื่อสร้างIDbQueryอินสแตนซ์ ฉันไม่คิดว่าคุณจะต้องการประเภททั่วไปสำหรับDatabaseResultชั้นเรียนคุณสามารถคาดหวังผลลัพธ์จากฐานข้อมูลที่จะจัดรูปแบบในลักษณะที่คล้ายกัน สิ่งที่นี่คือเมื่อจัดการกับฐานข้อมูลและ SQL ดิบคุณอยู่ในระดับต่ำในใบสมัครของคุณ (หลัง DAL และ Repositories) ว่าไม่มีความจำเป็นสำหรับ ...
Andy

... วิธีการทั่วไปอีกต่อไป
Andy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.