Thiết kế lớp hướng đối tượng


12

Tôi đã tự hỏi về thiết kế lớp hướng đối tượng tốt. Cụ thể, tôi có một thời gian khó khăn để quyết định giữa các tùy chọn này:

  1. phương thức tĩnh vs dụ
  2. phương thức không có tham số hoặc giá trị trả về so với phương thức có tham số và giá trị trả về
  3. chồng chéo so với chức năng phương thức riêng biệt
  4. phương pháp riêng tưcông cộng

Ví dụ 1:

Việc triển khai này sử dụng các phương thức cá thể, không có giá trị trả về hoặc tham số, không có chức năng chồng lấp và tất cả các phương thức công khai

XmlReader reader = new XmlReader(url);
reader.openUrl();
reader.readXml();
Document result = reader.getDocument();

Ví dụ 2:

Việc triển khai này sử dụng các phương thức tĩnh, với các giá trị và tham số trả về, với chức năng chồng chéo và các phương thức riêng tư

Document result = XmlReader.readXml(url); 

Trong ví dụ một, tất cả các phương thức là ví dụ công khai, giúp chúng dễ dàng kiểm tra đơn vị. Mặc dù tất cả các phương thức là khác biệt, readXml () phụ thuộc vào openUrl () trong đó openUrl () phải được gọi trước. Tất cả dữ liệu được khai báo trong các trường đối tượng, do đó không có giá trị trả về hoặc tham số trong bất kỳ phương thức nào, ngoại trừ trong hàm tạo và hàm truy cập.

Trong ví dụ hai, chỉ có một phương thức là công khai, phần còn lại là tĩnh riêng, điều này khiến chúng khó kiểm tra đơn vị. Các phương thức được chồng lấp trong đó readXml () gọi openUrl (). Không có trường nào, tất cả dữ liệu được truyền dưới dạng tham số trong các phương thức và kết quả được trả về ngay lập tức.

Những nguyên tắc nào tôi nên tuân theo để làm lập trình hướng đối tượng thích hợp?


2
Những điều tĩnh là xấu khi bạn làm đa luồng. Ngày khác, tôi đã có một XMLWriter tĩnh, như XMLWriter.write (data, fileurl). Tuy nhiên, vì nó có FileStream tĩnh riêng, sử dụng lớp này từ nhiều luồng cùng một lúc, khiến luồng thứ hai ghi đè lên luồng đầu tiên FileStream, gây ra lỗi rất khó tìm. Các lớp tĩnh với các thành viên tĩnh + đa luồng là một công thức cho thảm họa.
Mỗi Alexandersson

1
@Paxinum. Vấn đề bạn mô tả là vấn đề trạng thái, không phải vấn đề "tĩnh". Nếu bạn đã sử dụng một singleton với các thành viên không tĩnh, bạn sẽ gặp vấn đề tương tự với đa luồng.
mike30

1
@Per Alexandersson Phương thức tĩnh không tệ liên quan đến tương tranh. Trạng thái tĩnh là xấu. Đây là lý do tại sao lập trình chức năng, trong đó tất cả các phương thức là tĩnh, hoạt động rất tốt trong các tình huống đồng thời.
Yuli Bonner

Câu trả lời:


10

Ví dụ 2 khá tệ khi kiểm tra ... và tôi không có nghĩa là bạn không thể kiểm tra nội bộ. Bạn cũng không thể thay thế XmlReaderđối tượng của mình bằng một đối tượng giả vì bạn hoàn toàn không có đối tượng.

Ví dụ 1 là khó sử dụng. Thế còn

XmlReader reader = new XmlReader(url);
Document result = reader.getDocument();

mà không khó sử dụng hơn phương thức tĩnh của bạn.

Những việc như mở URL, đọc XML, chuyển đổi byte thành chuỗi, phân tích cú pháp, đóng ổ cắm và bất cứ điều gì, đều không thú vị. Tạo một đối tượng và sử dụng nó là quan trọng.

Vì vậy, IMHO Thiết kế OO thích hợp là chỉ công khai hai điều (trừ khi bạn thực sự cần các bước trung gian vì một số lý do). Tĩnh là ác.


-1. YOu thực sự CÓ THỂ thay thế XmlReader bằng một đối tượng giả. Không phải là khuôn khổ moick mã nguồn mở đã chết, nhưng với một loại công nghiệp tốt, bạn có thể;) Chi phí vài trăm USD cho mỗi nhà phát triển nhưng làm việc rất tuyệt vời để kiểm tra chức năng niêm phong trong API mà bạn xuất bản.
TomTom

2
+1 vì không nói dối về doanh số của TomTom. Khi tôi muốn có một lớp lót, tôi muốn viết một cái gì đó như Document result = new XmlReader(url).getDocument();Tại sao? Để tôi có thể nâng cấp nó lên Document result = whateverReader.getDocument();whateverReadertrao cho tôi thứ khác.
candied_orange

6

Đây là điều - không có ai trả lời đúng, và không có định nghĩa tuyệt đối về "thiết kế hướng đối tượng phù hợp" (một số người sẽ cung cấp cho bạn một câu, nhưng họ ngây thơ ... cho họ thời gian).

Tất cả đều thuộc về MỤC TIÊU của bạn.

Bạn là một nghệ sĩ, và giấy trống. Bạn có thể vẽ một bức chân dung bên cạnh màu đen và trắng bút chì tinh xảo, hoặc một bức tranh trừu tượng với những luồng gió khổng lồ. Hoặc bất cứ điều gì ở giữa.

Vì vậy, những gì CẢM XÚC cho vấn đề bạn đang giải quyết? Khiếu nại của những người cần sử dụng các lớp học của bạn để làm việc với xml là gì? Công việc của họ là gì? Loại mã nào họ đang cố gắng viết bao quanh các cuộc gọi đến thư viện của bạn và làm thế nào bạn có thể giúp dòng đó tốt hơn cho họ?

Họ có muốn cô đọng hơn không? Họ có muốn nó rất thông minh trong việc tìm ra các giá trị mặc định cho các tham số, vì vậy họ không phải chỉ định nhiều (hoặc bất cứ điều gì) và nó đoán chính xác? Bạn có thể tự động hóa các tác vụ thiết lập và dọn dẹp mà thư viện của bạn yêu cầu để họ không thể quên các bước đó không? Bạn có thể làm gì khác cho họ?

Chết tiệt, những gì bạn có thể cần làm là mã hóa 4 hoặc 5 cách khác nhau, sau đó đội mũ tiêu dùng của bạn và viết mã sử dụng tất cả 5, và xem cảm giác nào tốt hơn. Nếu bạn không thể làm điều đó cho toàn bộ thư viện của mình, thì hãy làm điều đó cho một tập hợp con. Và bạn cũng cần thêm một số lựa chọn thay thế vào danh sách của mình - về giao diện thông thạo hoặc cách tiếp cận chức năng hơn hoặc tham số được đặt tên hoặc một cái gì đó dựa trên DynamicObject để bạn có thể tạo ra các "phương pháp giả" có ý nghĩa giúp chúng ngoài?

Tại sao jQuery là vua ngay bây giờ? Bởi vì Resig và nhóm đã tuân theo quy trình này, cho đến khi họ bắt gặp một nguyên tắc cú pháp làm giảm đáng kể số lượng mã JS cần thiết để làm việc với dom và các sự kiện. Nguyên tắc cú pháp đó không rõ ràng với họ hoặc bất cứ ai khác khi họ bắt đầu. Họ thành lập nó.

Là một lập trình viên, đó là những gì bạn gọi cao nhất. Bạn mò mẫm trong bóng tối cố gắng cho đến khi bạn tìm thấy nó. Khi bạn làm, bạn sẽ biết. Và bạn sẽ được cho phép người dùng của bạn một khổng lồ bước nhảy vọt năng suất. Và đó là những gì thiết kế (trong lĩnh vực phần mềm) là tất cả về.


1
Điều này ngụ ý rằng không có sự khác biệt giữa thực tiễn tốt nhất và thực tiễn khác ngoài cách nó "cảm thấy". Đây là cách bạn kết thúc với những quả bóng bùn không thể nhầm lẫn - bởi vì với nhiều nhà phát triển, vươn ra khỏi ranh giới Lớp học, v.v., "cảm thấy" thật tuyệt vời.
Amy Blankenship

@Amy Blankenship, tôi sẽ nói một cách dứt khoát rằng không có "cách tốt nhất" nào để đưa ra những lựa chọn mà OP đang hỏi. Nó phụ thuộc vào một triệu thứ, và có một triệu độ tự do. Tuy nhiên, tôi nghĩ có một nơi dành cho "Thực tiễn tốt nhất" và đó là trong môi trường nhóm , nơi đã có những lựa chọn nhất định và chúng tôi cần các thành viên còn lại trong nhóm tuân thủ những lựa chọn trước đó. Nói cách khác, trong một bối cảnh cụ thể , có thể có những lý do để gắn nhãn một số điều "thực tiễn tốt nhất". Nhưng OP đã không đưa ra bất kỳ bối cảnh. Anh ta đang xây dựng một cái gì đó và ...
Charlie Hoa

... anh ấy phải đối mặt với tất cả những lựa chọn có thể. Không có ai "trả lời đúng" cho những lựa chọn đó. Nó được thúc đẩy bởi các mục tiêu và các điểm đau của hệ thống. Tôi đảm bảo với bạn rằng các lập trình viên Haskell không nghĩ rằng tất cả các phương thức nên là phương thức cá thể. Và các lập trình viên nhân Linux không nghĩ rằng làm cho mọi thứ có thể truy cập được vào TDD là rất quan trọng. Và các lập trình viên trò chơi C ++ thường thà bó dữ liệu của họ vào một cấu trúc dữ liệu chặt chẽ trong bộ nhớ hơn là gói gọn mọi thứ vào các đối tượng. Mỗi "Thực hành tốt nhất" chỉ là "thực tiễn tốt nhất" trong một bối cảnh nhất định và là một mô hình chống đối trong một số bối cảnh khác.
Charlie Hoa

@AmyBlankenship Một điều nữa: Tôi không đồng ý rằng việc vươn ra khỏi ranh giới Lớp học "cảm thấy thật tuyệt vời." Nó dẫn đến những quả bóng bùn không thể nhầm lẫn, mà cảm thấy khủng khiếp . Tôi nghĩ rằng bạn đang cố gắng giải quyết vấn đề rằng một số công nhân đang cẩu thả / không có động lực / rất thiếu kinh nghiệm. Trong trường hợp đó, một người cẩn thận, có động lực và có kinh nghiệm nên đưa ra những lựa chọn chính, và gọi họ là "Thực tiễn tốt nhất". Nhưng, người chọn "Thực tiễn tốt nhất" đó vẫn đưa ra lựa chọn dựa trên những gì "cảm thấy đúng" và không có câu trả lời đúng nào. Bạn chỉ đang kiểm soát người đưa ra lựa chọn.
Charlie Hoa

Tôi đã làm việc với một số lập trình viên, những người nghĩ rằng họ là cấp cao và được quản lý nghĩ theo cách đó bởi những người tin chắc rằng statics và Singletons hoàn toàn là cách đúng đắn để xử lý các vấn đề giao tiếp. Phần tĩnh của câu hỏi này thậm chí sẽ không được hỏi nếu tiếp cận các ranh giới Lớp như thế "cảm thấy" sai đối với các nhà phát triển, cũng như câu trả lời ủng hộ thay thế tĩnh sẽ nhận được bất kỳ phiếu bầu nào.
Amy Blankenship

3

Tùy chọn thứ hai tốt hơn vì mọi người sử dụng đơn giản hơn (ngay cả khi chỉ là bạn), đơn giản hơn nhiều.

Đối với thử nghiệm đơn vị, tôi chỉ kiểm tra giao diện không phải là phần bên trong nếu bạn thực sự muốn sau đó chuyển phần bên trong từ riêng sang bảo vệ.


2
Bạn cũng có thể đặt gói phương thức của mình ở chế độ riêng tư (mặc định), nếu các trường hợp thử nghiệm của bạn nằm trong cùng một gói, cho mục đích thử nghiệm đơn vị.

Điểm tốt - đó là tốt hơn.

2

Tập trung vào quan điểm của khách hàng.

IReader reader = new XmlReader.readXml(url);  // or injection, or factory or ...
Document document = reader.read();

Các phương thức tĩnh có xu hướng hạn chế sự phát triển trong tương lai, khách hàng của chúng tôi đang làm việc theo giao diện được cung cấp bởi có thể có nhiều triển khai khác nhau.

Vấn đề chính với thành ngữ mở / đọc của bạn là khách hàng cần biết thứ tự để gọi các phương thức, khi anh ta chỉ muốn thực hiện công việc đơn giản. Rõ ràng ở đây, nhưng trong một Lớp lớn hơn thì không rõ ràng.

Phương pháp nguyên tắc để kiểm tra là read (). Các phương thức nội bộ có thể được hiển thị cho các chương trình thử nghiệm bằng cách đặt chúng ở chế độ công khai hoặc riêng tư và đặt các thử nghiệm trong cùng một gói - các thử nghiệm vẫn có thể được tách biệt khỏi mã được phát hành.


Các phương thức có khả năng hiển thị mặc định vẫn hiển thị, nếu bộ thử nghiệm nằm trong một dự án khác?
siamii

Java không biết về các dự án. Các dự án là một cấu trúc IDE. Trình biên dịch và JVM xem xét các gói mà các lớp được kiểm tra và kiểm thử nằm trong cùng một gói, khả năng hiển thị mặc định được cho phép. Trong Eclipse tôi sử dụng một dự án duy nhất, với hai thư mục nguồn khác nhau. Tôi vừa thử với hai dự án, nó hoạt động được.

2

phương thức tĩnh vs dụ

Trong thực tế, bạn sẽ thấy các phương thức tĩnh thường bị giới hạn trong các lớp tiện ích và không nên làm lộn xộn các đối tượng miền, trình quản lý, bộ điều khiển hoặc DAO của bạn. Các phương thức tĩnh là hữu ích nhất khi tất cả các tham chiếu cần thiết có thể được truyền một cách hợp lý dưới dạng tham số và cung cấp một số chức năng có thể được sử dụng lại trên nhiều lớp. Nếu bạn thấy mình sử dụng một phương thức tĩnh như một cách giải quyết để có một tham chiếu đến một đối tượng thể hiện, hãy tự hỏi tại sao bạn không chỉ có tham chiếu đó thay thế.

phương thức không có tham số hoặc giá trị trả về so với phương thức có tham số và giá trị trả về

Nếu bạn không cần tham số trên phương thức, đừng thêm chúng. Tương tự với giá trị trả về. Ghi nhớ điều này sẽ đơn giản hóa mã của bạn và đảm bảo bạn không mã hóa cho vô số tình huống không bao giờ xảy ra.

chồng chéo so với chức năng phương thức riêng biệt

Đó là một ý tưởng tốt để cố gắng tránh chức năng chồng chéo. Đôi khi điều đó có thể khó khăn, nhưng khi cần thay đổi logic, việc thay đổi một phương thức được sử dụng lại sẽ dễ dàng hơn nhiều so với thay đổi toàn bộ phương thức có chức năng tương tự

phương pháp riêng tư và công cộng

Thông thường getters, setters và constructor nên được công khai. Mọi thứ khác bạn sẽ muốn cố gắng giữ riêng tư trừ khi có trường hợp một lớp khác cần thực thi nó. Giữ các phương thức mặc định thành riêng tư sẽ giúp duy trì Đóng gói . Tương tự với các trường, làm quen với tư nhân như mặc định


2

1. Tĩnh so với cá thể

Tôi nghĩ rằng có những hướng dẫn rất rõ ràng về thiết kế OO tốt và những gì không. Vấn đề là thế giới blog làm cho khó phân tách cái tốt và cái xấu. Bạn có thể tìm thấy một số loại tài liệu tham khảo hỗ trợ ngay cả thực tiễn tồi tệ nhất bạn có thể nghĩ đến.

Và thực tế tồi tệ nhất tôi có thể nghĩ đến là Nhà nước toàn cầu, bao gồm cả những thống kê mà bạn đề cập và Singleton yêu thích của mọi người. Một số trích đoạn từ bài viết kinh điển của Misko Hevery về chủ đề này .

Để thực sự hiểu các phụ thuộc, các nhà phát triển phải đọc mọi dòng mã. Nó gây ra Hành động ma quái ở khoảng cách xa: khi chạy các bộ thử nghiệm, trạng thái toàn cầu bị đột biến trong một thử nghiệm có thể khiến thử nghiệm tiếp theo hoặc song song bị lỗi bất ngờ. Phá vỡ sự phụ thuộc tĩnh bằng cách sử dụng tiêm phụ thuộc thủ công hoặc Guice.

Hành động ma quái ở khoảng cách là khi chúng ta chạy một thứ mà chúng ta tin là bị cô lập (vì chúng ta không chuyển bất kỳ tài liệu tham khảo nào) nhưng các tương tác bất ngờ và thay đổi trạng thái xảy ra ở các vị trí xa của hệ thống mà chúng ta không nói cho đối tượng biết. Điều này chỉ có thể xảy ra thông qua nhà nước toàn cầu.

Có thể bạn chưa từng nghĩ về nó theo cách này trước đây, nhưng bất cứ khi nào bạn sử dụng trạng thái tĩnh, bạn đang tạo các kênh liên lạc bí mật và không làm cho chúng rõ ràng trong API. Spooky Action at a distance buộc các nhà phát triển phải đọc từng dòng mã để hiểu các tương tác tiềm năng, làm giảm năng suất của nhà phát triển và gây nhầm lẫn cho các thành viên nhóm mới.

Điều này có nghĩa là bạn không nên cung cấp các tham chiếu tĩnh cho bất cứ thứ gì có một số trạng thái được lưu trữ. Nơi duy nhất tôi sử dụng statics là cho các hằng số liệt kê, và tôi có những hiểu lầm về điều đó.

2. Các phương thức có tham số đầu vào và giá trị trả về so với phương thức không có

Điều bạn cần nhận ra là các phương thức không có tham số đầu vào và không có tham số đầu ra được đảm bảo khá nhiều để hoạt động trên một số trạng thái được lưu trữ bên trong (nếu không, chúng đang làm gì?). Có toàn bộ ngôn ngữ được xây dựng trên ý tưởng tránh trạng thái lưu trữ.

Bất cứ khi nào bạn đã lưu trữ trạng thái, bạn có khả năng cho các tác dụng phụ, vì vậy hãy chắc chắn rằng bạn luôn sử dụng nó một cách cẩn thận. Điều này ngụ ý rằng bạn nên thích các chức năng với đầu vào và / hoặc đầu ra được xác định.

Và trên thực tế, các hàm đã xác định đầu vào và đầu ra dễ kiểm tra hơn nhiều - bạn không phải chạy một hàm ở đây và nhìn qua đó để xem điều gì đã xảy ra và bạn không phải đặt thuộc tính ở đâu đó khác trước khi bạn chạy chức năng được thử nghiệm.

Bạn cũng có thể sử dụng loại chức năng này một cách an toàn . Tuy nhiên, tôi sẽ không làm thế, vì nếu sau này tôi muốn sử dụng một triển khai khác của chức năng đó ở đâu đó, thay vì cung cấp một thể hiện khác với cách triển khai mới, tôi không có cách nào để thay thế chức năng.

3. Chồng chéo so với khác biệt

Tôi không hiểu câu hỏi. Lợi thế của 2 phương pháp chồng chéo là gì?

4. Riêng tư so với công chúng

Đừng phơi bày bất cứ điều gì bạn không cần phải phơi bày. Tuy nhiên, tôi cũng không phải là một fan hâm mộ lớn của tư nhân. Tôi không phải là nhà phát triển C #, mà là nhà phát triển ActionScript. Tôi đã dành rất nhiều thời gian cho mã Flex Framework của Adobe, được viết vào khoảng năm 2007. Và họ đã đưa ra một số lựa chọn thực sự tồi tệ về việc làm cho riêng tư, điều này khiến cho nó trở thành một cơn ác mộng khi cố gắng mở rộng Lớp học của họ.

Vì vậy, trừ khi bạn nghĩ bạn là một kiến ​​trúc sư giỏi hơn các nhà phát triển Adobe vào khoảng năm 2007 (từ câu hỏi của bạn, tôi sẽ nói rằng bạn có một vài năm nữa trước khi bạn có cơ hội đưa ra yêu cầu đó), bạn có thể muốn mặc định được bảo vệ .


Có một số vấn đề với các ví dụ mã của bạn, điều đó có nghĩa là chúng không được kiến ​​trúc tốt, vì vậy không thể chọn A hoặc B.

Đối với một điều, có lẽ bạn nên tách việc tạo đối tượng của bạn khỏi việc sử dụng nó . Vì vậy, bạn thường không có new XMLReader()quyền của bạn bên cạnh nơi nó được sử dụng.

Ngoài ra, như @djna nói, bạn nên gói gọn các phương thức được sử dụng trong Trình đọc XML của bạn, để API (ví dụ ví dụ) của bạn có thể được đơn giản hóa thành:

_document Document = reader.read(info);

Tôi không biết C # hoạt động như thế nào, nhưng vì tôi đã làm việc với một số công nghệ web, tôi nghi ngờ rằng bạn sẽ không thể trả lại tài liệu XML ngay lập tức (ngoại trừ có thể là một lời hứa hoặc loại tương lai đối tượng), nhưng tôi không thể cho bạn lời khuyên về cách xử lý tải không đồng bộ trong C #.

Lưu ý rằng với cách tiếp cận này, bạn có thể tạo một số triển khai có thể lấy tham số cho chúng biết vị trí / cái gì cần đọc và trả về một đối tượng XML và trao đổi chúng dựa trên nhu cầu dự án của bạn. Ví dụ: bạn có thể đang đọc trực tiếp từ cơ sở dữ liệu, từ cửa hàng địa phương hoặc, như trong ví dụ ban đầu của bạn, từ một URL. Bạn không thể làm điều đó nếu bạn sử dụng một phương thức tĩnh.


1

Tôi sẽ không trả lời câu hỏi của bạn, nhưng tôi nghĩ rằng một vấn đề được tạo ra bởi các điều khoản bạn đã sử dụng. Ví dụ

XmlReader.read => twice "read"

Tôi nghĩ rằng bạn cần một XML vì vậy tôi sẽ tạo một XML đối tượng có thể được tạo từ một loại văn bản (tôi không biết C # ... trong Java, nó được gọi là String). Ví dụ

class XML {
    XML(String text) { [...] }
}

Bạn có thể kiểm tra nó và nó rõ ràng. Sau đó, nếu bạn cần một nhà máy, bạn có thể thêm một phương thức nhà máy (và nó có thể tĩnh như ví dụ thứ 2 của bạn). Ví dụ

class XML {
    XML(String text) { [...] }

    static XML fromUrl(url) { [...] }

}

0

Bạn có thể làm theo một số quy tắc đơn giản. Nó giúp nếu bạn hiểu lý do cho các quy tắc.

phương thức tĩnh vs dụ

Đối với phương pháp bạn không phải đưa ra quyết định này một cách có ý thức. Nếu có vẻ như phương thức của bạn không sử dụng bất kỳ thành viên trường nào (bộ phân tích yêu thích của bạn sẽ cho bạn biết như vậy), bạn nên thêm từ khóa tĩnh.

phương thức không có tham số hoặc giá trị trả về so với phương thức có tham số và giá trị trả về

Tùy chọn thứ hai là tốt hơn vì phạm vi. Bạn nên luôn luôn giữ phạm vi của bạn chặt chẽ. Tiếp cận với những thứ bạn cần là xấu, bạn nên có đầu vào, làm việc tại địa phương và trả về kết quả. Tùy chọn đầu tiên của bạn là xấu vì cùng lý do các biến toàn cục thường xấu: chúng chỉ có ý nghĩa đối với một phần mã của bạn nhưng chúng có thể nhìn thấy (và do đó nhiễu) ở mọi nơi khác và có thể bị can thiệp từ bất cứ đâu. Điều này làm cho nó khó có được một bức tranh hoàn chỉnh về logic của bạn.

chồng chéo so với chức năng phương thức riêng biệt

Tôi không nghĩ rằng đây là một vấn đề. Các phương thức gọi các phương thức khác là tốt nếu điều này kết hợp các tác vụ thành các khối chức năng riêng biệt nhỏ hơn.

phương pháp riêng tư và công cộng

Làm cho mọi thứ riêng tư trừ khi bạn cần nó được công khai. Người dùng của lớp bạn có thể làm mà không có tiếng ồn, anh ta chỉ muốn xem những gì quan trọng với anh ta.

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.