คำตอบของ 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 นี้เรียกว่ารูปแบบกลยุทธ์และแม้ว่าจะเป็นและใช้ในแอปพลิเคชันระดับองค์กร แต่ก็มีการใช้งานบ่อยกว่าเมื่อพัฒนาเกมคอมพิวเตอร์
DbQuery
ยกตัวอย่างวัตถุของคุณ สมมติว่าวัตถุนั้นมีสมาชิกสำหรับสตริงแบบสอบถาม SQL ที่จะดำเนินการวิธีการอย่างใดอย่างหนึ่งอาจทำให้ทั่วไปที่?