Đấu tranh với nguyên tắc trách nhiệm duy nhất


11

Xem xét ví dụ này:

Tôi có một trang web. Nó cho phép người dùng tạo bài đăng (có thể là bất cứ điều gì) và thêm các thẻ mô tả bài đăng. Trong mã, tôi có hai lớp đại diện cho bài viết và thẻ. Hãy gọi những lớp này PostTag.

Postđảm nhiệm việc tạo bài viết, xóa bài đăng, cập nhật bài viết, v.v ... Tagđảm nhiệm việc tạo thẻ, xóa thẻ, cập nhật thẻ, v.v.

Có một hoạt động bị thiếu. Việc liên kết các thẻ với bài viết. Tôi đang vật lộn với ai nên thực hiện thao tác này. Nó có thể phù hợp như nhau trong cả hai lớp.

Một mặt, Postlớp có thể có một hàm lấy Tagtham số, và sau đó lưu nó vào danh sách các thẻ. Mặt khác, Taglớp có thể có một hàm lấy Posttham số và liên kết Tagđến Post.

Trên đây chỉ là một ví dụ về vấn đề của tôi. Tôi thực sự đang chạy vào điều này với nhiều lớp hoàn toàn giống nhau. Nó có thể phù hợp như nhau trong cả hai. Nói ngắn gọn là thực sự đưa chức năng vào cả hai lớp, những quy ước hay phong cách thiết kế nào tồn tại để giúp tôi giải quyết vấn đề này. Tôi đang giả sử phải có một cái gì đó ngắn gọn chỉ cần chọn một?

Có lẽ đặt nó trong cả hai lớp là câu trả lời đúng?

Câu trả lời:


11

Giống như Mã hải tặc, SRP là một hướng dẫn hơn là một quy tắc và nó thậm chí không phải là một từ đặc biệt tốt. Hầu hết các nhà phát triển đã chấp nhận các định nghĩa lại của Martin Fowler (trong Tái cấu trúc ) và Robert Martin (trong Clean Code ), cho rằng một lớp chỉ nên có một lý do để thay đổi (trái ngược với một trách nhiệm).

Đó là một hướng dẫn tốt, vững chắc (xin lỗi chơi chữ), nhưng nó gần như nguy hiểm khi bị treo lên vì nó là để bỏ qua nó.

Nếu bạn có thể thêm Bài đăng vào Thẻ ngược lại, bạn đã không vi phạm nguyên tắc trách nhiệm đơn lẻ. Cả hai vẫn chỉ có một lý do để thay đổi - nếu cấu trúc của đối tượng đó thay đổi. Thay đổi cấu trúc của một trong hai không thay đổi cách nó được thêm vào cái khác, vì vậy bạn không thêm "trách nhiệm" mới cho nó.

Quyết định cuối cùng của bạn nên thực sự được quyết định bởi chức năng cần có ở mặt trước. Có khả năng sẽ cần thêm Thẻ vào Bài đăng tại một số điểm, vì vậy hãy thực hiện một số việc như sau:

// C-style-language pseudo-code
class Post {
    string _title;
    string _content;
    Date _date;
    List<Tag> _tags;

    Post(string title, string content) {
        _title = title;
        _content = content;
        _date = Now;
        _tags = new List<Tag>();
    }

    Tag[] getTags() {
        return _tags.toArray();
    }

    void addTag(Tag tag) {
        if (_tags.contains(tag)) {
            throw "Cannot add tag twice";
        }

        _tags.Add(tag);
        tag.referencePost(this);
    }

    // more stuff here, obviously
}

class Tag {
    string _name;
    List<Post> _posts;

    Tag(string name) {
        _name = name;
    }

    Post[] getPosts() {
        return _posts.toArray();
    }

    void referencePost(Post post) {
        if (!post.getTags().contains(this) || _posts.contains(post)) {
            throw "Only reference a post by calling Post.addTag()";
        }

        _posts.Add(post);
    }

    // more stuff here too
}

Nếu, sau này, bạn cũng thấy cần thêm Bài vào Thẻ, chỉ cần thêm phương thức addPost vào lớp Thẻ và phương thức ReferenceTag cho lớp Bài. Rõ ràng, tôi đã đặt tên chúng theo cách khác để bạn không vô tình gây ra lỗi tràn ngăn xếp bằng cách gọi addTag từ addPost và addPost từ addTag.


Tôi nghĩ rằng mối quan hệ giữa Tag và Post là nhiều-nhiều, trong trường hợp đó, nó có ý nghĩa đối với một Tag để giữ các tham chiếu đến nhiều bài viết. Làm thế nào bạn sẽ xử lý này nếu bạn giữ một tài liệu tham khảo?
Andres F.

@AndresF.: Tôi đồng ý với bạn, vì vậy tôi rõ ràng đã không viết câu trả lời của mình rất tốt. Tôi đã chỉnh sửa đáng kể. (Xin lỗi người nâng cấp trước nếu điều này thay đổi ý nghĩa như bạn đã thấy.)
pdr

6

Không, không phải trong cả hai! Nó nên ở một nơi.

Điều tôi thấy không hài lòng trong câu hỏi của bạn là việc bạn nói " Postquan tâm đến việc tạo bài viết, xóa bài đăng, cập nhật bài viết" và tương tự cho Tag. Vâng, điều đó không đúng. PostChỉ có thể chăm sóc cập nhật, tương tự cho Tag. Tạo và xóa là công việc của người khác, bên ngoài PostTag(hãy gọi nó là Store).

Trách nhiệm tốt Postlà "biết tác giả, nội dung và ngày cập nhật cuối cùng". Trách nhiệm tốt Taglà "biết tên và mục đích của nó (đọc: mô tả)". Trách nhiệm tốt Storelà "biết tất cả Bài đăng và tất cả Thẻ và có thể thêm, xóa và tìm kiếm chúng".

Nếu bạn nhìn vào ba người tham gia này, ai là người tự nhiên nhất nên có kiến ​​thức về mối quan hệ Post-Tag?

(đối với tôi, đó là Bài đăng, có vẻ tự nhiên rằng nó "biết các thẻ của nó"; tìm kiếm ngược (tất cả các bài đăng cho một thẻ) dường như là công việc của Cửa hàng; mặc dù tôi có thể nhầm lẫn)


Nếu mỗi bài đăng có một danh sách các thẻ và / hoặc mỗi thẻ có một danh sách các bài đăng bao gồm nó, người ta có thể dễ dàng trả lời câu hỏi "có bài x bao gồm thẻ y" không. Có cách nào để trả lời một cách hiệu quả một câu hỏi như vậy mà không có lớp nào chịu trách nhiệm, ngoài việc sử dụng một cái gì đó giống như ConditionalWeakTable(giả sử một người đủ may mắn để có một khuôn khổ nơi một người tồn tại)?
supercat

3

Có một chi tiết quan trọng bị thiếu trong phương trình. Tại sao thẻ chứa bài và visa-ngược lại? Câu trả lời cho câu hỏi này xác định giải pháp cho từng bộ đã cho.

Nói chung tôi có thể nghĩ về một tình huống tương tự. Một hộp và nội dung. Một hộp có nội dung để mối quan hệ có-có là phù hợp. Nội dung có thể có một hộp? Chắc chắn, một hộp với một hộp. Một hộp là nội dung. Nhưng IS-A không phải là một thiết kế tốt cho tất cả các hộp. Trong trường hợp như vậy tôi sẽ xem xét mô hình trang trí. Bằng cách này, một hộp được trang trí với nội dung trong thời gian chạy khi cần thiết.

Thẻ cũng có thể có Bài viết, nhưng với tôi đây không phải là một mối quan hệ tĩnh. Thay vào đó có thể là một báo cáo của tất cả các bài viết có thẻ nói. Trong trường hợp này, nó là một thực thể mới, không có-a.


2

Mặc dù trong lý thuyết, những thứ như thế có thể đi theo một cách nào đó, trong thực tế khi bạn bắt tay vào thực hiện, một cách gần như luôn luôn phù hợp hơn so với cách khác. Linh cảm của tôi là nó sẽ phù hợp hơn trong Postlớp vì liên kết sẽ được tạo trong quá trình tạo hoặc chỉnh sửa bài đăng, khi những thứ khác về bài đăng đang thay đổi cùng một lúc.

Ngoài ra, nếu bạn đang liên kết nhiều thẻ và muốn thực hiện nó trong một bản cập nhật cơ sở dữ liệu, bạn cần xây dựng một số loại danh sách tất cả các thẻ được liên kết với cùng một bài trước khi thực hiện cập nhật. Danh sách đó phù hợp hơn rất nhiều trong Postlớp.


1

Cá nhân, tôi sẽ không thêm chức năng đó cho một trong số họ.

Đối với tôi, cả hai PostTagđều là đối tượng dữ liệu, vì vậy không nên xử lý chức năng cơ sở dữ liệu. Họ nên đơn giản tồn tại. Chúng có nghĩa là để giữ dữ liệu và được sử dụng bởi các phần khác trong ứng dụng của bạn.

Thay vào đó, tôi có một lớp khác chịu trách nhiệm về logic kinh doanh và dữ liệu liên quan đến trang web của bạn. Nếu trang của bạn đang hiển thị một bài đăng và cho phép người dùng thêm thẻ, thì lớp sẽ có một Postđối tượng và chứa chức năng để thêm Tagsvào đó Post. Nếu trang của bạn đang hiển thị các thẻ và cho phép người dùng thêm bài đăng vào các thẻ đó, nó sẽ chứa một Tagđối tượng và có chức năng để thêm Postsvào đó Tag.

Đó chỉ là tôi. Nếu bạn cảm thấy rằng bạn phải xử lý chức năng cơ sở dữ liệu trong các đối tượng dữ liệu của mình, thì tôi khuyên bạn nên trả lời câu trả lời của pdr


0

Khi tôi đọc câu hỏi này, điều đầu tiên xuất hiện trong đầu là mối quan hệ cơ sở dữ liệu Many To Many . Bài viết có thể có nhiều Thẻ ... Thẻ có thể có nhiều Bài viết .... Đối với tôi, cả hai lớp đều cần khả năng quản lý mối quan hệ này ở một mức độ nào đó.

Từ quan điểm Đăng ...
Nếu bạn chỉnh sửa hoặc tạo Đăng, một hoạt động thứ cấp sẽ trở thành quản lý các mối quan hệ Thẻ .

  1. Thêm thẻ hiện có vào bài viết
  2. Revove Tag từ bài viết

IMO, việc tạo ra một TAG hoàn toàn mới không thuộc về nơi này.

Từ quan điểm Thẻ ...
Bạn có thể tạo Thẻ mà không phải gán thẻ cho Bài đăng. Hoạt động duy nhất tôi thấy liên quan đến việc tương tác với Bài đăng là chức năng Xóa Thẻ. Tuy nhiên, chức năng này phải là một chức năng độc lập độc lập.

Điều này sẽ chỉ hoạt động nếu có một bảng liên kết cơ sở dữ liệu giải quyết mối quan hệ Nhiều đến Nhiều

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.