Nguyên tắc DRY trong thực hành tốt?


11

Tôi đang cố gắng tuân theo nguyên tắc DRY trong lập trình của mình hết sức có thể. Gần đây tôi đã học các mẫu thiết kế trong OOP và cuối cùng tôi đã lặp lại khá nhiều.

Tôi đã tạo một mẫu Kho lưu trữ cùng với các mẫu Factory và Gateway để xử lý sự kiên trì của tôi. Tôi đang sử dụng cơ sở dữ liệu trong ứng dụng của mình nhưng điều đó không thành vấn đề vì tôi có thể trao đổi Gateway và chuyển sang một loại kiên trì khác nếu tôi muốn.

Vấn đề tôi đã tự tạo ra cho mình là tôi tạo cùng một đối tượng cho số lượng bảng tôi có. Ví dụ, đây sẽ là các đối tượng tôi cần để xử lý một bảng 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);
    }
}

Sau đó, bộ điều khiển của tôi trông giống như

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

Vì vậy, tôi nghĩ rằng tôi đã sử dụng các mẫu thiết kế một cách chính xác và giữ các thông lệ tốt, nhưng vấn đề với điều này là khi tôi thêm một bảng mới, tôi phải tạo cùng một lớp chỉ với các tên khác. Điều này làm tăng sự nghi ngờ trong tôi rằng tôi có thể đang làm gì đó sai.

Tôi đã nghĩ đến một giải pháp thay vì các giao diện, tôi có các lớp trừu tượng sử dụng tên lớp để tìm ra bảng mà họ cần thao tác nhưng dường như đó không phải là điều đúng, nếu tôi quyết định chuyển sang lưu trữ tệp hoặc memcache nơi không có bảng.

Tôi đang tiếp cận điều này một cách chính xác, hay có một quan điểm khác mà tôi nên xem xét?


Khi bạn tạo một bảng mới, bạn có luôn sử dụng cùng một bộ truy vấn SQL (hoặc một bộ cực kỳ giống nhau) để tương tác với nó không? Ngoài ra, Nhà máy có gói gọn bất kỳ logic có ý nghĩa nào trong chương trình thực không?
Ixrec

@Ixrec thường sẽ có các phương thức tùy chỉnh trong cổng và kho lưu trữ thực hiện các truy vấn sql phức tạp hơn như tham gia, vấn đề là các hàm bền, truy xuất và xóa - được xác định bởi giao diện - luôn giống nhau ngoại trừ tên bảng và có thể nhưng không chắc là cột khóa chính, vì vậy tôi phải lặp lại những điều đó trong mỗi lần thực hiện. Nhà máy rất hiếm khi giữ bất kỳ logic nào và đôi khi tôi bỏ qua nó và có cổng trả về đối tượng thay vì dữ liệu, nhưng tôi đã tạo một nhà máy cho ví dụ này vì nó có phải là thiết kế phù hợp không?
Emilio

Tôi có thể không đủ điều kiện để đưa ra một câu trả lời thích hợp, nhưng tôi có ấn tượng rằng 1) các lớp Factory và Kho lưu trữ không thực sự làm gì hữu ích, vì vậy bạn nên từ bỏ chúng và làm việc chỉ với Comment và CommentGateway trực tiếp 2) Có thể đặt các hàm duy trì / truy xuất / xóa chung ở một nơi thay vì sao chép chúng, có lẽ trong một lớp trừu tượng của "triển khai mặc định" (giống như những gì Bộ sưu tập trong Java làm)
Ixrec

Câu trả lời:


12

Vấn đề bạn đang giải quyết là khá cơ bản.

Tôi đã gặp vấn đề tương tự khi tôi làm việc cho một công ty đã tạo ra một ứng dụng J2EE lớn bao gồm hàng trăm trang web và hơn một triệu rưỡi mã Java. Mã này đã sử dụng ORM (JPA) để duy trì.

Vấn đề này trở nên tồi tệ hơn khi bạn sử dụng các công nghệ của bên thứ 3 trong mọi lớp của kiến ​​trúc và tất cả các công nghệ đều yêu cầu biểu diễn dữ liệu của riêng họ.

Vấn đề của bạn không thể được giải quyết ở cấp độ ngôn ngữ lập trình bạn đang sử dụng. Sử dụng các mẫu là tốt nhưng như bạn thấy nó gây ra sự lặp lại của mã (chính xác hơn là đặt quặng: sự lặp lại của thiết kế).

Cách tôi nhìn thấy nó chỉ có 3 giải pháp khả thi. Trong thực tế các giải pháp đi xuống như nhau.

Giải pháp 1: Sử dụng một số khung kiên trì khác cho phép bạn chỉ nêu những gì phải được duy trì. Có lẽ có một khung như vậy xung quanh. Vấn đề với cách tiếp cận này là nó khá ngây thơ vì không phải tất cả các mẫu của bạn sẽ liên quan đến sự kiên trì. Bạn cũng muốn sử dụng các mẫu cho mã giao diện người dùng để sau đó bạn cần một khung GUI có thể sử dụng lại các biểu diễn dữ liệu của khung bền vững mà bạn chọn. Nếu bạn không thể sử dụng lại chúng, bạn sẽ cần phải viết mã tấm nồi hơi để kết nối các biểu diễn dữ liệu của khung GUI và khung bền vững .. và điều này lại trái với nguyên tắc DRY.

Giải pháp 2: Sử dụng ngôn ngữ lập trình khác - mạnh hơn - có các cấu trúc cho phép bạn thể hiện thiết kế lặp đi lặp lại để bạn có thể sử dụng lại mã thiết kế. Đây có lẽ không phải là một lựa chọn cho bạn nhưng giả sử trong một khoảnh khắc. Sau đó, một lần nữa khi bạn bắt đầu tạo giao diện người dùng ở trên lớp kiên trì, bạn sẽ muốn ngôn ngữ trở lại đủ mạnh để hỗ trợ tạo GUI mà không phải viết mã tấm nồi hơi. Không chắc là có một ngôn ngữ đủ mạnh để làm những gì bạn muốn vì hầu hết các ngôn ngữ đều dựa vào khung của bên thứ 3 để xây dựng GUI mà mỗi ngôn ngữ yêu cầu biểu diễn dữ liệu của riêng họ để hoạt động.

Giải pháp 3: Tự động hóa việc lặp lại mã và thiết kế bằng cách sử dụng một số hình thức tạo mã. Lo lắng của bạn là về việc phải lặp lại mã tay của các mẫu và thiết kế vì mã / thiết kế lặp lại mã hóa vi phạm nguyên tắc DRY. Ngày nay có các khung tạo mã rất mạnh ngoài kia. Thậm chí còn có "bàn làm việc ngôn ngữ" cho phép bạn nhanh chóng (nửa ngày khi bạn không có kinh nghiệm) tạo ngôn ngữ lập trình của riêng bạn và tạo bất kỳ mã nào (PHP / Java / SQL - bất kỳ tệp văn bản có thể nghĩ nào) bằng ngôn ngữ đó. Tôi có kinh nghiệm với XText nhưng MetaEdit và MPS dường như cũng tốt. Tôi thực sự khuyên bạn nên kiểm tra một trong những bàn làm việc ngôn ngữ này. Đối với tôi đó là trải nghiệm tự do nhất trong cuộc đời chuyên nghiệp của tôi.

Sử dụng Xtext, bạn có thể tạo cho máy của mình tạo mã lặp lại. Xtext thậm chí còn tạo một trình soạn thảo tô sáng cú pháp cho bạn với việc hoàn thành mã cho đặc tả ngôn ngữ của riêng bạn. Từ thời điểm đó, bạn chỉ cần lấy cổng và lớp nhà máy của mình và biến chúng thành các mẫu mã bằng cách đục lỗ trên chúng. Bạn cung cấp chúng cho trình tạo của bạn (được gọi bởi trình phân tích cú pháp ngôn ngữ của bạn cũng được tạo hoàn toàn bởi Xtext) và trình tạo sẽ điền vào các lỗ hổng trong các mẫu của bạn. Kết quả là mã được tạo. Từ thời điểm đó, bạn có thể đưa ra bất kỳ sự lặp lại mã nào ở bất cứ đâu (mã lưu giữ mã GUI, v.v.).


Cảm ơn bạn đã trả lời, tôi đã nghiêm túc xem xét việc tạo mã và tôi thậm chí đang bắt đầu thực hiện một giải pháp. Chúng là 4 lớp soạn sẵn nên tôi đoán tôi có thể làm điều đó trong chính PHP. Mặc dù điều này không giải quyết được vấn đề mã lặp lại, tôi nghĩ rằng sự đánh đổi là xứng đáng - có khả năng duy trì cao và dễ dàng thay đổi mặc dù mã lặp đi lặp lại.
Emilio

Đây là lần đầu tiên tôi nghe nói về XText và nó trông rất mạnh mẽ. Cảm ơn bạn làm cho tôi nhận thức được điều này!
Matthew James Briggs

8

Vấn đề bạn gặp phải là một vấn đề cũ: mã cho các đối tượng liên tục thường trông giống nhau cho mỗi lớp, nó chỉ đơn giản là mã soạn sẵn. Đó là lý do tại sao một số người thông minh đã phát minh ra Mappers đối tượng quan hệ - họ giải quyết chính xác vấn đề đó. Xem bài viết SO cũ này để biết danh sách các ORM cho PHP.

Khi các ORM hiện tại không đáp ứng nhu cầu của bạn, cũng có một cách khác: bạn có thể viết trình tạo mã của riêng mình, mô tả meta của các đối tượng của bạn để duy trì và tạo ra phần lặp lại của mã từ đó. Điều đó thực sự không quá khó, tôi đã làm điều này trong quá khứ cho một số ngôn ngữ lập trình khác nhau, tôi chắc chắn cũng có thể thực hiện những điều như vậy trong PHP.


Tôi đã tạo ra chức năng như vậy nhưng tôi đã chuyển từ nó sang chức năng này bởi vì tôi đã từng có đối tượng dữ liệu xử lý các tác vụ lưu trữ dữ liệu không tuân thủ SRP. Ví dụ tôi đã từng sử dụng Model::getByPKphương thức và trong ví dụ trên tôi có thể làm Comment::getByPKđược nhưng lấy dữ liệu từ cơ sở dữ liệu và xây dựng đối tượng đều có trong lớp đối tượng dữ liệu, đó là vấn đề tôi đang cố gắng giải quyết bằng cách sử dụng các mẫu thiết kế .
Emilio

Các ORM không phải đặt logic bền vững trong đối tượng mô hình. Đây là mẫu Bản ghi hoạt động và trong khi phổ biến có những lựa chọn thay thế. Hãy xem những ORM nào có sẵn và bạn sẽ tìm thấy một ORM không có vấn đề này.
Jules

@Jules đó là một điểm rất tốt, nó làm tôi suy nghĩ và tôi đã tự hỏi - điều gì sẽ là vấn đề của việc có cả triển khai ActiveRecord và Data Mapper trong ứng dụng của tôi. Sau đó, tôi có thể sử dụng từng cái khi tôi cần chúng - điều này sẽ giải quyết vấn đề viết lại cùng một mã bằng cách sử dụng mẫu ActiveRecord và sau đó khi tôi thực sự cần một trình ánh xạ dữ liệu, sẽ không quá khó để tạo ra các lớp cần thiết cho công việc?
Emilio

1
Vấn đề duy nhất tôi có thể thấy với điều này ngay bây giờ là xử lý các trường hợp cạnh khi một truy vấn cần tham gia hai bảng trong đó một bảng sử dụng Bản ghi hoạt động và bảng kia được quản lý bởi Data Mapper của bạn - nó sẽ thêm một lớp phức tạp nếu không sẽ 'phát sinh. Cá nhân, tôi chỉ sử dụng trình ánh xạ - tôi chưa bao giờ thích Active Record ngay từ đầu - nhưng tôi biết đó chỉ là ý kiến ​​của tôi và những người khác không đồng ý.
Jules
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.