Đó là một thực tiễn tốt hơn - phương thức trợ giúp như thể hiện hoặc tĩnh?


48

Câu hỏi này là chủ quan nhưng tôi chỉ tò mò làm thế nào hầu hết các lập trình viên tiếp cận điều này. Mẫu bên dưới là giả hành C # nhưng điều này cũng nên áp dụng cho Java, C ++ và các ngôn ngữ OOP khác.

Dù sao, khi viết các phương thức của trình trợ giúp trong các lớp của tôi, tôi có xu hướng khai báo chúng là tĩnh và chỉ chuyển các trường nếu phương thức của trình trợ giúp cần chúng. Ví dụ, được cung cấp mã bên dưới, tôi thích sử dụng Phương thức gọi số 2 .

class Foo
{
  Bar _bar;

  public void DoSomethingWithBar()
  {
    // Method Call #1.
    DoSomethingWithBarImpl();

    // Method Call #2.
    DoSomethingWithBarImpl(_bar);
  }

  private void DoSomethingWithBarImpl()
  {
    _bar.DoSomething();
  }

  private static void DoSomethingWithBarImpl(Bar bar)
  {
    bar.DoSomething();
  }
}

Lý do của tôi để làm điều này là vì nó cho thấy rõ ràng (ít nhất là trong mắt tôi) rằng phương thức của trình trợ giúp có tác dụng phụ có thể có đối với các đối tượng khác - ngay cả khi không đọc triển khai của nó. Tôi thấy rằng tôi có thể nhanh chóng tìm kiếm các phương pháp sử dụng thực hành này và do đó giúp tôi gỡ lỗi mọi thứ.

Bạn thích làm gì trong mã của riêng bạn và lý do của bạn để làm như vậy là gì?


3
Tôi sẽ loại bỏ C ++ khỏi câu hỏi này: trong C ++, rõ ràng, bạn sẽ biến nó thành một phương thức miễn phí, bởi vì không có điểm nào cho nó bị tùy tiện đâm vào một đối tượng: nó vô hiệu hóa ADL.
Matthieu M.

1
@MatthieuM.: Phương pháp miễn phí hay không, không thành vấn đề. Mục đích của câu hỏi của tôi là hỏi liệu có bất kỳ lợi thế nào trong việc khai báo các phương thức của trình trợ giúp như là các ví dụ hay không. Vì vậy, trong C ++, sự lựa chọn có thể là phương thức cá thể so với phương thức / hàm miễn phí. Bạn đã có một điểm tốt về ADL. Vì vậy, bạn đang nói rằng bạn thích sử dụng các phương pháp miễn phí? Cảm ơn!
Ilian

2
Đối với C ++, các phương pháp miễn phí được khuyến nghị. Chúng làm tăng sự đóng gói (một cách gián tiếp, vì chỉ có thể truy cập vào giao diện công cộng) và vẫn chơi tốt vì ADL (đặc biệt là trong các mẫu). Tuy nhiên tôi không biết điều này có áp dụng được với Java / C # hay không, C ++ khác rất nhiều so với Java / C # vì vậy tôi hy vọng các câu trả lời sẽ khác nhau (và do đó tôi không nghĩ việc hỏi 3 ngôn ngữ cùng nhau có ý nghĩa gì).
Matthieu M.


1
Không cố làm cho ai thêm trầm trọng, tôi chưa bao giờ nghe một lý do chính đáng nào để không sử dụng các phương pháp tĩnh. Tôi sử dụng chúng bất cứ nơi nào tôi có thể vì chúng làm cho nó rõ ràng hơn những gì phương pháp làm.
Sridhar Sarnobat

Câu trả lời:


23

Điều này thực sự phụ thuộc. Nếu các giá trị mà trình trợ giúp của bạn hoạt động là nguyên thủy, thì các phương thức tĩnh là một lựa chọn tốt, như Péter đã chỉ ra.

Nếu họ rất phức tạp, sau đó RẮN áp dụng, cụ thể hơn là S thì tôiD .

Thí dụ:

class CookieJar {
      function takeCookies(count:Int):Array<Cookie> { ... }
      function countCookies():Int { ... }
      function ressuplyCookies(cookies:Array<Cookie>
      ... // lot of stuff we don't care about now
}

class CookieFan {
      function getHunger():Float;
      function eatCookies(cookies:Array<Cookie>):Smile { ... }
}

class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieJar;
      function makeEveryBodyAsHappyAsPossible():Void {
           //perform a lot of operations on jake, jane and the cookies
      }
      public function cookieTime():Void {
           makeEveryBodyAsHappyAsPossible();
      }
}

Đây sẽ là về vấn đề của bạn. Bạn có thể tạo makeEveryBodyAsHappyAsPossiblemột phương thức tĩnh, sẽ lấy các tham số cần thiết. Một lựa chọn khác là:

interface CookieDistributor {
    function distributeCookies(to:Array<CookieFan>):Array<Smile>;
}
class HappynessMaximizingDistributor implements CookieDistributor {
    var jar:CookieJar;
    function distributeCookies(to:Array<CookieFan>):Array<Smile> {
         //put the logic of makeEveryBodyAsHappyAsPossible here
    }
}
//and make a change here
class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieDistributor;

      public function cookieTime():Void {
           cookies.distributeCookies([jake, jane]);
      }
}

Bây giờ OurHousekhông cần biết về sự phức tạp của các quy tắc phân phối cookie. Nó chỉ phải bây giờ là một đối tượng, trong đó thực hiện một quy tắc. Việc thực hiện được trừu tượng hóa thành một đối tượng, trách nhiệm duy nhất của ai là áp dụng quy tắc. Đối tượng này có thể được thử nghiệm trong sự cô lập. OurHousecó thể được kiểm tra bằng cách sử dụng một mock đơn thuần của CookieDistributor. Và bạn có thể dễ dàng quyết định thay đổi quy tắc phân phối cookie.

Tuy nhiên, hãy cẩn thận rằng bạn không làm quá. Ví dụ, có một hệ thống phức tạp gồm 30 lớp đóng vai trò là việc thực hiện CookieDistributor, trong đó mỗi lớp chỉ hoàn thành một nhiệm vụ nhỏ, không thực sự có ý nghĩa. Giải thích của tôi về SRP là nó không chỉ ra lệnh rằng mỗi lớp chỉ có thể có một trách nhiệm, mà còn một trách nhiệm duy nhất nên được thực hiện bởi một lớp duy nhất.

Trong trường hợp nguyên thủy hoặc đối tượng bạn sử dụng như nguyên thủy (ví dụ các đối tượng đại diện cho các điểm trong không gian, ma trận hoặc một cái gì đó), các lớp trợ giúp tĩnh có rất nhiều ý nghĩa. Nếu bạn có sự lựa chọn và nó thực sự có ý nghĩa, thì bạn thực sự có thể xem xét việc thêm một phương thức vào lớp đại diện cho dữ liệu, ví dụ như Pointviệc có một addphương thức là hợp lý . Một lần nữa, đừng lạm dụng nó.

Vì vậy, tùy thuộc vào vấn đề của bạn, có nhiều cách khác nhau để đi về nó.


18

Đây là một thành ngữ nổi tiếng để khai báo các phương thức của các lớp tiện ích static, vì vậy các lớp như vậy không cần phải được khởi tạo. Theo thành ngữ này làm cho mã của bạn dễ hiểu hơn, đó là một điều tốt.

Mặc dù có một hạn chế nghiêm trọng đối với phương pháp này: các phương thức / lớp như vậy không thể bị chế giễu một cách dễ dàng (mặc dù AFAIK ít nhất là đối với C # có các khung mô phỏng có thể đạt được ngay cả điều này, nhưng chúng không phổ biến và ít nhất là một số trong số chúng thương mại). Vì vậy, nếu một phương thức của trình trợ giúp có bất kỳ sự phụ thuộc bên ngoài nào (ví dụ DB) làm cho nó - do đó người gọi của nó - khó kiểm tra đơn vị, tốt hơn là khai báo nó không tĩnh . Điều này cho phép tiêm phụ thuộc, do đó làm cho người gọi phương thức dễ dàng kiểm tra đơn vị hơn.

Cập nhật

Làm rõ: phần trên nói về các lớp tiện ích, chỉ chứa các phương thức của trình trợ giúp cấp thấp và (thường) không có trạng thái. Các phương thức trợ giúp bên trong các lớp không tiện ích có trạng thái là một vấn đề khác nhau; xin lỗi vì đã hiểu sai về OP.

Trong trường hợp này, tôi cảm nhận được mùi mã: một phương thức của lớp A hoạt động chủ yếu trên một thể hiện của lớp B thực sự có thể có một vị trí tốt hơn trong lớp B. Nhưng nếu tôi quyết định giữ nó ở nơi đó, tôi thích tùy chọn 1 hơn, vì nó đơn giản và dễ đọc hơn.


Bạn có thể chuyển phụ thuộc vào phương thức trợ giúp tĩnh không?
Ilian

1
@JackOf ALLTrades, nếu mục đích duy nhất của phương pháp là để đối phó với một nguồn tài nguyên bên ngoài, hầu như không có bất kỳ điểm nào phụ thuộc vào sự phụ thuộc đó. Mặt khác, nó có thể là một giải pháp (mặc dù trong trường hợp sau, phương thức này có thể phá vỡ Nguyên tắc Trách nhiệm duy nhất, do đó là một ứng cử viên để tái cấu trúc).
Péter Török

1
số liệu thống kê dễ dàng bị 'chế giễu' - Microsoft Moles hoặc Fakes (tùy thuộc, VS2010 hoặc VS2012 +) cho phép bạn thay thế một phương thức tĩnh bằng cách thực hiện trong các thử nghiệm của riêng bạn. Làm cho các khung chế nhạo cũ trở nên lỗi thời. (nó cũng thực hiện các mô phỏng truyền thống, và cũng có thể chế nhạo các lớp bê tông). Nó miễn phí từ MS thông qua quản lý mở rộng.
gbjbaanb

4

Bạn thích làm gì trong mã của riêng bạn

Tôi thích # 1 ( this->DoSomethingWithBarImpl();), trừ khi tất nhiên phương thức của trình trợ giúp không cần truy cập vào dữ liệu / triển khai của cá thể static t_image OpenDefaultImage().

và lý do của bạn để làm như vậy là gì?

giao diện công cộng và thực hiện riêng tư và các thành viên là riêng biệt. các chương trình sẽ khó đọc hơn nhiều nếu tôi chọn # 2. với # 1, tôi luôn có sẵn các thành viên và ví dụ. so với nhiều triển khai dựa trên OO, tôi có xu hướng sử dụng rất nhiều đóng gói và rất ít thành viên mỗi lớp. theo như tác dụng phụ - tôi ổn với điều đó, thường có xác nhận trạng thái mở rộng các phụ thuộc (ví dụ: đối số người ta sẽ cần phải vượt qua).

# 1 đơn giản hơn nhiều để đọc, duy trì và có những đặc điểm hiệu suất tốt. Tất nhiên, sẽ có trường hợp bạn có thể chia sẻ triển khai:

static void ValidateImage(const t_image& image);
void validateImages() const {
    ValidateImage(this->innerImage());
    ValidateImage(this->outerImage());
}

Nếu # 2 là một giải pháp phổ biến tốt (ví dụ như mặc định) trong một cơ sở mã, tôi sẽ lo ngại về cấu trúc của thiết kế: các lớp có làm quá nhiều không? không đủ đóng gói hay không đủ tái sử dụng? mức độ trừu tượng có đủ cao không?

cuối cùng, # 2 có vẻ dư thừa nếu bạn đang sử dụng các ngôn ngữ OO nơi hỗ trợ đột biến (ví dụ: một constphương thứ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.