การประยุกต์ใช้หลักการ SOLID


13

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

แอปพลิเคชั่นมีงานดังต่อไปนี้ (จริง ๆ แล้วมากกว่านั้น แต่ให้ง่าย): มันต้องอ่านไฟล์ XML ซึ่งมีฐานข้อมูลตาราง / คอลัมน์ / ดูคำนิยาม ฯลฯ และสร้างไฟล์ SQL ที่สามารถใช้เพื่อสร้าง สกีมาฐานข้อมูล ORACLE

(หมายเหตุ: โปรดอย่าอภิปรายว่าทำไมฉันถึงต้องการหรือทำไมฉันถึงไม่ใช้ XSLT และอื่น ๆ มีเหตุผลหลายประการ แต่มันไม่ใช่หัวข้อ)

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

ข้อ จำกัด เป็นส่วนหนึ่งของตาราง (หรือมากกว่าอย่างแม่นยำส่วนหนึ่งของคำสั่ง CREATE TABLE) และข้อ จำกัด อาจอ้างอิงตารางอื่น

ก่อนอื่นฉันจะอธิบายว่าแอปพลิเคชันมีลักษณะอย่างไรในตอนนี้ (ไม่ได้ใช้ SOLID):

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

เห็นได้ชัดว่านี่ไม่ได้ใช้กับโซลิดเลย ตัวอย่างเช่นมีการขึ้นต่อกันแบบวงกลมซึ่งทำให้โค้ดในแง่ของ "เพิ่ม" / "ลบ" วิธีการที่จำเป็นและวัตถุขนาดใหญ่ destructors

ดังนั้นมีคำถามสองสามข้อ:

  1. ฉันควรแก้ไขการอ้างอิงแบบวงกลมโดยใช้การฉีดพึ่งพาหรือไม่ ถ้าเป็นเช่นนั้นฉันคิดว่าข้อ จำกัด ควรได้รับตารางเจ้าของ (และเลือกที่อ้างอิง) ในตัวสร้าง แต่ฉันจะเรียกใช้ข้อ จำกัด สำหรับตารางเดียวได้อย่างไร?
  2. หากคลาส Table เก็บสถานะของตัวเอง (เช่นชื่อตารางความคิดเห็นของตาราง ฯลฯ ) และลิงก์ไปยังข้อ จำกัด "ความรับผิดชอบ" หนึ่งหรือสองอย่างนี้เป็นความคิดของหลักการความรับผิดชอบเดี่ยวหรือไม่?
  3. ในกรณีที่ 2 ถูกต้องฉันควรสร้างคลาสใหม่ในชั้นธุรกิจแบบลอจิคัลซึ่งจัดการลิงก์หรือไม่ ถ้าเป็นเช่นนั้น 1. จะไม่มีความเกี่ยวข้องอีกต่อไป
  4. วิธีการ "createStatement" ควรเป็นส่วนหนึ่งของคลาส Table / Constraint หรือฉันควรจะย้ายมันออกมาด้วยเช่นกัน? ถ้าเป็นเช่นนั้นจะไปที่ไหน? หนึ่งคลาสผู้จัดการต่อหนึ่งคลาสหน่วยเก็บข้อมูล (เช่นตาราง, ข้อ จำกัด , ... )? หรือค่อนข้างสร้างคลาสผู้จัดการต่อลิงค์ (คล้ายกับ 3)?

เมื่อใดก็ตามที่ฉันพยายามตอบคำถามเหล่านี้ฉันพบว่าตัวเองทำงานอยู่ในแวดวงที่ไหนสักแห่ง

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


3
คุณใช้ภาษาอะไร คุณช่วยโพสต์โครงกระดูกอย่างน้อยบางรหัสได้ไหม เป็นการยากมากที่จะหารือเกี่ยวกับคุณภาพของรหัสและการปรับโครงสร้างที่เป็นไปได้โดยไม่เห็นรหัสจริง
PéterTörök

ฉันกำลังใช้ C ++ แต่ผมพยายามที่จะให้มันออกจากการอภิปรายในขณะที่คุณอาจจะมีปัญหานี้ในภาษาใด ๆ
ทิมเมเยอร์

ใช่ แต่การประยุกต์ใช้รูปแบบและการปรับโครงสร้างนั้นขึ้นอยู่กับภาษา Eg @ back2dos แนะนำ AOP ในคำตอบของเขาด้านล่างซึ่งเห็นได้ชัดว่าไม่ได้ใช้กับ C ++
PéterTörök

โปรดดูprogrammers.stackexchange.com/questions/155852//สำหรับข้อมูลเพิ่มเติมเกี่ยวกับหลักการ SOLID
LCJ

คำตอบ:


8

คุณอาจเริ่มจากมุมมองที่แตกต่างเพื่อใช้ "หลักการความรับผิดชอบเดี่ยว" ที่นี่ สิ่งที่คุณแสดงให้เราเห็นคือ (มากหรือน้อย) เฉพาะรูปแบบข้อมูลของแอปพลิเคชันของคุณ SRP ที่นี่หมายถึง: ตรวจสอบให้แน่ใจว่าตัวแบบข้อมูลของคุณรับผิดชอบเฉพาะในการเก็บข้อมูล - ไม่น้อยไม่มาก

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

[XML] -> ("Read XML") -> [Data model of DB definition] -> ("Write SQL") -> [SQL]

ดังนั้นสถานที่เดียวที่รหัสเฉพาะ XML Read_XMLควรจะวางอยู่ในระดับที่มีชื่อเช่น สถานที่เดียวสำหรับรหัสเฉพาะ SQL Write_SQLควรจะเป็นชั้นเหมือน แน่นอนว่าบางทีคุณอาจแบ่งงาน 2 อย่างนั้นออกเป็นงานย่อย ๆ เพิ่มเติม (และแบ่งคลาสของคุณออกเป็นคลาสผู้จัดการหลายคลาส) แต่ "โมเดลข้อมูล" ของคุณไม่ควรรับผิดชอบใด ๆ จากเลเยอร์นั้น ดังนั้นอย่าเพิ่ม a createStatementลงในคลาสโมเดลข้อมูลใด ๆ ของคุณเนื่องจากนี่เป็นความรับผิดชอบของโมเดลข้อมูลของคุณสำหรับ SQL

ฉันไม่เห็นปัญหาใด ๆ เมื่อคุณอธิบายว่า Table รับผิดชอบในการเก็บส่วนต่าง ๆ ทั้งหมดของมัน (ชื่อคอลัมน์ความคิดเห็นข้อ จำกัด ... ) นั่นคือแนวคิดที่อยู่เบื้องหลังตัวแบบข้อมูล แต่คุณอธิบายว่า "ตาราง" ยังรับผิดชอบการจัดการหน่วยความจำของบางส่วน นั่นเป็นปัญหาเฉพาะ C ++ ซึ่งคุณจะไม่ต้องเผชิญในภาษาอย่าง Java หรือ C # อย่างง่ายดาย วิธีการกำจัดความรับผิดชอบเหล่านี้คือการใช้พอยน์เตอร์อัจฉริยะการมอบหมายความเป็นเจ้าของให้กับเลเยอร์ที่แตกต่างกัน (ตัวอย่างเช่นไลบรารีเพิ่มหรือเลเยอร์ตัวชี้ "สมาร์ท" ของคุณเอง) แต่ระวังการพึ่งพาแบบวนรอบของคุณอาจ "ทำให้ระคายเคือง" การใช้งานตัวชี้สมาร์ท

มีอะไรเพิ่มเติมเกี่ยวกับ SOLID: นี่เป็นบทความที่ดี

http://cre8ivethought.com/blog/2011/08/23/software-development-is-not-a-jenga-game

อธิบาย SOLID โดยตัวอย่างเล็ก ๆ ลองใช้สิ่งนั้นกับกรณีของคุณ:

  • คุณจะต้องไม่เพียง แต่คลาสRead_XMLและWrite_SQLยังเป็นคลาสที่สามซึ่งจัดการการโต้ตอบของ 2 คลาสนั้น ConversionManagerช่วยให้เรียกว่า

  • การใช้หลักการ DI อาจหมายถึงที่นี่: ConversionManager ไม่ควรสร้างอินสแตนซ์ของ Read_XMLและWrite_SQLโดยตัวมันเอง วัตถุเหล่านั้นสามารถถูกฉีดเข้าไปใน Constructor และตัวสร้างควรมีลายเซ็นเช่นนี้

    ConversionManager(IDataModelReader reader, IDataModelWriter writer)

ที่IDataModelReaderเป็นอินเตอร์เฟซที่Read_XMLสืบทอดและเหมือนกันสำหรับIDataModelWriter Write_SQLสิ่งนี้ทำให้การConversionManagerเปิดสำหรับส่วนขยาย (คุณให้ผู้อ่านหรือนักเขียนที่แตกต่างกันได้ง่ายมาก) โดยไม่ต้องเปลี่ยนดังนั้นเราจึงมีตัวอย่างสำหรับหลักการเปิด / ปิด คิดเกี่ยวกับสิ่งที่คุณจะต้องเปลี่ยนเมื่อคุณต้องการที่จะสนับสนุนผู้ขายฐานข้อมูลอื่น - โดยที่คุณไม่ต้องเปลี่ยนแปลงอะไรในรูปแบบของคุณเพียงแค่ให้ SQL-Writer อื่นแทน


ในขณะที่นี่คือการออกกำลังกายที่เหมาะสมมากของ SOLID, (upvoted) โปรดทราบว่ามันเป็นการละเมิด "โรงเรียนเก่า Kay / Holub OOP" โดยกำหนดให้ getters และ setters สำหรับแบบจำลองข้อมูลโลหิตจางค่อนข้าง นอกจากนี้ยังทำให้ผมนึกถึงที่น่าอับอายสตีฟ Yegge พูดจาโผงผาง
user949300

2

คุณควรใช้ S ของโซลิดในกรณีนี้

ตารางมีข้อ จำกัด ทั้งหมดที่กำหนดไว้ในตาราง ข้อ จำกัด มีตารางทั้งหมดที่อ้างอิง รุ่นธรรมดาและเรียบง่าย

สิ่งที่คุณยึดติดอยู่คือความสามารถในการทำการค้นหาแบบผกผันคือการพิจารณาว่าข้อ จำกัด บางตารางถูกอ้างอิง
ดังนั้นสิ่งที่คุณต้องการจริงๆคือบริการจัดทำดัชนี นั่นเป็นภารกิจที่แตกต่างไปจากเดิมอย่างสิ้นเชิงดังนั้นจึงควรดำเนินการโดยวัตถุอื่น

หากต้องการทำลายให้เป็นเวอร์ชันที่ง่ายมาก:

class Table {
      void addConstraint(Constraint constraint) { ... }
      bool removeConstraint(Constraint constraint) { ... }
      Iterator<Constraint> getConstraints() { ... }
}
class Constraint {
      //actually I am not so sure these two should be exposed directly at all
      void addReference(Table to) { ... }
      bool removeReference(Table to) { ... }
      Iterator<Table> getReferencedTables() { ... }
}
class Database {
      void addTable(Table table) { ... }
      bool removeTable(Table table) { ... }
      Iterator<Table> getTables() { ... }
}
class Index {
      Iterator<Constraint> getConstraintsReferencing(Table target) { ... }
}

สำหรับการนำดัชนีไปใช้มี 3 วิธีดังนี้:

  • getContraintsReferencingวิธีสามารถจริงๆเพียงแค่รวบรวมข้อมูลทั้งหมดDatabaseสำหรับTableอินสแตนซ์และรวบรวมข้อมูลของพวกเขาConstraintที่จะได้รับผล ขึ้นอยู่กับว่าคุณต้องเสียค่าใช้จ่ายมากน้อยเพียงใดและอาจเป็นตัวเลือก
  • มันยังสามารถใช้แคช หากโมเดลฐานข้อมูลของคุณสามารถเปลี่ยนแปลงได้เมื่อกำหนดไว้แล้วคุณสามารถบำรุงรักษาแคชได้โดยการส่งสัญญาณจากที่เกี่ยวข้องTableและConstraintอินสแตนซ์เมื่อมีการเปลี่ยนแปลง วิธีแก้ปัญหาที่เรียบง่ายกว่าเล็กน้อยคือการIndexสร้าง "snapshot index" ของภาพรวมDatabaseเพื่อทำงานร่วมกับที่คุณจะทิ้งไป แน่นอนว่าเป็นไปได้หากใบสมัครของคุณสร้างความแตกต่างอย่างมากระหว่าง "เวลาการทำแบบจำลอง" และ "เวลาสอบถาม" หากมีแนวโน้มที่จะทำทั้งสองอย่างพร้อมกันแสดงว่าสิ่งนี้ไม่สามารถทำงานได้
  • อีกทางเลือกหนึ่งคือการใช้AOPเพื่อสกัดกั้นการเรียกทั้งหมดของการสร้างและรักษาดัชนีให้สอดคล้อง

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

@ Tim Meyer: DI ไม่จำเป็นต้องฉีดคอนสตรัคเตอร์ DI สามารถทำได้ด้วยฟังก์ชั่นสมาชิก ถ้าตารางควรได้รับทุกส่วนของมันผ่านคอนสตรัคเตอร์ขึ้นอยู่กับว่าคุณต้องการเพิ่มชิ้นส่วนเหล่านั้นในเวลาการก่อสร้างและไม่เคยเปลี่ยนในภายหลังหรือถ้าคุณต้องการสร้างตารางทีละขั้นตอน นั่นควรเป็นพื้นฐานในการตัดสินใจออกแบบของคุณ
Doc Brown

1

การรักษาสำหรับการพึ่งพาแบบวงกลมคือการปฏิญาณว่าคุณจะไม่มีวันสร้างมันขึ้นมา ฉันพบว่าการทดสอบการเข้ารหัสครั้งแรกเป็นตัวยับยั้งที่แข็งแกร่ง

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

แต่อย่างที่คุณสงสัยทางออกที่ดีที่สุดสำหรับปัญหานี้อาจไม่จำเป็นต้องมีการติดตามความสัมพันธ์ของวัตถุ หากคุณต้องการแปล XML เป็น SQL คุณไม่จำเป็นต้องแสดงกราฟข้อ จำกัด ในหน่วยความจำ กราฟข้อ จำกัด น่าจะดีถ้าคุณต้องการเรียกใช้อัลกอริทึมกราฟ แต่คุณไม่ได้พูดถึงดังนั้นฉันจะถือว่ามันไม่ใช่ข้อกำหนด คุณเพียงต้องการรายการตารางและรายการข้อ จำกัด และผู้เยี่ยมชมสำหรับแต่ละภาษา SQL ที่คุณต้องการให้การสนับสนุน สร้างตารางจากนั้นสร้างข้อ จำกัด ภายนอกตาราง จนกว่าความต้องการจะเปลี่ยนไปฉันจะไม่มีปัญหากับการเชื่อมต่อตัวสร้าง SQL กับ XML DOM บันทึกพรุ่งนี้เพื่อพรุ่งนี้


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