Trách nhiệm thực sự của một lớp học là gì?


42

Tôi cứ tự hỏi liệu có hợp pháp không khi sử dụng các động từ dựa trên danh từ trong OOP.
Tôi đã xem qua bài viết tuyệt vời này , mặc dù tôi vẫn không đồng ý với quan điểm của nó.

Để giải thích vấn đề nhiều hơn một chút, bài viết nói rằng không nên có một FileWriterlớp, nhưng vì viết là một hành động nên nó là một phương thức của lớp File. Bạn sẽ nhận ra rằng nó thường phụ thuộc ngôn ngữ vì một lập trình viên Ruby có thể sẽ chống lại việc sử dụng một FileWriterlớp (Ruby sử dụng phương thức File.openđể truy cập một tệp), trong khi đó, một lập trình viên Java thì không.

Quan điểm cá nhân của tôi (và vâng, rất khiêm tốn) là làm như vậy sẽ phá vỡ nguyên tắc Trách nhiệm duy nhất. Khi tôi lập trình bằng PHP (vì rõ ràng PHP là ngôn ngữ tốt nhất cho OOP, phải không?), Tôi thường sử dụng loại khung này:

<?php

// This is just an example that I just made on the fly, may contain errors

class User extends Record {

    protected $name;

    public function __construct($name) {
        $this->name = $name;
    }

}

class UserDataHandler extends DataHandler /* knows the pdo object */ {

    public function find($id) {
         $query = $this->db->prepare('SELECT ' . $this->getFields . ' FROM users WHERE id = :id');
         $query->bindParam(':id', $id, PDO::PARAM_INT);
         $query->setFetchMode( PDO::FETCH_CLASS, 'user');
         $query->execute();
         return $query->fetch( PDO::FETCH_CLASS );
    }


}

?>

Theo hiểu biết của tôi thì DataHandler không thêm bất cứ thứ gì có liên quan ; nhưng vấn đề là nguyên tắc trách nhiệm duy nhất cho chúng ta biết rằng một đối tượng được sử dụng làm mô hình chứa dữ liệu (có thể được gọi là Bản ghi) cũng không nên có trách nhiệm thực hiện các truy vấn SQL và truy cập DataBase. Điều này bằng cách nào đó làm mất hiệu lực mẫu ActionRecord được sử dụng bởi Ruby on Rails.

Tôi đã bắt gặp mã C # này (yay, ngôn ngữ đối tượng thứ tư được sử dụng trong bài đăng này) vào một ngày khác:

byte[] bytes = Encoding.Default.GetBytes(myString);
myString = Encoding.UTF8.GetString(bytes);

Và tôi phải nói rằng nó không có ý nghĩa gì với tôi rằng một Encodinghoặc một Charsetlớp thực sự mã hóa các chuỗi. Nó chỉ nên là một đại diện cho những gì một mã hóa thực sự là.

Vì vậy, tôi có xu hướng nghĩ rằng:

  • Việc Filemở, đọc hoặc lưu tệp không phải là trách nhiệm của lớp.
  • Nó không phải là một Xmltrách nhiệm giai cấp để tuần tự hóa chính nó.
  • UserTruy vấn cơ sở dữ liệu không phải là trách nhiệm của lớp.
  • Vân vân.

Tuy nhiên, nếu chúng ta ngoại suy những ý tưởng này, tại sao lại Objectcó một toStringlớp học? Bây giờ không phải là trách nhiệm của một chiếc Xe hay Chó để chuyển đổi thành một chuỗi, phải không?

Tôi hiểu rằng từ quan điểm thực dụng, loại bỏ toStringphương pháp cho vẻ đẹp của việc tuân theo một hình thức RẮN nghiêm ngặt, làm cho mã dễ duy trì hơn bằng cách làm cho nó vô dụng, không phải là một lựa chọn chấp nhận được.

Tôi cũng hiểu rằng có thể không có câu trả lời chính xác (sẽ là một bài luận hơn là một câu trả lời nghiêm túc) cho vấn đề này, hoặc nó có thể dựa trên ý kiến. Tuy nhiên, tôi vẫn muốn biết liệu cách tiếp cận của tôi có thực sự tuân theo nguyên tắc trách nhiệm đơn lẻ thực sự là gì không.

Trách nhiệm của một lớp học là gì?


Đối tượng toString là tiện lợi chủ yếu và toString thường được sử dụng để nhanh chóng xem trạng thái nội bộ trong quá trình gỡ lỗi
ratchet freak

3
@ratchetfreak Tôi đồng ý, nhưng đó là một ví dụ (gần như một trò đùa) để cho thấy rằng trách nhiệm duy nhất rất thường bị phá vỡ theo cách mà tôi đã mô tả.
Pierre Arlaud

8
Mặc dù nguyên tắc trách nhiệm duy nhất có những người ủng hộ nhiệt tình, nhưng theo tôi, đó là một hướng dẫn tốt nhất, và là một nguyên tắc rất lỏng lẻo cho phần lớn các lựa chọn thiết kế. Vấn đề với SRP là bạn kết thúc với hàng trăm lớp có tên kỳ quặc, điều này khiến việc tìm kiếm những gì bạn cần (hoặc tìm hiểu xem những gì bạn cần đã tồn tại) thực sự khó khăn và nắm bắt được bức tranh lớn. Tôi rất thích có ít lớp được đặt tên tốt hơn cho tôi biết ngay lập tức những gì mong đợi từ họ ngay cả khi họ có khá nhiều trách nhiệm. Nếu trong tương lai mọi thứ thay đổi thì tôi sẽ chia lớp.
Dunk

Câu trả lời:


24

Với một số khác biệt giữa các ngôn ngữ, đây có thể là một chủ đề khó khăn. Do đó, tôi đang xây dựng các bài bình luận sau đây theo cách cố gắng toàn diện nhất có thể trong phạm vi của OO.

Trước hết, cái gọi là "Nguyên tắc trách nhiệm duy nhất" là một phản xạ - được tuyên bố rõ ràng - về sự gắn kết khái niệm . Đọc tài liệu của thời đại (khoảng năm 70), mọi người đã (và vẫn đang) đấu tranh để xác định mô-đun là gì và cách xây dựng chúng theo cách bảo tồn các thuộc tính tốt. Vì vậy, họ sẽ nói "đây là một loạt các cấu trúc và thủ tục, tôi sẽ tạo ra một mô-đun từ chúng", nhưng không có tiêu chí nào về lý do tại sao tập hợp các thứ tùy ý này được đóng gói cùng nhau, tổ chức có thể cuối cùng có ý nghĩa - "sự gắn kết" nhỏ. Do đó, các cuộc thảo luận về tiêu chí đã xuất hiện.

Vì vậy, điều đầu tiên cần lưu ý ở đây là, cho đến nay, cuộc tranh luận xoay quanh tổ chức và các tác động liên quan đến bảo trì và dễ hiểu (đối với máy tính rất ít nếu mô-đun "có ý nghĩa").

Sau đó, một người khác (ông Martin) đã đến và áp dụng suy nghĩ tương tự cho đơn vị của một lớp như một tiêu chí để sử dụng khi nghĩ về những gì nên hay không nên thuộc về nó, thúc đẩy tiêu chí này thành một nguyên tắc, một tiêu chí đang được thảo luận đây. Quan điểm mà ông đưa ra là "Một lớp chỉ nên có một lý do để thay đổi" .

Vâng, chúng tôi biết từ kinh nghiệm rằng nhiều đối tượng (và nhiều lớp) dường như làm "nhiều việc" có lý do rất tốt để làm việc đó. Trường hợp không mong muốn sẽ là các lớp được làm đầy với chức năng đến mức không thể bảo trì, v.v. Và để hiểu cái sau là xem mr ở đâu. Martin đã nhắm đến khi ông xây dựng về chủ đề này.

Tất nhiên, sau khi đọc những gì ông. Martin đã viết, cần phải rõ ràng đây là những tiêu chí cho định hướng và thiết kế để tránh các tình huống có vấn đề, không theo bất kỳ cách nào để theo đuổi bất kỳ loại tuân thủ nào, chứ đừng nói đến việc tuân thủ mạnh mẽ, đặc biệt khi "trách nhiệm" không được xác định (và các câu hỏi như "làm điều này vi phạm nguyên tắc? "là những ví dụ hoàn hảo cho sự nhầm lẫn phổ biến). Vì vậy, tôi thấy không may nó được gọi là một nguyên tắc, gây hiểu lầm cho mọi người để cố gắng đưa nó đến hậu quả cuối cùng, nơi nó sẽ làm không tốt. Chính ông Martin đã thảo luận về các thiết kế "làm nhiều hơn một việc" có lẽ nên được giữ theo cách đó, vì việc tách ra sẽ mang lại kết quả tồi tệ hơn. Ngoài ra, có nhiều thách thức được biết đến về tính mô đun (và chủ đề này là trường hợp của nó), chúng tôi không có câu trả lời tốt ngay cả đối với một số câu hỏi đơn giản về nó.

Tuy nhiên, nếu chúng ta ngoại suy những ý tưởng này, tại sao Object lại có lớp toString? Bây giờ không phải là trách nhiệm của một chiếc Xe hay Chó để chuyển đổi thành một chuỗi, phải không?

Bây giờ, hãy để tôi tạm dừng để nói điều gì đó ở đây toString: có một điều cơ bản thường bị bỏ qua khi người ta thực hiện quá trình chuyển đổi suy nghĩ từ mô-đun sang các lớp và suy nghĩ về phương thức nào nên thuộc về một lớp. Và điều là công văn động (hay còn gọi là ràng buộc muộn, "đa hình").

Trong một thế giới không có "phương thức ghi đè", việc chọn giữa "obj.toString ()" hoặc "toString (obj)" chỉ là vấn đề ưu tiên cú pháp. Tuy nhiên. và điều tương tự có thể không đúng với "thủ tục miễn phí" (các ngôn ngữ hỗ trợ đa phương thức có cách thoát khỏi sự phân đôi này). Do đó, nó không chỉ là một cuộc thảo luận về tổ chức mà còn về ngữ nghĩa nữa. Cuối cùng, phương thức nào bị ràng buộc, cũng trở thành một quyết định có ảnh hưởng (và trong nhiều trường hợp, cho đến nay, chúng tôi có ít hơn các hướng dẫn để giúp chúng tôi quyết định nơi mọi thứ thuộc về,

Cuối cùng, chúng ta phải đối mặt với các ngôn ngữ mang các quyết định thiết kế khủng khiếp, ví dụ, buộc người ta phải tạo ra một lớp cho từng chút một. Do đó, lý do chính tắc và tiêu chí chính để có các đối tượng (và trong đất lớp, do đó, các lớp) là gì, có những "đối tượng" đó là loại "hành vi cũng hành xử giống như dữ liệu" , nhưng bảo vệ đại diện cụ thể của họ (nếu có) khỏi thao tác trực tiếp bằng mọi giá (và đó là gợi ý chính cho giao diện của một đối tượng, theo quan điểm của khách hàng), bị mờ và nhầm lẫn.


31

[Lưu ý: Tôi sẽ nói về các đối tượng ở đây. Đối tượng là những gì lập trình hướng đối tượng, sau tất cả, không phải là các lớp.]

Trách nhiệm của một đối tượng phụ thuộc chủ yếu vào mô hình miền của bạn. Thường có nhiều cách để mô hình hóa cùng một tên miền và bạn sẽ chọn cách này hay cách khác dựa trên cách hệ thống sẽ được sử dụng.

Như chúng ta đã biết, phương pháp "động từ / danh từ" thường được dạy trong các khóa học giới thiệu là vô lý, bởi vì nó phụ thuộc rất nhiều vào cách bạn hình thành một câu. Bạn có thể diễn đạt hầu hết mọi thứ như một danh từ hoặc động từ, bằng giọng chủ động hoặc bị động. Có mô hình miền của bạn phụ thuộc vào điều đó là sai, nó nên là một lựa chọn thiết kế có ý thức cho dù đó là một đối tượng hay một phương thức, không chỉ là hậu quả ngẫu nhiên của cách bạn hình thành các yêu cầu.

Tuy nhiên, điều đó cho thấy rằng, giống như bạn có nhiều khả năng diễn đạt điều tương tự bằng tiếng Anh, bạn có nhiều khả năng diễn đạt điều tương tự trong mô hình miền của bạn, và không có khả năng nào trong số đó là chính xác hơn những khả năng đó. Nó phụ thuộc vào ngữ cảnh.

Dưới đây là một ví dụ cũng rất phổ biến trong các khóa học OO giới thiệu: tài khoản ngân hàng. Mà thường được mô hình hóa như một đối tượng với một balancetrường và một transferphương thức. Nói cách khác: số dư tài khoản là dữ liệu và chuyển khoản là một hoạt động . Đó là một cách hợp lý để mô hình hóa một tài khoản ngân hàng.

Ngoại trừ, đó không phải là cách các tài khoản ngân hàng được mô hình hóa trong phần mềm ngân hàng trong thế giới thực (và trên thực tế, đó không phải là cách các ngân hàng hoạt động trong thế giới thực). Thay vào đó, bạn có một phiếu giao dịch và số dư tài khoản được tính bằng cách thêm (và trừ) lên tất cả các phiếu giao dịch cho một tài khoản. Nói cách khác: chuyển tiền là dữ liệu và số dư là một hoạt động ! (Thật thú vị, điều này cũng làm cho hệ thống của bạn hoàn toàn hoạt động, vì bạn không bao giờ cần phải sửa đổi số dư, cả đối tượng tài khoản và đối tượng giao dịch không bao giờ cần thay đổi, chúng là bất biến.)

Đối với câu hỏi cụ thể của bạn về toString, tôi đồng ý. Tôi rất thích giải pháp Haskell của một Showable lớp loại . (Scala dạy chúng ta rằng các lớp loại phù hợp tuyệt vời với OO.) Tương tự với sự bình đẳng. Bình đẳng thường không phải là một thuộc tính của một đối tượng mà là bối cảnh mà đối tượng được sử dụng. Chỉ cần nghĩ về số dấu phẩy động: epsilon nên là gì? Một lần nữa, Haskell có Eqlớp loại.


Tôi thích sự so sánh haskell của bạn, nó làm cho câu trả lời thêm một chút mới mẻ. Sau đó, bạn sẽ nói gì về các thiết kế cho phép ActionRecordcả người mẫu và người yêu cầu Cơ sở dữ liệu? Bất kể bối cảnh nó dường như đang phá vỡ nguyên tắc trách nhiệm duy nhất.
Pierre Arlaud

5
"... bởi vì nó phụ thuộc rất nhiều vào cách bạn xây dựng một câu." Yay! Oh và tôi thích ví dụ ngân hàng của bạn. Không bao giờ biết rằng trong suốt thập niên tám mươi, tôi đã được dạy lập trình chức năng :) (thiết kế tập trung vào dữ liệu và lưu trữ độc lập với thời gian, nơi cân bằng, v.v. có thể tính toán và không nên lưu trữ, và không nên thay đổi địa chỉ của ai đó một sự thật mới) ...
Marjan Venema 4/12/13

1
Tôi sẽ không nói phương pháp Động từ / Danh từ là "nực cười". Tôi muốn nói rằng các thiết kế đơn giản sẽ thất bại và rất khó để có được đúng. Bởi vì, ví dụ mẫu, không phải là API RESTful một phương pháp Động từ / Danh từ? Ở đâu, đối với một mức độ khó khăn thêm, các động từ là vô cùng hạn chế?
dùng949300

@ user949300: Thực tế là tập hợp các động từ đã được sửa, tránh chính xác vấn đề tôi đã đề cập, cụ thể là bạn có thể tạo câu theo nhiều cách khác nhau, từ đó tạo ra danh từ và động từ phụ thuộc vào cách viết và không phân tích tên miền .
Jörg W Mittag

8

Tất cả quá thường xuyên Nguyên tắc Trách nhiệm duy nhất trở thành Nguyên tắc Trách nhiệm Không, và bạn kết thúc với các Lớp thiếu máu không làm gì cả (ngoại trừ setters và getters), dẫn đến thảm họa Vương quốc Danh từ .

Bạn sẽ không bao giờ có được nó hoàn hảo, nhưng tốt hơn là một lớp học làm quá nhiều hơn là quá ít.

Trong ví dụ của bạn với Encoding, IMO, nó chắc chắn sẽ có thể mã hóa. Thay vào đó, bạn nên làm gì? Chỉ cần có một cái tên "utf" là không có trách nhiệm. Bây giờ, có lẽ tên nên là Encoder. Nhưng, như Konrad đã nói, dữ liệu (mã hóa) và hành vi (thực hiện nó) thuộc về nhau .


7

Một thể hiện của một lớp là một bao đóng. Đó là nó. Nếu bạn nghĩ theo cách đó thì tất cả các phần mềm được thiết kế tốt mà bạn nhìn vào sẽ có vẻ đúng, và tất cả các phần mềm được suy nghĩ kém sẽ không như vậy. Hãy để tôi mở rộng.

Nếu bạn muốn viết một cái gì đó để ghi vào một tệp, hãy nghĩ về tất cả những thứ bạn cần chỉ định (cho HĐH): tên tệp, phương thức truy cập (đọc, viết, nối), chuỗi bạn muốn viết.

Vì vậy, bạn xây dựng một đối tượng Tệp với tên tệp (và phương thức truy cập). Đối tượng File hiện được đóng trên tên tệp (trong trường hợp này có thể nó sẽ lấy nó làm giá trị chỉ đọc / const). Hiện tại Tệp đã sẵn sàng nhận các cuộc gọi đến phương thức "ghi" được định nghĩa trong lớp. Điều này chỉ cần một đối số chuỗi, nhưng trong phần thân của phương thức ghi, việc thực hiện, cũng có quyền truy cập vào tên tệp (hoặc xử lý tệp được tạo từ nó).

Do đó, một thể hiện của lớp File đóng hộp một số dữ liệu vào một số loại blob tổng hợp để việc sử dụng thể hiện đó sau này đơn giản hơn. Khi bạn có một đối tượng Tệp, bạn không cần phải lo lắng về tên tệp là gì, bạn chỉ quan tâm đến chuỗi nào bạn đặt làm đối số trong lệnh gọi. Để nhắc lại - đối tượng Tệp đóng gói tất cả những gì bạn không cần biết về nơi chuỗi bạn muốn viết thực sự sẽ đi đến đâu.

Hầu hết các vấn đề mà bạn muốn giải quyết được xếp lớp theo cách này - những thứ được tạo ra ở phía trước, những thứ được tạo ra khi bắt đầu mỗi lần lặp của một loại vòng lặp quy trình, sau đó những thứ khác nhau được khai báo ở hai nửa của một thứ khác, sau đó những thứ được tạo ra tại bắt đầu một số loại vòng lặp phụ, sau đó mọi thứ được khai báo là một phần của thuật toán trong vòng lặp đó, v.v. Khi bạn thêm ngày càng nhiều hàm gọi vào ngăn xếp, bạn đang thực hiện mã chi tiết hơn, nhưng mỗi lần bạn sẽ có đóng hộp dữ liệu trong các lớp thấp hơn trong ngăn xếp thành một loại trừu tượng đẹp giúp dễ dàng truy cập. Xem những gì bạn đang làm là sử dụng "OO" như một loại khung quản lý đóng cho phương pháp lập trình chức năng.

Giải pháp phím tắt cho một số vấn đề liên quan đến trạng thái có thể thay đổi của các đối tượng, không có chức năng như vậy - bạn đang thao túng các bao đóng từ bên ngoài. Bạn đang thực hiện một số điều trong lớp "toàn cầu" của mình, ít nhất là theo phạm vi trên. Mỗi khi bạn viết một setter - hãy cố gắng tránh những thứ đó. Tôi cho rằng đây là nơi bạn nhận trách nhiệm của lớp mình, hoặc các lớp khác mà nó hoạt động, sai. Nhưng đừng quá lo lắng - đôi khi thực tế là phải khuấy động một chút trạng thái có thể thay đổi để giải quyết nhanh chóng một số loại vấn đề nhất định, không quá tải về nhận thức và thực sự làm được mọi việc.

Vì vậy, tóm lại, để trả lời câu hỏi của bạn - trách nhiệm thực sự của một lớp là gì? Đó là trình bày loại nền tảng, dựa trên một số dữ liệu cơ bản, để đạt được một loại hoạt động chi tiết hơn. Đó là gói gọn một nửa dữ liệu liên quan đến một hoạt động thay đổi ít thường xuyên hơn nửa còn lại của dữ liệu (Hãy suy nghĩ về việc quấy rối ...). Ví dụ tên tệp vs chuỗi để viết.

Thông thường các đóng gói này trông bề ngoài giống như đối tượng trong thế giới thực / một đối tượng trong miền vấn đề nhưng không bị lừa. Khi bạn tạo một lớp Xe hơi, nó không thực sự là một chiếc xe hơi, đó là một tập hợp dữ liệu tạo thành một nền tảng để đạt được một số điều bạn có thể xem xét muốn làm trên xe hơi. Hình thành một đại diện chuỗi (toString) là một trong những điều đó - phun ra tất cả dữ liệu nội bộ đó. Hãy nhớ rằng đôi khi một lớp Xe hơi có thể không phải là lớp phù hợp ngay cả khi miền vấn đề là tất cả về xe hơi. Trái ngược với Vương quốc của danh từ, đó là các hoạt động, động từ mà một lớp nên dựa trên.


4

Tôi cũng hiểu rằng có thể không có câu trả lời chính xác (sẽ là một bài luận hơn là một câu trả lời nghiêm túc) cho vấn đề này, hoặc nó có thể dựa trên ý kiến. Tuy nhiên, tôi vẫn muốn biết liệu cách tiếp cận của tôi có thực sự tuân theo nguyên tắc trách nhiệm đơn lẻ thực sự là gì không.

Trong khi nó, nó có thể không nhất thiết phải làm cho mã tốt. Ngoài thực tế đơn giản là bất kỳ loại quy tắc mù quáng nào dẫn đến mã xấu, bạn đang bắt đầu một tiền đề không hợp lệ: các đối tượng (lập trình) không phải là đối tượng (vật lý).

Đối tượng là một tập hợp các gói dữ liệu và hoạt động gắn kết (và đôi khi chỉ là một trong hai). Trong khi những thứ này thường mô hình hóa các đối tượng trong thế giới thực, sự khác biệt giữa mô hình máy tính của một thứ gì đó và chính nó đòi hỏi sự khác biệt.

Bằng cách lấy ranh giới cứng giữa một "danh từ" đại diện cho một vật và những thứ khác tiêu thụ chúng, bạn sẽ đi ngược lại lợi ích chính của lập trình hướng đối tượng (có chức năng và trạng thái với nhau để chức năng có thể bảo vệ bất biến của trạng thái) . Tồi tệ hơn, bạn đang cố gắng đại diện cho thế giới vật lý trong mã, như lịch sử đã chỉ ra, sẽ không hoạt động tốt.


4

Tôi đã bắt gặp mã C # này (yay, ngôn ngữ đối tượng thứ tư được sử dụng trong bài đăng này) vào một ngày khác:

byte[] bytes = Encoding.Default.GetBytes(myString);
myString = Encoding.UTF8.GetString(bytes);

Và tôi phải nói rằng nó không có ý nghĩa gì với tôi rằng một Encodinghoặc một Charsetlớp thực sự mã hóa các chuỗi. Nó chỉ nên là một đại diện cho những gì một mã hóa thực sự là.

Về lý thuyết, vâng, nhưng tôi nghĩ rằng C # tạo ra một sự thỏa hiệp hợp lý vì mục đích đơn giản (trái ngược với cách tiếp cận chặt chẽ hơn của Java).

Nếu Encodingchỉ được đại diện (hoặc xác định) một số mã hóa nhất định - giả sử, UTF-8 - thì rõ ràng bạn cũng cần một Encoderlớp, để bạn có thể thực hiện GetBytestrên nó - nhưng sau đó bạn cần quản lý mối quan hệ giữa Encoders và Encodings, vì vậy chúng tôi kết thúc với ol 'tốt của chúng tôi

EncodersFactory.getDefaultFactory().createEncoder(new UTF8Encoding()).getBytes(myString)

thật tuyệt vời trong bài viết mà bạn liên kết đến (đọc rất hay, nhân tiện).

Nếu tôi hiểu bạn rõ, bạn đang hỏi đường dây ở đâu.

Vâng, ở phía đối diện của quang phổ là cách tiếp cận thủ tục, không khó để thực hiện bằng các ngôn ngữ OOP với việc sử dụng các lớp tĩnh:

HelpfulStuff.GetUTF8Bytes(myString)

Vấn đề lớn ở đây là khi tác giả của những HelpfulStuffchiếc lá và tôi ngồi trước màn hình, tôi không có cách nào để biết nơi nào GetUTF8Bytesđược thực hiện.

Tôi tìm kiếm nó trong HelpfulStuff, Utils, StringHelper?

Chúa giúp tôi nếu phương pháp thực sự được thực hiện độc lập ở cả ba trong số đó ... điều đó xảy ra rất nhiều, tất cả chỉ là anh chàng trước tôi cũng không biết tìm chức năng đó ở đâu, và tin rằng không có bất kỳ Tuy nhiên, vì vậy họ đã chộp lấy một số khác và bây giờ chúng tôi có chúng rất nhiều.

Đối với tôi, thiết kế phù hợp không phải là để đáp ứng các tiêu chí trừu tượng, mà là sự dễ dàng của tôi để tiếp tục sau khi tôi ngồi trước mã của bạn. Bây giờ làm thế nào

EncodersFactory.getDefaultFactory().createEncoder(new UTF8Encoding()).getBytes(myString)

tỷ lệ trong khía cạnh này? Nghèo cũng vậy. Đó không phải là một câu trả lời tôi muốn nghe khi tôi hét lên với đồng nghiệp của mình "làm thế nào để tôi mã hóa một chuỗi thành một chuỗi byte?" :)

Vì vậy, tôi sẽ đi với cách tiếp cận vừa phải và tự do về trách nhiệm duy nhất. Tôi muốn có Encodingcả lớp xác định một loại mã hóa và thực hiện mã hóa thực tế: dữ liệu không tách rời khỏi hành vi.

Tôi nghĩ rằng nó vượt qua như một mô hình mặt tiền, giúp bôi trơn các bánh răng. Liệu mô hình hợp pháp này có vi phạm RẮN? Một câu hỏi liên quan: Mẫu mặt tiền có vi phạm SRP không?

Lưu ý rằng đây có thể là cách nhận byte được mã hóa được triển khai bên trong (trong các thư viện .NET). Các lớp chỉ không hiển thị công khai và chỉ API "mặt tiền" này được hiển thị bên ngoài. Không khó để xác minh nếu điều đó là sự thật, tôi chỉ hơi lười biếng để làm điều đó ngay bây giờ :) Có lẽ không, nhưng điều đó không làm mất hiệu lực quan điểm của tôi.

Bạn có thể chọn đơn giản hóa việc triển khai hiển thị công khai vì mục đích thân thiện trong khi vẫn duy trì việc triển khai nghiêm ngặt hơn, nghiêm ngặt hơn trong nội bộ.


1

Trong Java, sự trừu tượng được sử dụng để biểu diễn các tệp là một luồng, một số luồng là đơn hướng (chỉ đọc hoặc viết) một số khác là hai chiều. Đối với Ruby, lớp File là sự trừu tượng hóa đại diện cho các tệp OS. Vì vậy, câu hỏi trở thành trách nhiệm duy nhất của nó là gì? Trong Java, trách nhiệm của FileWriter là cung cấp một luồng byte đơn hướng được kết nối với tệp OS. Trong Ruby, trách nhiệm của File là cung cấp quyền truy cập hai chiều vào tệp hệ thống. Cả hai đều hoàn thành SRP mà họ chỉ có trách nhiệm khác nhau.

Bây giờ không phải là trách nhiệm của một chiếc Xe hay Chó để chuyển đổi thành một chuỗi, phải không?

Chắc chắn tại sao không? Tôi hy vọng lớp Xe sẽ thể hiện đầy đủ sự trừu tượng của Xe. Nếu nó có ý nghĩa cho việc trừu tượng xe hơi được sử dụng khi cần một chuỗi thì tôi hoàn toàn mong đợi lớp Xe sẽ hỗ trợ chuyển đổi thành chuỗi.

Để trả lời trực tiếp câu hỏi lớn hơn, trách nhiệm của một lớp là hoạt động như một sự trừu tượng. Công việc đó có thể phức tạp, nhưng đó là một điểm quan trọng, một sự trừu tượng sẽ che giấu những điều phức tạp đằng sau một giao diện dễ sử dụng hơn, ít phức tạp hơn. SRP là một hướng dẫn thiết kế để bạn tập trung vào câu hỏi "sự trừu tượng này thể hiện điều gì?". Nếu nhiều hành vi khác nhau là cần thiết để trừu tượng hóa hoàn toàn khái niệm thì cũng vậy.


0

Lớp có thể có nhiều trách nhiệm. Ít nhất là trong OOP trung tâm dữ liệu cổ điển.

Nếu bạn đang áp dụng nguyên tắc trách nhiệm đơn lẻ ở mức độ đầy đủ, bạn sẽ có được thiết kế tập trung vào trách nhiệm trong đó về cơ bản mọi phương thức không trợ giúp đều thuộc về lớp riêng của nó.

Tôi nghĩ không có gì sai khi trích xuất một phần phức tạp từ một lớp cơ sở như trong trường hợp của File và FileWriter. Ưu điểm là bạn có được sự phân tách mã rõ ràng chịu trách nhiệm cho một hoạt động nhất định và bạn cũng có thể ghi đè (lớp con) chỉ đoạn mã đó thay vì ghi đè toàn bộ lớp cơ sở (ví dụ: Tệp). Tuy nhiên, áp dụng một cách có hệ thống dường như là một quá mức đối với tôi. Bạn chỉ cần nhận được nhiều lớp hơn để xử lý và cho mọi hoạt động bạn cần để gọi lớp tương ứng của nó. Đó là sự linh hoạt đi kèm với một số chi phí.

Những lớp học chiết xuất nên có những cái tên rất mô tả dựa trên các hoạt động chúng chứa, ví dụ như <X>Writer, <X>Reader, <X>Builder. Những cái tên như <X>Manager, <X>Handlerhoàn toàn không mô tả hoạt động và nếu chúng được gọi như vậy bởi vì chúng chứa nhiều hơn một thao tác, thì không rõ bạn đã đạt được gì. Bạn đã tách chức năng khỏi dữ liệu của nó, bạn vẫn vi phạm nguyên tắc trách nhiệm duy nhất ngay cả trong lớp được trích xuất đó và nếu bạn đang tìm kiếm một phương thức nhất định, bạn có thể không biết tìm nó ở đâu (nếu trong <X>hoặc <X>Manager).

Bây giờ, trường hợp của bạn với UserDataHandler thì khác vì không có lớp cơ sở (lớp chứa dữ liệu thực tế). Dữ liệu cho lớp này được lưu trữ trong một tài nguyên bên ngoài (cơ sở dữ liệu) và bạn đang sử dụng một api để truy cập chúng và thao tác với chúng. Lớp Người dùng của bạn đại diện cho người dùng thời gian chạy, thực sự khác với người dùng liên tục và việc phân tách trách nhiệm (mối quan tâm) có thể có ích ở đây đặc biệt là nếu bạn có nhiều logic liên quan đến người dùng thời gian chạy.

Bạn có thể đặt tên UserDataHandler như vậy vì về cơ bản chỉ có chức năng trong lớp đó và không có dữ liệu thực. Tuy nhiên, bạn cũng có thể trừu tượng với thực tế đó và coi dữ liệu trong cơ sở dữ liệu thuộc về lớp. Với sự trừu tượng này, bạn có thể đặt tên lớp dựa trên những gì nó là và không dựa trên những gì nó làm. Bạn có thể đặt cho nó một cái tên như UserRep repository, khá đơn giản và cũng gợi ý sử dụng mẫu kho lưu trữ .

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.