Lớp học với phương pháp duy nhất - phương pháp tốt nhất?


172

Nói rằng tôi có một lớp có nghĩa là để thực hiện một chức năng duy nhất. Sau khi thực hiện chức năng, nó có thể bị phá hủy. Có bất kỳ lý do để thích một trong những phương pháp này?

// Initialize arguments in constructor
MyClass myObject = new MyClass(arg1, arg2, arg3);
myObject.myMethod();

// Pass arguments to method
MyClass myObject = new MyClass();
myObject.myMethod(arg1, arg2, arg3);

// Pass arguments to static method
MyClass.myMethod(arg1, arg2, arg3);

Tôi đã cố tình mơ hồ về các chi tiết, để cố gắng có được hướng dẫn cho các tình huống khác nhau. Nhưng tôi không thực sự có các hàm thư viện đơn giản như Math.random (). Tôi đang nghĩ nhiều hơn về các lớp thực hiện một số nhiệm vụ cụ thể, phức tạp, nhưng chỉ yêu cầu một phương thức (công khai) để thực hiện.

Câu trả lời:


264

Tôi đã từng yêu thích các lớp tiện ích chứa đầy các phương thức tĩnh. Họ đã thực hiện một sự hợp nhất tuyệt vời của các phương pháp trợ giúp mà nếu không sẽ nằm xung quanh gây ra sự dư thừa và bảo trì địa ngục. Chúng rất dễ sử dụng, không cần khởi tạo, không xử lý, chỉ cần đốt lửa. Tôi đoán đây là nỗ lực vô tình đầu tiên của tôi trong việc tạo ra một kiến ​​trúc hướng dịch vụ - rất nhiều dịch vụ phi trạng thái chỉ làm công việc của họ và không có gì khác. Khi một hệ thống phát triển, những con rồng đang đến.

Đa hình
Nói chúng ta có phương thức UtilityClass.SomeMethod vui vẻ reo lên. Đột nhiên chúng ta cần thay đổi chức năng một chút. Hầu hết các chức năng là như nhau, nhưng chúng ta phải thay đổi một vài phần dù sao. Nếu nó không phải là một phương thức tĩnh, chúng ta có thể tạo một lớp phái sinh và thay đổi nội dung phương thức khi cần. Vì nó là một phương pháp tĩnh, chúng ta không thể. Chắc chắn, nếu chúng ta chỉ cần thêm chức năng trước hoặc sau phương thức cũ, chúng ta có thể tạo một lớp mới và gọi lớp cũ bên trong nó - nhưng đó chỉ là thô.

Khủng hoảng giao diện
Các phương thức tĩnh không thể được xác định thông qua các giao diện vì lý do logic. Và vì chúng ta không thể ghi đè các phương thức tĩnh, các lớp tĩnh là vô dụng khi chúng ta cần chuyển chúng qua giao diện của chúng. Điều này làm cho chúng ta không thể sử dụng các lớp tĩnh như là một phần của mẫu chiến lược. Chúng tôi có thể khắc phục một số vấn đề bằng cách chuyển đại biểu thay vì giao diện .

Kiểm tra
Điều này về cơ bản đi đôi với các tai ương giao diện được đề cập ở trên. Vì khả năng trao đổi triển khai của chúng tôi rất hạn chế, chúng tôi cũng sẽ gặp khó khăn khi thay thế mã sản xuất bằng mã kiểm tra. Một lần nữa, chúng ta có thể gói chúng lại nhưng nó sẽ yêu cầu chúng ta thay đổi phần lớn mã của chúng ta chỉ để có thể chấp nhận các hàm bao thay vì các đối tượng thực tế.

Thúc đẩy các đốm màu
Vì các phương thức tĩnh thường được sử dụng làm phương thức tiện ích và phương thức tiện ích thường sẽ có các mục đích khác nhau, chúng tôi sẽ nhanh chóng kết thúc với một lớp lớn chứa đầy chức năng không kết hợp - lý tưởng là mỗi lớp nên có một mục đích duy nhất trong hệ thống . Tôi muốn có nhiều hơn năm lần các lớp miễn là mục đích của chúng được xác định rõ.

Thông số leo
Để bắt đầu, phương pháp tĩnh nhỏ dễ thương và ngây thơ đó có thể có một tham số duy nhất. Khi chức năng phát triển, một vài tham số mới được thêm vào. Các tham số tiếp theo được thêm vào là tùy chọn, vì vậy chúng tôi tạo ra sự quá tải của phương thức (hoặc chỉ thêm các giá trị mặc định, bằng các ngôn ngữ hỗ trợ chúng). Không lâu sau, chúng ta có một phương thức lấy 10 tham số. Chỉ có ba cái đầu tiên thực sự cần thiết, tham số 4-7 là tùy chọn. Nhưng nếu tham số 6 được chỉ định, 7-9 cũng được yêu cầu điền vào ... Chúng ta đã tạo một lớp với mục đích duy nhất là làm phương thức tĩnh này đã làm gì, chúng ta có thể giải quyết điều này bằng cách lấy các tham số bắt buộc trong hàm tạo và cho phép người dùng đặt các giá trị tùy chọn thông qua các thuộc tính hoặc phương thức để đặt nhiều giá trị phụ thuộc lẫn nhau cùng một lúc. Ngoài ra, nếu một phương thức đã phát triển đến mức độ phức tạp này,

Yêu cầu người tiêu dùng tạo ra một thể hiện của các lớp mà không có lý do
Một trong những đối số phổ biến nhất là, tại sao yêu cầu người tiêu dùng của lớp chúng ta tạo một thể hiện để gọi phương thức đơn này, trong khi không sử dụng ví dụ sau đó? Tạo một thể hiện của một lớp là một hoạt động rất rẻ ở hầu hết các ngôn ngữ, vì vậy tốc độ không phải là vấn đề. Thêm một dòng mã bổ sung cho người tiêu dùng là một chi phí thấp để đặt nền tảng của một giải pháp dễ bảo trì hơn nhiều trong tương lai. Và cuối cùng, nếu bạn muốn tránh tạo các thể hiện, chỉ cần tạo một trình bao bọc đơn của lớp cho phép tái sử dụng dễ dàng - mặc dù điều này làm cho yêu cầu rằng lớp của bạn không trạng thái. Nếu nó không trạng thái, bạn vẫn có thể tạo các phương thức trình bao bọc tĩnh xử lý mọi thứ, trong khi vẫn mang lại cho bạn tất cả lợi ích trong thời gian dài. Cuối cùng,

Chỉ có một Sith thỏa thuận trong tuyệt đối
Tất nhiên, có những trường hợp ngoại lệ đối với việc tôi không thích các phương thức tĩnh. Các lớp tiện ích thực sự không gây ra bất kỳ rủi ro nào cho sự phình to là trường hợp tuyệt vời cho các phương thức tĩnh - System.Convert làm ví dụ. Nếu dự án của bạn là một lần duy nhất không có yêu cầu bảo trì trong tương lai, thì kiến ​​trúc tổng thể thực sự không quan trọng lắm - tĩnh hoặc không tĩnh, không thực sự quan trọng - tuy nhiên tốc độ phát triển.

Tiêu chuẩn, tiêu chuẩn, tiêu chuẩn!
Sử dụng các phương thức cá thể không ngăn cản bạn sử dụng các phương thức tĩnh và ngược lại. Miễn là có lý do đằng sau sự khác biệt và nó được chuẩn hóa. Không có gì tệ hơn là nhìn qua một lớp kinh doanh ngổn ngang với các phương thức thực hiện khác nhau.


Như Mark nói, tính đa hình, có thể truyền các phương thức dưới dạng tham số 'chiến lược' và có thể định cấu hình / thiết lập tùy chọn là tất cả lý do để sử dụng một thể hiện . Ví dụ: một ListD006iter hoặc DelimiterParser có thể được cấu hình để sử dụng / chấp nhận các dấu phân cách nào, có nên cắt khoảng trắng từ các mã thông báo được phân tích cú pháp hay không, có nên đặt dấu ngoặc trong danh sách, cách xử lý danh sách trống / null .. vv
Thomas W

4
"Khi một hệ thống phát triển ..." bạn tái cấu trúc?
Rodney Gitzel

3
@ user3667089 Các đối số chung cần thiết cho lớp tồn tại một cách logic, những đối số mà tôi sẽ truyền vào hàm tạo. Chúng thường được sử dụng trong hầu hết / tất cả các phương pháp. Đối số phương thức cụ thể tôi sẽ vượt qua trong phương thức cụ thể đó.
Đánh dấu S. Rasmussen

1
Thực sự những gì bạn đang làm với một lớp tĩnh sẽ quay trở lại C thô, không hướng đối tượng (mặc dù được quản lý bộ nhớ) - tất cả các chức năng đều ở trong một không gian toàn cầu, không có this, bạn phải quản lý trạng thái toàn cầu, v.v. Nếu bạn thích lập trình thủ tục, hãy làm điều này. Nhưng đánh giá cao rằng sau đó bạn mất nhiều lợi ích cấu trúc của OO.
Kỹ sư

1
Hầu hết các lý do này phải làm với quản lý mã xấu, không sử dụng các phương thức tĩnh. Trong F # và các ngôn ngữ chức năng khác, chúng tôi sử dụng các phương thức tĩnh cơ bản (các hàm không có trạng thái thể hiện) ở mọi nơi và không gặp phải các vấn đề này. Điều đó nói rằng, F # cung cấp một mô hình tốt hơn cho việc sử dụng các hàm (các hàm là hạng nhất, các hàm có thể được xử lý và áp dụng một phần) khiến chúng khả thi hơn trong C #. Một lý do lớn hơn để sử dụng các lớp là vì đó là những gì C # được xây dựng cho. Tất cả các cấu trúc Dependency Injection trong .NET Core đều xoay quanh các lớp cá thể với deps.
Justin J Stark

89

Tôi thích cách tĩnh. Vì Class không đại diện cho một đối tượng, nó không có ý nghĩa để tạo ra một thể hiện của nó.

Các lớp chỉ tồn tại cho các phương thức của chúng nên được giữ tĩnh.


19
-1 "Các lớp chỉ tồn tại cho các phương thức của chúng nên được giữ tĩnh."
Rookian

8
@Rookian tại sao bạn không đồng ý với điều đó?
jjnguy

6
một ví dụ sẽ là mẫu kho lưu trữ. Lớp chỉ chứa các phương thức. Theo cách tiếp cận của bạn không cho phép sử dụng giao diện. => tăng khớp nối
Rookian

1
@Rook, nói chung mọi người không nên tạo các lớp chỉ được sử dụng cho các phương thức. Trong ví dụ bạn đã đưa ra các phương thức tĩnh không phải là một ý tưởng tốt. Nhưng đối với các phương thức tiện ích đơn giản, cách tĩnh là tốt nhất.
jjnguy

9
Hoàn toàn chính xác :) Với conditon của bạn "cho các phương thức tiện ích đơn giản" Tôi hoàn toàn đồng ý với bạn, nhưng bạn đã đưa ra một quy tắc chung trong câu trả lời của bạn là imo sai.
Rookian

18

Nếu không có lý do để có một thể hiện của lớp được tạo để thực thi hàm thì hãy sử dụng triển khai tĩnh. Tại sao làm cho người tiêu dùng của lớp này tạo một cá thể khi không cần thiết.


16

Nếu bạn không cần lưu trạng thái của đối tượng, thì không cần phải khởi tạo nó ngay từ đầu. Tôi sẽ đi với phương thức tĩnh duy nhất mà bạn truyền tham số.

Tôi cũng sẽ cảnh báo chống lại một lớp Utils khổng lồ có hàng tá phương thức tĩnh không liên quan. Điều này có thể trở nên vô tổ chức và khó sử dụng một cách vội vàng. Tốt hơn là có nhiều lớp, mỗi lớp có ít phương thức liên quan.


6

Tôi có thể nói định dạng Phương thức tĩnh sẽ là lựa chọn tốt hơn. Và tôi cũng sẽ làm cho lớp tĩnh, theo cách đó bạn sẽ không phải lo lắng về việc vô tình tạo một thể hiện của lớp.


5

Tôi thực sự không biết tình huống ở đây là gì, nhưng tôi sẽ xem việc đặt nó như một phương thức trong một trong các lớp mà arg1, arg2 hoặc arg3 thuộc về - Nếu bạn có thể nói rằng về mặt ngữ nghĩa thì một trong những lớp đó sẽ sở hữu phương pháp.


4

Tôi đề nghị rằng thật khó để trả lời dựa trên thông tin được cung cấp.

Quan điểm của tôi là nếu bạn chỉ có một phương thức, và bạn sẽ ném lớp đó ngay lập tức, thì hãy biến nó thành một lớp tĩnh có tất cả các tham số.

Tất nhiên, thật khó để nói chính xác lý do tại sao bạn cần tạo một lớp duy nhất chỉ cho một phương thức này. Đây có phải là tình huống "Lớp tiện ích" điển hình như hầu hết đang giả định không? Hoặc bạn đang thực hiện một số loại quy tắc, trong đó có thể có nhiều hơn trong tương lai.

Ví dụ, có lớp đó có thể cắm được. Sau đó, bạn muốn tạo Giao diện cho một phương thức của mình và sau đó bạn muốn có tất cả các tham số được truyền vào giao diện, thay vì vào hàm tạo, nhưng bạn sẽ không muốn nó ở trạng thái tĩnh.


3

Lớp học của bạn có thể được thực hiện tĩnh?

Nếu vậy, thì tôi sẽ biến nó thành một lớp 'Tiện ích' mà tôi sẽ đặt tất cả các lớp một chức năng của mình vào.


2
Tôi hơi không đồng ý. Trong các dự án tôi tham gia, lớp Tiện ích của bạn có thể có hàng trăm phương thức không liên quan trong đó.
Paul Tomblin

3
@Paul: Nói chung là đồng ý. Tôi ghét phải xem các lớp học với hàng tá (hoặc có, hàng trăm) phương pháp hoàn toàn không liên quan. Nếu người ta phải thực hiện phương pháp này, ít nhất là chia chúng thành các bộ tiện ích nhỏ hơn, có liên quan (EG, FooUtilities, BarUtilities, v.v.).
John Rudy

9
một lớp học - một trách nhiệm
Andreas Petersson

3

Nếu phương pháp này là không trạng thái và bạn không cần phải vượt qua nó, thì nó có ý nghĩa nhất để định nghĩa nó là tĩnh. Nếu bạn cần phải truyền phương thức xung quanh, bạn có thể cân nhắc sử dụng một đại biểu thay vì một trong những phương pháp đề xuất khác của bạn.


3

Đối với các ứng dụng và internaltrợ giúp đơn giản , tôi sẽ sử dụng một phương thức tĩnh. Đối với các ứng dụng có thành phần, tôi yêu Khung mở rộng được quản lý . Đây là một đoạn trích từ một tài liệu tôi đang viết để mô tả các mẫu bạn sẽ tìm thấy trên các API của mình.

  • Dịch vụ
    • Được xác định bởi một I[ServiceName]Service giao diện.
    • Xuất và nhập theo loại giao diện.
    • Việc triển khai duy nhất được cung cấp bởi ứng dụng máy chủ và được sử dụng trong nội bộ và / hoặc bởi các tiện ích mở rộng.
    • Các phương thức trên giao diện dịch vụ là an toàn chủ đề.

Như một ví dụ giả định:

public interface ISettingsService
{
    string ReadSetting(string name);

    void WriteSetting(string name, string value);
}

[Export]
public class ObjectRequiringSettings
{
    [Import]
    private ISettingsService SettingsService
    {
        get;
        set;
    }

    private void Foo()
    {
        if (SettingsService.ReadSetting("PerformFooAction") == bool.TrueString)
        {
            // whatever
        }
    }
}

2

Tôi sẽ chỉ làm mọi thứ trong hàm tạo. như vậy

new MyClass(arg1, arg2, arg3);// the constructor does everything.

hoặc là

MyClass my_object(arg1, arg2, arg3);

Điều gì nếu bạn cần trả lại bất cứ thứ gì ngoài một đối tượng thuộc loại MyClass?
Lập hóa đơn cho thằn lằn

13
Tôi coi đó là thực tế xấu để đặt logic thực thi trong một hàm tạo. Phát triển tốt là về ngữ nghĩa. Về mặt ngữ nghĩa, một constructor tồn tại để xây dựng một đối tượng, không phải để thực hiện các nhiệm vụ khác.
mstrobl

hóa đơn, nếu bạn cần giá trị trả về, chuyển tham chiếu đến ret vvalue: MyClass myObject (arg1, arg2, arg3, retvalue); mstrobl, nếu đối tượng không cần thiết, tại sao tạo nó? và thủ thuật này thực tế có thể giúp bạn trong một số trường hợp.

Nếu một đối tượng không có trạng thái, tức là không có vars, thì việc kích hoạt nó sẽ không tạo ra bất kỳ sự phân bổ nào - không phải trên heap hay stack. Bạn có thể nghĩ về các đối tượng trong C ++ chỉ là các hàm C gọi với một tham số đầu tiên bị ẩn chỉ đến một cấu trúc.
mstrobl

mstrobl, nó giống như chức năng ac trên steroid vì bạn có thể xác định các chức năng riêng tư mà ctor có thể sử dụng. bạn cũng có thể sử dụng các biến thành viên lớp để giúp truyền dữ liệu đến các hàm riêng.

0

Một vấn đề quan trọng hơn cần xem xét là liệu hệ thống sẽ chạy ở môi trường đa luồng hay không và liệu nó có an toàn cho luồng hay không khi có một phương thức tĩnh hoặc các biến ...

Bạn nên chú ý đến trạng thái hệ thống.


0

Bạn có thể tránh được tình huống này cùng nhau. Cố gắng cấu trúc lại để bạn có đượcarg1.myMethod1(arg2, arg3) . Trao đổi arg1 với arg2 hoặc arg3 nếu nó có ý nghĩa hơn.

Nếu bạn không có quyền kiểm soát lớp arg1, thì hãy trang trí nó:

class Arg1Decorator
    private final T1 arg1;
    public Arg1Decorator(T1 arg1) {
        this.arg1 = arg1;
    }
    public T myMethod(T2 arg2, T3 arg3) {
        ...
    }
 }

 arg1d = new Arg1Decorator(arg1)
 arg1d.myMethod(arg2, arg3)

Lý do là, trong OOP, dữ liệu và phương thức xử lý dữ liệu đó thuộc về nhau. Thêm vào đó bạn nhận được tất cả những lợi thế mà Mark đã đề cập.


0

tôi nghĩ, nếu các thuộc tính của lớp của bạn hoặc thể hiện của lớp sẽ không được sử dụng trong các hàm tạo hoặc trong các phương thức của bạn, các phương thức không được đề xuất để được thiết kế như mẫu 'tĩnh'. phương pháp tĩnh nên luôn luôn được suy nghĩ theo cách 'trợ giúp'.


0

Tùy thuộc vào việc bạn muốn chỉ làm một cái gì đó hoặc làm và trả lại một cái gì đó bạn có thể làm điều này:

public abstract class DoSomethingClass<T>
{
    protected abstract void doSomething(T arg1, T arg2, T arg3);
}

public abstract class ReturnSomethingClass<T, V>
{
    public T value;
    protected abstract void returnSomething(V arg1, V arg2, V arg3);
}

public class DoSomethingInt extends DoSomethingClass<Integer>
{
    public DoSomethingInt(int arg1, int arg2, int arg3)
    {
        doSomething(arg1, arg2, arg3);
    }

    @Override
    protected void doSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        // ...
    }
}

public class ReturnSomethingString extends ReturnSomethingClass<String, Integer>
{
    public ReturnSomethingString(int arg1, int arg2, int arg3)
    {
        returnSomething(arg1, arg2, arg3);
    }

    @Override
    protected void returnSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        String retValue;
        // ...
        value = retValue;
    }
}

public class MainClass
{
    static void main(String[] args)
    {
        int a = 3, b = 4, c = 5;

        Object dummy = new DoSomethingInt(a,b,c);  // doSomething was called, dummy is still around though
        String myReturn = (new ReturnSomethingString(a,b,c)).value; // returnSomething was called and immediately destroyed
    }
}
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.