หลักการแห้งในแนวปฏิบัติที่ดี?


11

ฉันพยายามทำตามหลักการ DRY ในการเขียนโปรแกรมของฉันอย่างหนักที่สุดเท่าที่จะทำได้ เมื่อไม่นานมานี้ฉันได้เรียนรู้รูปแบบการออกแบบใน OOP แล้วและฉันก็ทำซ้ำตัวเองค่อนข้างมาก

ฉันสร้างรูปแบบ Repository พร้อมกับรูปแบบ Factory และ Gateway เพื่อจัดการกับความคงทนของฉัน ฉันใช้ฐานข้อมูลในแอปพลิเคชันของฉัน แต่ไม่ควรทำเช่นนี้เพราะฉันควรจะสามารถสลับเกตเวย์และเปลี่ยนไปใช้ความพยายามชนิดอื่นได้หากฉันต้องการ

ปัญหาที่ฉันสร้างเพื่อตัวเองคือฉันสร้างวัตถุเดียวกันสำหรับจำนวนตารางที่ฉันมี commentsตัวอย่างเหล่านี้จะเป็นวัตถุที่ฉันต้องการที่จะจัดการกับตาราง

class Comment extends Model {

    protected $id;
    protected $author;
    protected $text;
    protected $date;
}

class CommentFactory implements iFactory {

    public function createFrom(array $data) {
        return new Comment($data);
    }
}

class CommentGateway implements iGateway {

    protected $db;

    public function __construct(\Database $db) {
        $this->db = $db;
    }

    public function persist($data) {

        if(isset($data['id'])) {
            $sql = 'UPDATE comments SET author = ?, text = ?, date = ? WHERE id = ?';
            $this->db->prepare($sql)->execute($data['author'], $data['text'], $data['date'], $data['id']);
        } else {
            $sql = 'INSERT INTO comments (author, text, date) VALUES (?, ?, ?)';
            $this->db->prepare($sql)->execute($data['author'], $data['text'], $data['date']);
        }
    }

    public function retrieve($id) {

        $sql = 'SELECT * FROM comments WHERE id = ?';
        return $this->db->prepare($sql)->execute($id)->fetch();
    }

    public function delete($id) {

        $sql = 'DELETE FROM comments WHERE id = ?';
        return $this->db->prepare($sql)->execute($id)->fetch();
    }
}

class CommentRepository {

    protected $gateway;
    protected $factory;

    public function __construct(iFactory $f, iGateway $g) {
        $this->gateway = $g;
        $this->factory = $f;
    }

    public function get($id) {

        $data = $this->gateway->retrieve($id);
        return $this->factory->createFrom($data);
    }

    public function add(Comment $comment) {

        $data = $comment->toArray();
        return $this->gateway->persist($data);
    }
}

จากนั้นตัวควบคุมของฉันดูเหมือน

class Comment {

    public function view($id) {

        $gateway = new CommentGateway(Database::connection());
        $factory = new CommentFactory();
        $repo = new CommentRepository($factory, $gateway);

        return Response::view('comment/view', $repo->get($id));
    }
}

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

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

ฉันเข้าใกล้สิ่งนี้อย่างถูกต้องหรือมีมุมมองที่ต่างออกไป


เมื่อคุณสร้างตารางใหม่คุณใช้ชุดคำสั่ง SQL ชุดเดียวกันเสมอ (หรือชุดที่คล้ายกันมาก) เพื่อโต้ตอบกับมันหรือไม่? นอกจากนี้ Factory ยังรวมถึงตรรกะที่มีความหมายใด ๆ ในโปรแกรมจริงหรือไม่?
Ixrec

@Ixrec โดยทั่วไปจะมีวิธีการที่กำหนดเองในเกตเวย์และพื้นที่เก็บข้อมูลที่ดำเนินการแบบสอบถาม SQL ที่ซับซ้อนมากขึ้นเช่นเข้าร่วมปัญหาคือว่ายังคงมีอยู่ดึงและลบฟังก์ชั่น - กำหนดโดยอินเตอร์เฟซ - จะเหมือนกันยกเว้นชื่อตารางและอาจเป็นไปได้ แต่ไม่น่าจะเป็นคอลัมน์หลักหลักดังนั้นฉันต้องทำซ้ำในทุกการใช้งาน โรงงานไม่ค่อยมีเหตุผลใด ๆ และบางครั้งฉันก็ข้ามมันไปและให้เกตเวย์คืนค่าวัตถุแทนข้อมูล แต่ฉันสร้างโรงงานสำหรับตัวอย่างนี้เนื่องจากมันควรเป็นการออกแบบที่เหมาะสมหรือไม่
Emilio Rodrigues

ฉันอาจไม่ได้มีคุณสมบัติที่จะให้คำตอบที่ถูกต้อง แต่ฉันได้รับความประทับใจว่า 1) คลาส Factory และ Repository ไม่ได้ทำอะไรที่มีประโยชน์จริงๆดังนั้นคุณควรจะทิ้งความคิดไว้และทำงานกับแค่ Comment และ CommentGateway โดยตรง 2) มันควรจะเป็นไปได้ที่จะนำทั่วไปยังคงมีอยู่ / ดึง / ฟังก์ชั่นลบในที่เดียวมากกว่าการคัดลอกวางพวกเขาอาจอยู่ในระดับนามธรรมของ "การใช้งานเริ่มต้น" (kinda เช่นสิ่งที่คอลเลกชันในชวาทำ)
Ixrec

คำตอบ:


12

ปัญหาที่คุณพูดถึงนั้นค่อนข้างพื้นฐาน

ฉันประสบปัญหาเดียวกันเมื่อฉันทำงานให้กับ บริษัท ที่สร้างแอปพลิเคชัน J2EE ขนาดใหญ่ซึ่งประกอบด้วยหน้าเว็บหลายร้อยหน้าและมากกว่าหนึ่งล้านครึ่งโค้ด Java รหัสนี้ใช้ ORM (JPA) เพื่อคงอยู่

ปัญหานี้แย่ลงเมื่อคุณใช้เทคโนโลยีของบุคคลที่สามในทุกชั้นของสถาปัตยกรรมและเทคโนโลยีทั้งหมดนั้นต้องการการแสดงข้อมูลของตัวเอง

ปัญหาของคุณไม่สามารถแก้ไขได้ในระดับภาษาการเขียนโปรแกรมที่คุณใช้ การใช้รูปแบบนั้นดี แต่อย่างที่คุณเห็นทำให้เกิดการซ้ำซ้อนของรหัส (ใส่อย่างแม่นยำมากขึ้น: การทำซ้ำของการออกแบบ)

วิธีที่ฉันเห็นมันมีเพียง 3 แนวทางเท่านั้น ในทางปฏิบัติโซลูชันเหล่านี้จะลดลงเช่นเดียวกัน

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

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

โซลูชันที่ 3: การทำซ้ำรหัสและการออกแบบโดยอัตโนมัติโดยใช้การสร้างรหัสบางรูปแบบ ความกังวลของคุณเกี่ยวกับการทำซ้ำรูปแบบและรหัสด้วยมือเนื่องจากการทำซ้ำรหัส / การออกแบบที่ทำด้วยมือซ้ำ ๆ นั้นเป็นการละเมิดหลักการ DRY ทุกวันนี้มีกรอบตัวสร้างโค้ดที่ทรงพลังมาก มีแม้กระทั่ง "workbenches ภาษา" ที่ช่วยให้คุณได้อย่างรวดเร็ว (ครึ่งวันเมื่อคุณไม่มีประสบการณ์) สร้างภาษาการเขียนโปรแกรมของคุณเองและสร้างรหัสใด ๆ (PHP / Java / SQL - ไฟล์ข้อความใด ๆ ที่คิดได้) โดยใช้ภาษานั้น ฉันมีประสบการณ์กับ XText แต่ MetaEdit และ MPS ก็ใช้ได้เช่นกัน ฉันขอแนะนำให้คุณตรวจสอบภาษาเหล่านี้อย่างใดอย่างหนึ่ง สำหรับฉันมันเป็นประสบการณ์ที่ปลดปล่อยมากที่สุดในชีวิตการทำงานของฉัน

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


ขอบคุณสำหรับการตอบกลับฉันได้พิจารณาการสร้างรหัสอย่างจริงจังและฉันก็เริ่มที่จะใช้โซลูชัน พวกเขาเป็น 4 คลาสสำเร็จรูปดังนั้นฉันเดาว่าฉันสามารถทำได้ใน PHP เอง แม้ว่านี่จะไม่ได้แก้ปัญหาการทำซ้ำรหัสฉันคิดว่าการแลกเปลี่ยนมีค่า - การบำรุงรักษาสูงและเปลี่ยนแปลงได้ง่ายแม้ว่ารหัสซ้ำ
Emilio Rodrigues

นี่เป็นครั้งแรกที่ฉันเคยได้ยิน XText และดูมีประสิทธิภาพมาก ขอบคุณที่ทำให้ฉันตระหนักถึงสิ่งนี้!
Matthew James Briggs

8

ปัญหาที่คุณเผชิญคือสิ่งเก่า: รหัสสำหรับวัตถุถาวรมักจะมีลักษณะคล้ายกันในแต่ละชั้นเรียนมันเป็นเพียงรหัสสำเร็จรูป นั่นเป็นเหตุผลที่คนฉลาดบางคนคิดค้นObject Relational Mappers - พวกเขาแก้ปัญหานั้นได้อย่างแน่นอน ดูโพสต์ SO ก่อนหน้านี้สำหรับรายการ ORMs สำหรับ PHP

เมื่อ ORM ที่มีอยู่ไม่ได้รับความต้องการของคุณนอกจากนี้ยังมีอีกทางเลือกหนึ่ง: คุณสามารถเขียนตัวสร้างโค้ดของคุณเองซึ่งจะอธิบายเมตาของวัตถุของคุณให้คงอยู่และสร้างส่วนที่ซ้ำกันของรหัสจากนั้น ที่จริงแล้วไม่ยากเกินไปฉันทำสิ่งนี้ในอดีตสำหรับภาษาการเขียนโปรแกรมที่แตกต่างกันบางอย่างฉันแน่ใจว่ามันจะเป็นไปได้ที่จะใช้สิ่งต่าง ๆ เช่นนี้ใน PHP


ฉันได้สร้างฟังก์ชั่นดังกล่าว แต่ฉันเปลี่ยนจากมันเป็นเพราะฉันเคยให้ data object จัดการกับข้อมูลที่มีอยู่ซึ่งไม่สอดคล้องกับ SRP ตัวอย่างเช่นฉันเคยมีModel::getByPKวิธีการและในตัวอย่างข้างต้นฉันจะสามารถทำได้Comment::getByPKแต่รับข้อมูลจากฐานข้อมูลและการสร้างวัตถุทั้งหมดที่มีอยู่ในคลาสวัตถุข้อมูลซึ่งเป็นปัญหาที่ฉันพยายามที่จะแก้ปัญหาโดยใช้รูปแบบการออกแบบ .
Emilio Rodrigues

ORMs ไม่จำเป็นต้องวางตรรกะการคงอยู่ในโมเดลวัตถุ นี่คือรูปแบบ Active Record และในขณะที่ความนิยมมีทางเลือก ดูที่ ORM ใดที่พร้อมใช้งานและคุณควรค้นหาที่ไม่มีปัญหานี้
จูลส์

@Jules นั้นเป็นจุดที่ดีมากมันทำให้ฉันคิดและฉันก็สงสัย - สิ่งที่จะเป็นปัญหาของการมีทั้งการใช้งาน ActiveRecord และ Data Mapper ที่มีอยู่ในใบสมัครของฉัน จากนั้นฉันก็สามารถใช้แต่ละคนเมื่อฉันต้องการพวกเขา - นี่จะแก้ปัญหาของฉันในการเขียนรหัสเดียวกันโดยใช้รูปแบบ ActiveRecord แล้วเมื่อฉันต้องการตัวสร้างข้อมูลจริง ๆ แล้วมันก็ไม่น่าเจ็บปวดที่จะสร้างคลาสที่จำเป็น สำหรับงานหรือไม่
Emilio Rodrigues

1
ปัญหาเดียวที่ฉันเห็นในตอนนี้คือการแก้ไขกรณีขอบเมื่อแบบสอบถามต้องการเข้าร่วมสองตารางโดยที่หนึ่งใช้ Active Record และอีกอันหนึ่งถูกจัดการโดย Data Mapper ของคุณ - มันจะเพิ่มเลเยอร์ของความซับซ้อนที่มิฉะนั้นจะไม่ จะไม่เกิดขึ้น ส่วนตัวฉันจะใช้ mapper - ฉันไม่เคยชอบบันทึกการใช้งานตั้งแต่ต้น - แต่ฉันรู้ว่าเป็นเพียงความคิดเห็นของฉันและคนอื่นไม่เห็นด้วย
จูลส์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.