Có lớp học 'Util' là một nguyên nhân cho mối quan tâm? [đóng cửa]


14

Đôi khi tôi tạo các lớp 'Util', chủ yếu phục vụ các phương thức và giá trị dường như không thực sự thuộc về nơi khác. Nhưng mỗi khi tôi tạo một trong những lớp này, tôi nghĩ "uh-oh, tôi sẽ hối hận về điều này sau ...", bởi vì tôi đã đọc ở đâu đó rằng điều đó thật tệ.

Nhưng mặt khác, dường như có hai trường hợp hấp dẫn (ít nhất là đối với tôi) đối với họ:

  1. bí mật thực hiện được sử dụng trong nhiều lớp trong một gói
  2. cung cấp chức năng hữu ích để tăng thêm một lớp, mà không làm lộn xộn giao diện của nó

Tôi đang trên đường hủy diệt? Những gì bạn nói !! Có nên tái cấu trúc?


11
Ngừng sử dụng Utiltên của lớp bạn. Vấn đề được giải quyết.
yannis

3
@MattFenwick Yannis có một điểm tốt. Đặt tên một lớp SomethingUtillà một chút lười biếng và chỉ che khuất mục đích thực sự của lớp - giống với các lớp được đặt tên SomethingManagerhoặc SomethingService. Nếu lớp đó có một trách nhiệm duy nhất, nó sẽ dễ dàng đặt cho nó một cái tên có ý nghĩa. Nếu không, đó là vấn đề thực sự cần giải quyết ...
MattDavey

1
@MDavey điểm tốt, nó có thể là một lựa chọn từ xấu để sử dụng Util, mặc dù rõ ràng tôi không mong đợi rằng nó sẽ được sửa chữa và phần còn lại của câu hỏi bị bỏ qua ...

1
Nếu bạn tạo nhiều lớp * Sử dụng các lớp đối tượng mà chúng thao tác, thì tôi nghĩ nó ổn. Tôi không thấy bất kỳ vấn đề nào khi có StringUtil, ListUtil, v.v. Trừ khi bạn ở C # thì bạn nên sử dụng các phương thức mở rộng.
marco-fiset

4
Tôi thường có các bộ chức năng được sử dụng cho các mục đích liên quan. Họ không thực sự ánh xạ tốt đến một lớp học. Tôi không cần một lớp ImageResizer, tôi chỉ cần một chức năng ResizeImage. Vì vậy, tôi sẽ đặt các hàm liên quan vào một lớp tĩnh như ImageTools. Đối với các hàm chưa có trong bất kỳ loại nhóm nào, tôi có một lớp tĩnh Util để giữ chúng, nhưng một khi tôi có một số ít có liên quan với nhau để phù hợp với một lớp, tôi sẽ di chuyển chúng. Tôi thấy không có cách OO nào tốt hơn để xử lý việc này.
Philip

Câu trả lời:


26

Thiết kế OO hiện đại chấp nhận rằng không phải mọi thứ đều là một đối tượng. Một số điều là hành vi, hoặc công thức, và một số trong đó không có trạng thái. Thật tốt khi mô hình hóa những thứ này như các hàm thuần túy để có được lợi ích của thiết kế đó.

Java và C # (và những người khác) yêu cầu bạn tạo một lớp tiện dụng và nhảy qua cái vòng đó để làm điều đó. Khó chịu, nhưng không phải là kết thúc của thế giới; và không thực sự rắc rối từ quan điểm thiết kế.


Vâng, đó là những gì tôi đã nghĩ. Cảm ơn vì sự trả lời!

Đồng ý, mặc dù cần phải đề cập rằng .NET hiện cung cấp các phương thức mở rộng và các lớp một phần thay thế cho phương pháp này. Nếu bạn đưa mặt hàng này đến mức cực đoan, bạn sẽ kết thúc với Ruby mixins - một ngôn ngữ ít có nhu cầu cho các lớp tiện ích.
neontapir

2
@neontapir - Ngoại trừ việc triển khai các phương thức mở rộng về cơ bản buộc bạn phải tạo một lớp tiện dụng với các phương thức mở rộng ...
Telastyn

Thật. Có hai nơi mà các lớp Util cản trở, một là trong chính lớp đó và một là lớp gọi. Bằng cách làm cho các phương thức trông giống như chúng tồn tại trên đối tượng nâng cao, các phương thức mở rộng giải quyết vấn đề về cuộc gọi. Tôi đồng ý, vẫn còn vấn đề tồn tại của lớp Util.
neontapir

16

Không bao giờ nói không bao giờ"

Tôi không nghĩ nó thực sự xấu, nó chỉ tệ nếu bạn làm điều đó tồi tệ và lạm dụng nó.

Tất cả chúng ta đều cần các công cụ và tiện ích

Để bắt đầu, tất cả chúng ta đều sử dụng một số thư viện đôi khi được coi là gần như phổ biến và phải có. Ví dụ, trong thế giới Java, Google ổi hoặc một số Apache Commons ( Apache Commons Lang , Apache Commons Bộ sưu tập , vv ...).

Vì vậy, rõ ràng là cần một thứ này.

Tránh các lỗi khó, trùng lặp và giới thiệu lỗi

Nếu bạn suy nghĩ về những được khá nhiều chỉ là một bó rất lớn của những Utillớp học mà bạn mô tả, ngoại trừ một người nào đó đã trải qua độ dài lớn để có được chúng (tương đối) đúng, và họ đã được thời gian - thử nghiệm và nặng nề mắt cuộn lại bởi những người khác.

Vì vậy, tôi muốn nói rằng quy tắc đầu tiên khi cảm thấy ngứa khi viết một Utillớp là kiểm tra xem Utillớp đó thực sự không tồn tại.

Đối số duy nhất tôi từng thấy đó là khi bạn muốn giới hạn sự phụ thuộc của mình vì:

  • bạn muốn giới hạn dung lượng bộ nhớ của các phụ thuộc của bạn,
  • hoặc bạn muốn kiểm soát chặt chẽ những gì các nhà phát triển được phép sử dụng (xảy ra trong các nhóm lớn ám ảnh hoặc khi một khung cụ thể được biết là có lớp siêu siêu kỳ quặc để tránh tuyệt đối ở đâu đó).

Nhưng cả hai điều này có thể được giải quyết bằng cách đóng gói lại lib bằng ProGuard hoặc tương đương hoặc tự tách nó ra (đối với người dùng Maven , maven-bóng-plugin cung cấp một số mẫu lọc để tích hợp phần này vào bản dựng của bạn).

Vì vậy, nếu nó ở dạng lib và phù hợp với trường hợp sử dụng của bạn và không có điểm chuẩn nào cho bạn biết khác, hãy sử dụng nó. Nếu nó thay đổi một chút so với những gì bạn, hãy mở rộng nó (nếu có thể) hoặc mở rộng nó, hoặc trong phương án cuối cùng viết lại nó.

Quy ước đặt tên

Tuy nhiên, cho đến nay trong câu trả lời này, tôi đã gọi họ Utilgiống như bạn. Đừng gọi tên họ như vậy.

Đặt cho họ những cái tên ý nghĩa. Lấy Google Guava làm ví dụ (rất, rất) tốt về những việc cần làm và chỉ cần tưởng tượng rằng com.google.guavakhông gian tên thực sự là utilgốc của bạn .

Gọi gói của bạn util, lúc tồi tệ nhất, nhưng không phải là các lớp. Nếu giao dịch với Stringcác đối tượng và thao tác với các cấu trúc chuỗi, hãy gọi nó Strings, không StringUtils(xin lỗi, Apache Commons Lang - Tôi vẫn thích và sử dụng bạn!). Nếu nó làm một cái gì đó cụ thể, chọn một tên lớp cụ thể (như Splitterhoặc Joiner).

Kiểm tra đơn vị

Nếu bạn phải dùng đến việc viết các tiện ích này, hãy đảm bảo kiểm tra đơn vị chúng. Điểm hay của các tiện ích là chúng thường là các thành phần khá khép kín, có các đầu vào cụ thể và trả về các đầu ra cụ thể. Đó là khái niệm. Vì vậy, không có lý do gì để không kiểm tra đơn vị chúng.

Ngoài ra, kiểm tra đơn vị sẽ cho phép bạn xác định và ghi lại hợp đồng API của họ. Nếu các bài kiểm tra bị hỏng, hoặc bạn đã thay đổi điều gì đó sai, hoặc điều đó có nghĩa là bạn đang cố gắng thay đổi hợp đồng API của mình (hoặc các bài kiểm tra ban đầu của bạn là tào lao - hãy học hỏi từ đó và đừng làm lại) .

Thiết kế API

Quyết định thiết kế mà bạn sẽ đưa ra cho các API này sẽ theo bạn trong một thời gian dài, có thể. Vì vậy, trong khi không dành hàng giờ để viết một văn bản Splitter, hãy cẩn thận về cách bạn tiếp cận vấn đề.

Hãy tự hỏi mình một vài câu hỏi:

  • Phương thức tiện ích của bạn có tự bảo đảm một lớp hay là một phương thức tĩnh đủ tốt, nếu nó có ý nghĩa đối với nó là một phần của một nhóm các phương thức hữu ích tương tự?
  • Bạn có cần các phương thức xuất xưởng để tạo các đối tượng và làm cho API của bạn dễ đọc hơn không?
  • Nói về khả năng đọc, bạn có cần API thông thạo , trình tạo , v.v ... không?

Bạn muốn những dụng cụ này bao gồm một số lượng lớn các trường hợp sử dụng, phải mạnh mẽ, ổn định, được ghi chép đầy đủ, theo nguyên tắc ít gây bất ngờ nhất và phải khép kín. Lý tưởng nhất, mỗi gói phụ của dụng cụ của bạn, hoặc ít nhất là toàn bộ gói sử dụng của bạn, sẽ có thể được xuất thành một gói để dễ dàng sử dụng lại.

Như thường lệ, học hỏi từ những người khổng lồ ở đây:

Có, nhiều trong số này nhấn mạnh vào các bộ sưu tập và cấu trúc dữ liệu, nhưng đừng nói với tôi rằng đó không phải là nơi hoặc những gì mà bạn thường có khả năng thực hiện hầu hết các tiện ích của mình, trực tiếp hoặc gián tiếp.


+1 choIf deals with String objects and manipulation of string constructs, call it Strings, not StringUtils
Tulains Córdova

+1 Đối với thiết kế API bằng .Net, hãy đọc sách của các kiến ​​trúc sư của .Net. Nguyên tắc thiết kế khung: Các quy ước, thành ngữ và mẫu cho các thư viện .NET có thể tái sử dụng
MarkJ

Tại sao bạn gọi nó là Chuỗi thay vì StringUtil (s)? Chuỗi đối với tôi âm thanh như một đối tượng bộ sưu tập.
bdrx

@bdrx: với tôi một đối tượng bộ sưu tập sẽ là một trong hai StringsCollectionTypeHerenếu bạn muốn triển khai cụ thể. Hoặc một tên cụ thể hơn nếu các chuỗi này có ý nghĩa cụ thể trong ngữ cảnh của ứng dụng của bạn. Trong trường hợp cụ thể này, Guava sử dụng Stringscho các trình trợ giúp liên quan đến chuỗi của nó, trái ngược với StringUtilstrong Commons Lang. Tôi thấy nó hoàn toàn chấp nhận được, nó chỉ có nghĩa với tôi rằng các lớp xử lý Stringđối tượng hoặc là một lớp mục đích chung để quản lý Chuỗi.
haylem

@bdrx: Nói chung, NameUtilstôi không thể kết thúc bởi vì nếu nó nằm dưới một tên gói được dán nhãn rõ ràng, tôi sẽ biết đó là một lớp tiện ích (và nếu không, sẽ nhanh chóng nhận ra khi nhìn vào API). Dường như khó chịu với tôi như những người tuyên bố thứ như SomethingInterface, ISomethinghoặc SomethingImpl. Tôi đã khá ổn với những đồ tạo tác như vậy khi mã hóa bằng C và không sử dụng IDE. Hôm nay tôi thường không cần những thứ đó.
haylem

8

Các lớp Util không gắn kết và nói chung, thiết kế xấu vì một lớp phải có một lý do duy nhất để thay đổi (Nguyên tắc Trách nhiệm duy nhất).

Tuy nhiên, tôi đã nhìn thấy "util" lớp trong rất Java API, như: Math, CollectionsArrays.

Trên thực tế, đây là các lớp tiện ích nhưng tất cả các phương thức của chúng đều liên quan đến một chủ đề duy nhất, một có các phép toán, một có các phương thức để thao tác các bộ sưu tập và lớp kia để thao tác các mảng.

Thử không không có các phương thức hoàn toàn không liên quan trong một lớp tiện ích. Nếu đó là trường hợp, rất có thể bạn cũng có thể đặt chúng ở nơi khác mà chúng thực sự thuộc về.

Nếu phải có util lớp sau đó thử chúng được tách ra theo chủ đề như Java Math, CollectionsArrays. Nó, ít nhất, cho thấy một số ý định thiết kế ngay cả khi chúng chỉ là không gian tên.

Tôi cho một, luôn luôn tránh các lớp tiện ích và không bao giờ có nhu cầu tạo một.


Cảm ơn vì sự trả lời. Vì vậy, ngay cả khi các lớp sử dụng là gói riêng, chúng vẫn có mùi thiết kế?

không phải tất cả mọi thứ thuộc về một lớp nếu bạn bị mắc kẹt với một ngôn ngữ khăng khăng rằng nó sẽ xảy ra thì các lớp học sẽ xảy ra.
jk.

1
Chà, các lớp tiện ích không có nguyên tắc thiết kế để cho bạn biết có bao nhiêu phương thức là quá nhiều, vì không có sự gắn kết để bắt đầu. Làm thế nào bạn có thể biết làm thế nào để dừng lại? Sau 30 phương pháp? 40? 100? Mặt khác, các nguyên tắc OOP cho bạn ý tưởng về việc khi nào một phương thức không thuộc về một lớp cụ thể và khi lớp đó làm quá nhiều. Đó gọi là sự gắn kết. Nhưng như tôi đã nói với bạn trong câu trả lời của tôi, chính những người đã thiết kế Java đã sử dụng các lớp tiện ích. Bạn cũng không thể làm điều đó. Tôi không tạo các lớp tiện ích và không gặp phải tình huống khi thứ gì đó không thuộc về một lớp bình thường.
Tulains Córdova

1
Các lớp Utils thường không phải là các lớp mà chúng chỉ là các không gian tên chứa một loạt các hàm, thường là thuần túy. Các chức năng thuần túy là gắn kết như bạn có thể nhận được.
jk.

1
@jk Ý tôi là Utils ... Nhân tiện, việc đưa ra một cái tên như Mathhoặc Arrayshiển thị ít nhất một số ý định thiết kế.
Tulains Córdova

3

Nó hoàn toàn có thể chấp nhận để có util lớp, mặc dù tôi thích thuật ngữ ClassNameHelper. .NET BCL thậm chí còn có các lớp trợ giúp trong đó. Điều quan trọng nhất cần nhớ, là ghi chép kỹ lưỡng mục đích của các lớp, cũng như từng phương thức của trình trợ giúp riêng lẻ và làm cho nó có thể duy trì mã chất lượng cao.

Và đừng mang đi với các lớp người trợ giúp.


0

Tôi sử dụng một cách tiếp cận hai tầng. Một lớp "Globals" trong gói "tận dụng" (thư mục). Đối với một cái gì đó để đi vào một lớp "Globals" hoặc gói "produc", nó phải là:

  1. Đơn giản,
  2. Không đổi
  3. Không phụ thuộc vào bất cứ điều gì khác trong ứng dụng của bạn

Các ví dụ vượt qua các bài kiểm tra này:

  • Định dạng thập phân và ngày trên toàn hệ thống
  • Dữ liệu toàn cầu bất biến, như EARLIEST_YEAR (mà hệ thống hỗ trợ) hoặc IS_TEST_MODE hoặc một số giá trị thực sự toàn cầu và không thay đổi (trong khi hệ thống đang chạy).
  • Các phương thức trợ giúp rất nhỏ hoạt động xung quanh các khoảng trống trong ngôn ngữ, khung hoặc bộ công cụ của bạn

Đây là một ví dụ về một phương thức trợ giúp rất nhỏ, hoàn toàn độc lập với phần còn lại của ứng dụng:

public static String ordinal(final int i) {
    return (i == 1) ? "1st" :
           (i == 2) ? "2nd" :
           (i == 3) ? "3rd" :
           new StringBuilder().append(i).append("th").toString();
}

Nhìn vào điều này tôi có thể thấy một lỗi 21 nên là "21", 22 nên là "22", v.v.

Nếu một trong những phương thức của trình trợ giúp phát triển hoặc trở nên phức tạp thì nên chuyển sang lớp của chính nó trong gói tiện dụng. Nếu hai hoặc nhiều lớp trình trợ giúp trong gói tiện ích có liên quan với nhau, thì chúng nên được chuyển sang gói riêng của chúng. Nếu một phương thức liên tục hoặc trợ giúp hóa ra có liên quan đến một phần cụ thể của ứng dụng của bạn, thì nó nên được chuyển đến đó.

Bạn sẽ có thể biện minh lý do tại sao không có nơi nào tốt hơn cho mọi thứ bạn gắn bó trong lớp Globals hoặc gói sử dụng và bạn nên làm sạch định kỳ bằng cách sử dụng các bài kiểm tra trên. Nếu không, bạn chỉ đang tạo ra một mớ hỗn độn.

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.