2009-04-15 30 views
77

Tôi có xu hướng tuyên bố là tĩnh tất cả các phương thức trong một lớp khi lớp đó không yêu cầu theo dõi trạng thái bên trong. Ví dụ, nếu tôi cần biến đổi A thành B và không dựa vào một số trạng thái bên trong C có thể thay đổi, tôi tạo một biến đổi tĩnh. Nếu có một trạng thái bên trong C mà tôi muốn có thể điều chỉnh, thì tôi thêm một hàm tạo để thiết lập C và không sử dụng một biến đổi tĩnh.Có sử dụng nhiều phương pháp tĩnh không?

Tôi đọc các đề xuất khác nhau (bao gồm trên StackOverflow) KHÔNG sử dụng quá nhiều phương pháp tĩnh nhưng tôi vẫn không hiểu sai về quy tắc của ngón tay cái ở trên.

Đó có phải là cách tiếp cận hợp lý hay không?

Trả lời

119

Có hai loại phương pháp tĩnh chung:

  • A "an toàn" phương pháp tĩnh sẽ luôn luôn cung cấp cho các đầu ra tương tự cho các đầu vào tương tự. Nó sửa đổi không có globals, và không gọi bất kỳ phương thức tĩnh "không an toàn" của bất kỳ lớp nào. Về cơ bản, bạn đang sử dụng một loại chương trình chức năng giới hạn - đừng sợ chúng, chúng ổn.
  • Phương thức tĩnh "không an toàn" làm thay đổi trạng thái toàn cầu hoặc proxy thành đối tượng chung hoặc một số hành vi không thể kiểm tra khác. Đây là những cú hích đối với lập trình thủ tục, và nên được tái cấu trúc nếu có thể.

Có một vài cách sử dụng phổ biến của các thống kê "không an toàn" - ví dụ, trong mẫu Singleton - nhưng lưu ý rằng mặc dù có tên đẹp, bạn chỉ cần thay đổi các biến toàn cầu. Hãy suy nghĩ cẩn thận trước khi sử dụng các thống kê không an toàn.

+0

Đây chính là vấn đề tôi phải giải quyết - việc sử dụng, hay đúng hơn là sử dụng sai mục đích của các đối tượng Singleton. – overslacked

+0

Cảm ơn bạn vì câu trả lời xuất sắc nhất. Câu hỏi của tôi là, nếu các singletons được truyền vào như các tham số cho các phương thức tĩnh, điều đó có làm cho phương thức tĩnh không an toàn không? –

+0

Thuật ngữ "hàm thuần túy" và "hàm số không tinh khiết" là các tên được đặt trong lập trình chức năng cho những gì bạn gọi là "an toàn" và "không an toàn" thống kê. – Omnimike

3

Điều đó có vẻ là một cách tiếp cận hợp lý. Lý do bạn không muốn sử dụng quá nhiều các lớp/phương thức tĩnh là bạn sẽ phải rời khỏi chương trình hướng đối tượng và nhiều hơn nữa vào lĩnh vực lập trình có cấu trúc.

Trong trường hợp của bạn, nơi bạn chỉ đơn giản là chuyển từ A đến B, nói tất cả chúng ta đang làm là chuyển văn bản để đi từ

"hello" =>(transform)=> "<b>Hello!</b>" 

Sau đó, một phương pháp tĩnh sẽ có ý nghĩa.

Tuy nhiên, nếu bạn thường xuyên gọi các phương thức tĩnh này trên một đối tượng và nó có xu hướng duy nhất cho nhiều cuộc gọi (ví dụ như cách bạn sử dụng nó phụ thuộc vào đầu vào), hoặc nó là một phần của hành vi vốn có của đối tượng, nó sẽ là khôn ngoan để làm cho nó trở thành một phần của đối tượng và duy trì trạng thái của nó. Một cách để làm điều này là triển khai nó như một giao diện.

class Interface{ 
    method toHtml(){ 
     return transformed string (e.g. "<b>Hello!</b>") 
    } 

    method toConsole(){ 
     return transformed string (e.g. "printf Hello!") 
    } 
} 


class Object implements Interface { 
    mystring = "hello" 

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object 

    method toHtml() 
    method toConsole() 
} 

Chỉnh sửa: Một ví dụ tốt về sử dụng tuyệt vời các phương pháp tĩnh là phương pháp trợ giúp html trong Asp.Net MVC hoặc Ruby. Chúng tạo ra các phần tử html không gắn liền với hành vi của một đối tượng và do đó là tĩnh.

Chỉnh sửa 2: Thay đổi lập trình chức năng thành lập trình có cấu trúc (vì một số lý do tôi bị nhầm lẫn), đạo cụ để Torsten chỉ ra điều đó.

+2

Tôi không nghĩ rằng việc sử dụng các phương pháp tĩnh đủ điều kiện là lập trình hàm, vì vậy tôi đoán bạn có nghĩa là lập trình có cấu trúc. –

14

Một đối tượng không có trạng thái bên trong là một điều đáng ngờ.

Thông thường, các đối tượng đóng gói trạng thái và hành vi. Một đối tượng mà chỉ đóng gói hành vi là lẻ. Đôi khi, đó là ví dụ về Trọng lượng nhẹ hoặc Bánh xe.

Lần khác, thiết kế thủ tục được thực hiện bằng ngôn ngữ đối tượng.

+5

Tôi nghe những gì bạn đang nói, nhưng làm thế nào có thể một cái gì đó giống như một đối tượng Math đóng gói bất cứ điều gì nhưng hành vi? – JonoW

+8

Anh ta chỉ nghi ngờ, không sai, và anh ấy hoàn toàn đúng. –

+2

@JonoW: Toán học là một trường hợp rất đặc biệt khi có nhiều chức năng không quốc tịch.Tất nhiên, nếu bạn đang làm lập trình chức năng trong Java, bạn sẽ có nhiều chức năng không trạng thái. –

1

Miễn là trạng thái nội bộ không được phát, điều này là tốt. Lưu ý rằng các phương thức tĩnh thường được mong đợi là an toàn luồng, vì vậy nếu bạn sử dụng cấu trúc dữ liệu của trình trợ giúp, hãy sử dụng chúng theo cách an toàn.

5

Các tùy chọn khác là thêm chúng như các phương pháp không tĩnh trên đối tượng có nguồn gốc:

tức là, thay đổi:

public class BarUtil { 
    public static Foo transform(Bar toFoo) { ... } 
} 

vào

public class Bar { 
    ... 
    public Foo transform() { ...} 
} 
tuy nhiên

trong nhiều tình huống isn này không thể (ví dụ, tạo mã lớp thông thường từ XSD/WSDL/etc), hoặc nó sẽ làm cho lớp này rất dài, và các phương thức chuyển đổi thường có thể là một nỗi đau thực sự cho các đối tượng phức tạp và bạn chỉ muốn chúng trong lớp riêng của chúng. Vì vậy, yeah, tôi có các phương thức tĩnh trong các lớp tiện ích.

3

Lý do bạn được cảnh báo tránh xa các phương pháp tĩnh là việc sử dụng chúng sẽ mất một trong những ưu điểm của đối tượng. Các đối tượng được thiết kế để đóng gói dữ liệu. Điều này ngăn cản các tác dụng phụ không mong muốn xảy ra, tránh các lỗi. Các phương thức tĩnh không có dữ liệu được đóng gói * và do đó không thu được lợi ích này.

Điều đó nói rằng, nếu bạn không sử dụng dữ liệu nội bộ, chúng sẽ tốt để sử dụng và nhanh hơn một chút để thực thi. Hãy chắc chắn rằng bạn không chạm vào dữ liệu toàn cầu trong chúng.

  • Một số ngôn ngữ cũng có các biến cấp lớp cho phép đóng gói dữ liệu và phương pháp tĩnh.
1

Gần đây, tôi đã tái cấu trúc một ứng dụng để xóa/sửa đổi một số lớp đã được triển khai ban đầu dưới dạng lớp tĩnh. Theo thời gian, các lớp này đã thu được rất nhiều và mọi người chỉ cần gắn thẻ các hàm mới là tĩnh, vì không bao giờ có một cá thể nổi xung quanh. Vì vậy, câu trả lời của tôi là các lớp tĩnh không phải là xấu nhưng nó có thể được dễ dàng hơn để bắt đầu tạo các trường hợp bây giờ, sau đó phải refactor sau này. Quay lại đầu trang |

0

Nếu bạn biết mình sẽ không bao giờ cần phải sử dụng trạng thái nội bộ của C, không sao cả. Nếu điều đó sẽ thay đổi trong tương lai, bạn cần phải làm cho phương thức không tĩnh. Nếu nó không tĩnh để bắt đầu, bạn có thể bỏ qua trạng thái bên trong nếu bạn không cần nó.

1

Tôi đã từng chuyển qua lại giữa một lớp với một loạt các phương pháp tĩnh và một singleton. Cả hai giải quyết vấn đề, nhưng singleton có thể dễ dàng thay thế nhiều hơn với nhiều hơn một. (Các lập trình viên dường như luôn chắc chắn rằng sẽ chỉ có 1 điều gì đó và tôi thấy mình đã đủ sai lầm để hoàn toàn từ bỏ các phương pháp tĩnh trừ một số trường hợp rất hạn chế).

Dù sao, singleton cung cấp cho bạn khả năng sau này vượt qua một cái gì đó vào nhà máy để có được một trường hợp khác và thay đổi hành vi của toàn bộ chương trình của bạn mà không cần tái cấu trúc. Thay đổi một lớp toàn cầu của phương pháp tĩnh thành một cái gì đó với dữ liệu "sao lưu" khác nhau hoặc một hành vi hơi khác nhau (lớp trẻ em) là một nỗi đau lớn ở mông.

Và các phương pháp tĩnh không có lợi thế tương tự.

Vì vậy, có, chúng rất tệ.

9

Điều này thực sự chỉ là sự theo dõi câu trả lời tuyệt vời của John Millikin.


Mặc dù có thể an toàn để thực hiện các phương pháp không trạng thái (có nhiều chức năng) tĩnh, đôi khi có thể dẫn đến khớp nối khó sửa đổi. Hãy xem xét bạn có một phương pháp tĩnh như vậy:

public class StaticClassVersionOne { 
    public static void doSomeFunkyThing(int arg); 
} 

nào bạn gọi như:

StaticClassVersionOne.doSomeFunkyThing(42); 

Đó là tất cả tốt và tốt, và rất thuận tiện, cho đến khi bạn gặp một trường hợp bạn phải thay đổi hành vi của phương pháp tĩnh và thấy rằng bạn bị ràng buộc chặt chẽ với StaticClassVersionOne. Có thể bạn có thể sửa đổi mã và nó sẽ là tốt, nhưng nếu có người gọi khác phụ thuộc vào hành vi cũ, họ sẽ cần phải được tính toán trong cơ thể của phương pháp. Trong một số trường hợp, thân phương pháp có thể trở nên khá xấu hoặc không thể duy trì được nếu nó cố gắng cân bằng tất cả các hành vi này. Nếu bạn tách ra các phương pháp, bạn có thể phải sửa đổi mã ở một số nơi để tính đến nó, hoặc thực hiện cuộc gọi đến các lớp mới. Hãy xem xét nếu bạn đã tạo ra một giao diện để cung cấp phương thức, và đưa nó cho người gọi, bây giờ khi hành vi phải thay đổi, một lớp mới có thể được tạo ra để thực hiện giao diện, sạch hơn, dễ dàng kiểm tra hơn, và dễ bảo trì hơn, và thay vào đó được trao cho người gọi. Trong trường hợp này, các lớp gọi không cần phải được thay đổi hoặc thậm chí biên dịch lại, và các thay đổi được bản địa hóa.

Có thể đây không phải là tình huống có thể xảy ra, nhưng tôi nghĩ điều đó đáng xem xét.

+4

Tôi cho rằng đây không chỉ là một kịch bản có khả năng, điều này làm cho statics là phương sách cuối cùng. Statics khiến TDD trở thành cơn ác mộng. Bất cứ nơi nào bạn sử dụng tĩnh, bạn không thể giả lập, bạn phải biết đầu vào và đầu ra là gì để kiểm tra một lớp không liên quan. Bây giờ, nếu bạn thay đổi hành vi của tĩnh, các bài kiểm tra của bạn trên các lớp không liên quan sử dụng tĩnh đó sẽ bị hỏng. Ngoài ra, nó sẽ trở thành một phụ thuộc ẩn mà bạn không thể vượt qua trên các nhà xây dựng để thông báo cho các nhà phát triển của một phụ thuộc có khả năng quan trọng. –

2

Tôi cho rằng đó là mùi thiết kế. Nếu bạn thấy mình sử dụng hầu hết các phương pháp tĩnh, có thể bạn không có thiết kế OO rất tốt. Nó không nhất thiết là xấu, nhưng như với tất cả các mùi nó sẽ làm cho tôi dừng lại và đánh giá lại. Nó gợi ý rằng bạn có thể làm cho một thiết kế OO tốt hơn, hoặc có thể bạn nên đi theo hướng khác và tránh OO hoàn toàn cho vấn đề này.

4

Lớp tĩnh miễn là chúng được sử dụng ở đúng nơi.

Cụ thể: Phương pháp là phương pháp 'lá' (chúng không sửa đổi trạng thái, chúng chỉ biến đổi đầu vào bằng cách nào đó). Ví dụ tốt về điều này là những thứ như Path.Combine. Những thứ này hữu ích và tạo ra cú pháp terser.

Các vấn đề tôi có với tĩnh rất nhiều:

Thứ nhất, nếu bạn có các lớp học tĩnh, phụ thuộc là ẩn. Hãy xem xét những điều sau đây:

public static class ResourceLoader 
{ 
    public static void Init(string _rootPath) { ... etc. } 
    public static void GetResource(string _resourceName) { ... etc. } 
    public static void Quit() { ... etc. } 
} 

public static class TextureManager 
{ 
    private static Dictionary<string, Texture> m_textures; 

    public static Init(IEnumerable<GraphicsFormat> _formats) 
    { 
     m_textures = new Dictionary<string, Texture>(); 

     foreach(var graphicsFormat in _formats) 
     { 
       // do something to create loading classes for all 
       // supported formats or some other contrived example! 
     } 
    } 

    public static Texture GetTexture(string _path) 
    { 
     if(m_textures.ContainsKey(_path)) 
      return m_textures[_path]; 

     // How do we know that ResourceLoader is valid at this point? 
     var texture = ResourceLoader.LoadResource(_path); 
     m_textures.Add(_path, texture); 
     return texture; 
    } 

    public static Quit() { ... cleanup code }  
} 

Nhìn vào TextureManager, bạn không thể biết những bước khởi tạo nào phải được thực hiện bằng cách xem hàm tạo. Bạn phải đào sâu vào lớp để tìm phụ thuộc của nó và khởi tạo mọi thứ theo đúng thứ tự. Trong trường hợp này, nó cần ResourceLoader được khởi tạo trước khi chạy. Bây giờ mở rộng cơn ác mộng phụ thuộc này và bạn có thể đoán điều gì sẽ xảy ra. Hãy tưởng tượng cố gắng duy trì mã khi không có thứ tự khởi tạo rõ ràng. Tương phản điều này với tiêm phụ thuộc với các trường hợp - trong trường hợp đó mã sẽ không ngay cả biên dịch nếu các phụ thuộc không được đáp ứng!

Hơn nữa, nếu bạn sử dụng các số liệu thống kê sửa đổi trạng thái, nó giống như một ngôi nhà của thẻ. Bạn không bao giờ biết ai có quyền truy cập vào cái gì, và thiết kế có xu hướng giống với một con quái vật spaghetti.

Cuối cùng, và cũng quan trọng hơn, việc sử dụng các thống kê liên kết một chương trình với một triển khai cụ thể. Mã tĩnh là phản đối của thiết kế cho testability. Mã thử nghiệm bị thủng với statics là một cơn ác mộng. Một cuộc gọi tĩnh không bao giờ có thể được hoán đổi cho một phép thử kép (trừ khi bạn sử dụng các khuôn khổ thử nghiệm được thiết kế đặc biệt để loại bỏ các kiểu tĩnh), vì vậy một hệ thống tĩnh làm cho mọi thứ sử dụng nó trở thành một thử nghiệm tích hợp ngay lập tức.

Tóm lại, các số liệu thống kê là tốt cho một số thứ và đối với các công cụ nhỏ hoặc mã số tôi sẽ không khuyến khích sử dụng chúng. Tuy nhiên, ngoài ra, họ là một cơn ác mộng đẫm máu để bảo trì, thiết kế tốt và dễ dàng kiểm tra.

Dưới đây là một bài viết tốt về các vấn đề: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/

1

Nếu đó là một phương pháp hữu ích, nó là tốt đẹp để làm cho nó tĩnh. Ổi và Apache Commons được xây dựng theo nguyên tắc này.

Ý kiến ​​của tôi về điều này hoàn toàn thực dụng. Nếu đó là mã ứng dụng của bạn, các phương pháp tĩnh thường không phải là điều tốt nhất để có. Các phương thức tĩnh có các giới hạn kiểm thử đơn vị nghiêm trọng - chúng không thể dễ dàng bị nhạo báng: bạn không thể tiêm một hàm tĩnh giả vào một số thử nghiệm khác. Bạn cũng không thể thường xuyên tiêm chức năng vào một phương thức tĩnh.

Vì vậy, trong logic ứng dụng của tôi, tôi thường có các cuộc gọi phương thức giống như tiện ích tĩnh nhỏ. I E.

static cutNotNull(String s, int length){ 
    return s == null ? null : s.substring(0, length); 
} 

một trong những lợi ích là tôi không kiểm tra các phương pháp như vậy :-)

1

Vâng, không có viên đạn bạc của khóa học. Các lớp tĩnh là ok cho các tiện ích/trợ giúp nhỏ. Nhưng sử dụng các phương pháp tĩnh để lập trình logic kinh doanh chắc chắn là điều ác. Xét đoạn mã sau

public class BusinessService 
    { 

     public Guid CreateItem(Item newItem, Guid userID, Guid ownerID) 
     { 
      var newItemId = itemsRepository.Create(createItem, userID, ownerID); 
      **var searchItem = ItemsProcessor.SplitItem(newItem);** 
      searchRepository.Add(searchItem); 
      return newItemId; 
     } 
    } 

Bạn thấy một phương pháp gọi tĩnh để ItemsProcessor.SplitItem(newItem); Nó có mùi gây

  • Bạn không có phụ thuộc rõ ràng tuyên bố và nếu bạn không thâm nhập vào mã bạn có thể bỏ qua các khớp nối giữa bạn thùng chứa phương thức tĩnh và lớp
  • Bạn không thể kiểm tra BusinessService cách ly nó khỏi ItemsProcessor (hầu hết các công cụ kiểm tra không giả lập các lớp tĩnh) và nó làm cho việc kiểm thử đơn vị không thể. Không có bài kiểm tra đơn vị == chất lượng thấp
0

Phương pháp tĩnh thường là lựa chọn không tốt ngay cả đối với mã không quốc tịch. Thay vào đó hãy tạo một lớp singleton với các phương thức này được khởi tạo một lần và được tiêm vào các lớp đó để sử dụng các phương thức. Các lớp như vậy dễ dàng hơn để thử và thử nghiệm. Họ có nhiều hướng đối tượng hơn. Bạn có thể bọc chúng với một proxy khi cần thiết. Thống kê làm cho OO trở nên khó hơn và tôi không thấy lý do gì để sử dụng chúng trong hầu hết các trường hợp. Không phải 100% nhưng hầu như tất cả.

Các vấn đề liên quan