Việc sử dụng hợp lệ của các lớp tĩnh là gì?


8

Tôi nhận thấy rằng gần như mỗi lần tôi thấy các lập trình viên sử dụng các lớp tĩnh trong các ngôn ngữ hướng đối tượng như C #, họ đang làm sai. Các vấn đề chính rõ ràng là tình trạng toàn cầu và khó khăn trong việc hoán đổi việc triển khai trong thời gian chạy hoặc với các giả / cuống trong các thử nghiệm.

Vì tò mò, tôi đã xem xét một vài dự án của mình để chọn những dự án được thử nghiệm tốt và khi tôi nỗ lực thực sự nghĩ về kiến ​​trúc và thiết kế. Hai tập quán của các lớp tĩnh tôi đã tìm thấy là:

  • Lớp tiện ích, một cái gì đó tôi muốn tránh nếu tôi viết mã này ngày hôm nay,

  • Bộ đệm ứng dụng: sử dụng lớp tĩnh cho điều đó hoàn toàn sai. Nếu tôi muốn thay thế nó bằng MemoryCachehoặc Redis thì sao?

Nhìn vào .NET Framework, tôi cũng không thấy bất kỳ ví dụ nào về việc sử dụng các lớp tĩnh hợp lệ. Ví dụ:

  • Filelớp tĩnh làm cho nó thực sự đau đớn để chuyển sang lựa chọn thay thế. Ví dụ: điều gì sẽ xảy ra nếu một người cần chuyển sang Lưu trữ cô lập hoặc lưu trữ tệp trực tiếp trong bộ nhớ hoặc cần nhà cung cấp có thể hỗ trợ giao dịch NTFS hoặc ít nhất có thể xử lý các đường dẫn dài hơn 259 ký tự ? Có một giao diện chung và nhiều triển khai thực hiện dễ dàng như sử dụng Filetrực tiếp, với lợi ích là không phải viết lại hầu hết các cơ sở mã khi các yêu cầu thay đổi.

  • Consolelớp tĩnh làm cho thử nghiệm quá phức tạp. Làm thế nào tôi nên đảm bảo trong một bài kiểm tra đơn vị rằng một phương thức đưa ra một dữ liệu chính xác cho bàn điều khiển? Tôi có nên sửa đổi phương thức để gửi đầu ra tới một ghi có thể ghi Streamđược trong thời gian chạy không? Có vẻ như một lớp giao diện điều khiển không tĩnh sẽ đơn giản như một lớp tĩnh, chỉ cần không có tất cả các nhược điểm của một lớp tĩnh, một lần nữa.

  • Mathlớp tĩnh cũng không phải là một ứng cử viên tốt; Nếu tôi cần chuyển sang số học chính xác tùy ý thì sao? Hoặc để một số thực hiện mới hơn, nhanh hơn? Hoặc với một sơ khai sẽ cho biết, trong một bài kiểm tra đơn vị, rằng một phương thức đã cho của một Mathlớp thực sự được gọi trong một bài kiểm tra?

Trên Lập trình viên. Tôi đã đọc:

Bên cạnh các phương thức mở rộng, việc sử dụng hợp lệ các lớp tĩnh là gì, tức là các trường hợp trong đó Dependency Injection hoặc singletons là không thể hoặc sẽ dẫn đến một thiết kế chất lượng thấp hơn và khả năng mở rộng và bảo trì khó hơn?


trong Groovy ? "Nếu lớp Groovy mà bạn đang kiểm tra thực hiện gọi một phương thức tĩnh trên một lớp Groovy khác, thì bạn có thể sử dụng ExpandoMetaClass cho phép bạn tự động thêm các phương thức, hàm tạo, thuộc tính và phương thức tĩnh ..."
gnat


Điều này không trả lời câu hỏi của bạn (đó là lý do tại sao đó là một nhận xét) và nó có thể sẽ là một ý kiến ​​không phổ biến, nhưng tôi nghĩ rằng bạn đang tìm kiếm một mức độ mô đun hóa mà Java / C # đơn giản không thể cung cấp (mà không nhảy qua nhiều vòng hơn một động vật xiếc). Bạn đã xác định rằng một phương thức tĩnh là một phụ thuộc được mã hóa cứng và nói chung, bất kỳ lớp nào cũng là một phụ thuộc được mã hóa cứng.
Doval 19/12/14

1
Vì vậy, bạn có thể đặt mọi thứ phía sau một giao diện và coi chúng là các mô-đun, nhưng sau đó bạn sẽ cần bộ điều hợp để chuyển đổi từ giao diện này sang giao diện khác khi bạn mang mã nước ngoài hoặc khi bạn muốn dán các chức năng khác nhau vào một giao diện hoặc chọn một tập hợp con của các chức năng của một giao diện. Và bạn mất khả năng có các phương thức nhị phân hữu ích (ít nhất là không gian lận instanceof) vì hai trường hợp IFookhông có gì đảm bảo chúng được hỗ trợ bởi cùng một lớp.
Doval 19/12/14

2
@Magus Đó hoặc bạn ném cánh tay của bạn lên, tiếng nói YAGNI, và chấp nhận rằng nếu bạn cần một loại khác nhau của chuỗi, bạn chỉ sẽ nhận được IDE của bạn để thay đổi mỗi thể hiện của com.lang.lib.Stringđể com.someones.elses.Stringtrong toàn bộ cơ sở mã.
Doval

Câu trả lời:


20

chuyện gì xảy ra nếu; chuyện gì xảy ra nếu; chuyện gì xảy ra nếu?

YAGNI

Nghiêm túc. Nếu ai đó muốn sử dụng các triển khai khác nhau Filehoặc Mathhoặc Consolesau đó họ có thể trải qua nỗi đau của việc trừu tượng hóa đi. Bạn đã thấy / sử dụng Java chưa?!? Các thư viện tiêu chuẩn Java là một ví dụ điển hình về những gì xảy ra khi bạn trừu tượng hóa mọi thứ vì sự trừu tượng hóa. Tôi có thực sự cần trải qua 3 bước để xây dựng đối tượng Lịch của mình không?

Tất cả những gì đã nói, tôi sẽ không ngồi đây và bảo vệ mạnh mẽ các lớp tĩnh. Trạng thái tĩnh là cực kỳ xấu (ngay cả khi tôi vẫn thỉnh thoảng sử dụng nó để gộp chung / lưu trữ mọi thứ). Các hàm tĩnh có thể là xấu do các vấn đề đồng thời và kiểm tra.

Nhưng các hàm tĩnh thuần túy không phải là khủng khiếp. Có được điều tiểu học mà không làm cho tinh thần để ra trừu tượng vì không có sự thay thế lành mạnh cho các hoạt động. Có những triển khai chiến lược có thể là tĩnh vì chúng đã được trừu tượng hóa thông qua đại biểu / functor. Và đôi khi các hàm này không có lớp đối tượng để được liên kết, vì vậy các lớp tĩnh rất tốt để giúp tổ chức chúng.

Ngoài ra, các phương thức mở rộng là một cách sử dụng thực tế , ngay cả khi chúng không phải là các lớp tĩnh về mặt triết học.


1
Một phần của câu hỏi dường như là lời phàn nàn rằng trong khi có những người nói rằng có những cách sử dụng hợp lệ cho các lớp tĩnh, họ không đưa ra tuyên bố đó bằng các ví dụ. Câu trả lời này có thể được cải thiện nếu nó làm như vậy.
Magus

@Magus - 3 ví dụ được đưa ra trong câu hỏi là cách sử dụng hoàn toàn tốt, mặc dù OP lập luận khác.
Telastyn

Tôi không nghĩ đó là một câu trả lời tồi, chỉ là có thể có chỗ để cải thiện. Nếu các ví dụ trong OP là những người duy nhất, điều đó là tốt. Nó có ý nghĩa để nói như vậy trong câu trả lời, vì lợi ích của các tìm kiếm trong tương lai. Dù bằng cách nào, nó có upvote của tôi.
Magus

1
Tôi muốn các khung công tác cung cấp phần nào hỗ trợ tốt hơn cho trạng thái tĩnh của luồng, thay vì xem nó như một suy nghĩ lại. Một cái gì đó giống như Consolethực sự không nên là một phần của trạng thái toàn hệ thống, nhưng cũng không phải là một phần của một đối tượng được truyền xung quanh liên tục. Thay vào đó, có vẻ như sẽ hợp lý hơn khi nói rằng mỗi luồng có thuộc tính "bàn điều khiển hiện tại" có thể dễ dàng lưu và khôi phục nếu cần phải có một thói quen viết để Consolesử dụng một thứ khác thay thế.
supercat

1
@BenjaminHodgson - thiết kế hiếm khi định lượng. Dù sao, các phương thức mở rộng có thể được triển khai khác nhau (nghĩ rằng thao tác nguyên mẫu JavaScript) và người dùng sẽ không khôn ngoan hơn. Rằng chúng là các hàm tĩnh là một chi tiết triển khai, không phải là một phần nội tại trong bản chất của chúng.
Telastyn

18

Trong một từ Đơn giản.

Khi bạn tách rời quá nhiều, bạn sẽ có được thế giới xin chào từ địa ngục.

void main(String[] args) {
    TextOutputFactory outputFactory = new TextOutputFactory();
    OutputStream stream = outputFactory.CreateStdOutputStream();

    Encoding encoding = new EncodingFactory.CreateUtf8Encoding();
    stream.Encoding = encoding;

    SystemConstant constant = new SystemConstant();
    stream.LineEnding = constant.PlatformLineEnding;

    stream.FlushOnEachLine = true;

    String greeting = new FixedSizeInMemoryString(); // We may have to switch to a file based string later!"
    greeting.SetContent("Hello world");
    greeting.Initialize();

    stream.SendContent(greeting);
    stream.EndCurrentLine();

    ProcessCompletion completion = new ProcessCompletion();
    completion.Status = constant.SuccessStatus;
    completion.ExitProgram();
}

Nhưng này, ít nhất là không có cấu hình XML, đó là một liên lạc tốt đẹp.

lớp tĩnh cho phép tối ưu hóa cho trường hợp nhanh và phổ biến. Hầu hết các điểm bạn đề cập có thể được giải quyết rất dễ dàng bằng cách giới thiệu sự trừu tượng của riêng bạn đối với chúng hoặc sử dụng những thứ như Console.Out.


5
Điều này làm tôi nhớ đến GNU Hello World, giải thích cho các mối quan tâm nội địa hóa khác nhau.
Telastyn

1
-1thiếu cấu hình XML. +2 Để đặt nó trong một tóm tắt.
s1lv3r

1
Hello World thực sự nên được khái quát hóa để cho phép các kịch bản siêu cân bằng. Ý tôi là, tôi có thể đang chào một thế giới khác , sau tất cả. Cuối cùng. Trước tiên, chúng ta cần xác định một số hình thức giao tiếp phổ biến ... Các số nguyên tố nhị phân, được gửi bởi tín hiệu sóng mang AM ... nhưng tần số nào? Tôi biết, chế độ cơ bản của các nguyên tử silicon!

10

Trường hợp cho các phương thức tĩnh: Điều này:

var c = Math.Max(a, Int32.Parse(b));

đơn giản hơn và rõ ràng hơn và dễ đọc hơn thế này:

IMathLibrary math = new DefaultMathLibrary();
IIntegerParser integerParser = new DefaultIntegerParser();
var c = math.Max(a, integerParser.Parse(b));

Bây giờ thêm phép tiêm phụ thuộc để ẩn các cảnh báo rõ ràng và xem một lớp lót phát triển thành hàng chục hoặc hàng trăm dòng - tất cả để hỗ trợ cho kịch bản không thể xảy ra mà bạn phát minh ra - việc Maxthực hiện nhanh hơn đáng kể so với trong khung bạn cần để có thể chuyển đổi một cách trong suốt giữa các lần thực hiện thay thế Max.

Thiết kế API là sự đánh đổi giữa sự đơn giản và tính linh hoạt. Bạn luôn có thể giới thiệu các lớp bổ sung ( ad infinitum ), nhưng bạn phải cân nhắc sự thuận tiện của trường hợp thông thường so với lợi ích của các lớp bổ sung.

Ví dụ: bạn luôn có thể viết trình bao bọc cá thể của riêng bạn xung quanh API hệ thống tệp nếu bạn cần có khả năng chuyển đổi trong suốt giữa các nhà cung cấp lưu trữ. Nhưng nếu không có các phương thức tĩnh đơn giản hơn, mọi người sẽ buộc phải tạo một cá thể mỗi khi họ phải làm một việc đơn giản như lưu tệp. Nó sẽ thực sự là một sự đánh đổi thiết kế tồi để tối ưu hóa cho một trường hợp cạnh cực kỳ hiếm, và làm cho mọi thứ trở nên phức tạp hơn cho trường hợp phổ biến. Tương tự với Console- bạn có thể dễ dàng tạo trình bao bọc của riêng mình nếu bạn cần trừu tượng hóa hoặc giả lập bảng điều khiển, nhưng bạn thường sử dụng bàn điều khiển cho các giải pháp nhanh chóng hoặc mục đích gỡ lỗi, vì vậy bạn chỉ muốn cách đơn giản nhất có thể để xuất một số văn bản.

Hơn nữa, trong khi "một giao diện chung với nhiều triển khai" là tuyệt vời, thì không nhất thiết phải có sự trừu tượng hóa "đúng" để thống nhất tất cả các nhà cung cấp lưu trữ mà bạn muốn. Bạn có thể muốn chuyển đổi giữa các tệp và lưu trữ bị cô lập, nhưng người khác có thể muốn chuyển đổi giữa các tệp, cơ sở dữ liệu và cookie để lưu trữ. Giao diện chung sẽ trông khác nhau và không thể dự đoán tất cả các khái niệm trừu tượng có thể có mà người dùng có thể muốn. Sẽ linh hoạt hơn nhiều khi cung cấp các phương thức tĩnh dưới dạng "nguyên thủy", và sau đó cho phép người dùng xây dựng các tóm tắt và trình bao bọc của riêng họ.

MathVí dụ của bạn thậm chí còn đáng ngờ hơn. Nếu bạn muốn thay đổi thành toán chính xác tùy ý, có lẽ bạn sẽ cần các kiểu số mới. Đây sẽ là một thay đổi cơ bản của toàn bộ chương trình của bạn và phải thay đổi Math.Max()thành điều ArbitraryPrecisionMath.Max()chắc chắn là điều bạn lo lắng nhất.

Điều đó nói rằng, tôi đồng ý rằng các lớp tĩnh trạng thái trong hầu hết các trường hợp xấu.


0

Nói chung, những nơi duy nhất mà tôi sẽ sử dụng các lớp tĩnh là những nơi mà lớp lưu trữ các hàm thuần túy không vượt qua ranh giới hệ thống. Tôi nhận ra cái sau là dư thừa vì bất cứ thứ gì vượt qua một ranh giới đều có khả năng bởi ý định không thuần túy. Tôi đi đến kết luận này cả từ sự không tin tưởng của nhà nước toàn cầu và từ quan điểm thử nghiệm dễ dàng. Ngay cả khi có một kỳ vọng hợp lý rằng sẽ có một triển khai duy nhất cho một lớp vượt qua ranh giới hệ thống (mạng, hệ thống tệp, thiết bị I / O), tôi chọn sử dụng các thể hiện thay vì các lớp tĩnh vì khả năng cung cấp giả / thực hiện giả trong các thử nghiệm đơn vị là rất quan trọng trong việc phát triển các thử nghiệm đơn vị nhanh mà không có khớp nối với các thiết bị thực tế. Trong trường hợp phương thức là một hàm thuần túy không có khớp nối với hệ thống / thiết bị bên ngoài, tôi nghĩ rằng một lớp tĩnh có thể phù hợp nhưng chỉ khi nó không cản trở khả năng kiểm tra. Tôi thấy các trường hợp sử dụng là tương đối hiếm bên ngoài các lớp khung hiện có. Ngoài ra, có thể có trường hợp bạn không có lựa chọn - ví dụ: các phương thức mở rộng trong C #.

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.