Phương thức cờ làm đối số hoặc là biến thành viên?


8

Tôi nghĩ tiêu đề "Phương thức cờ là đối số hoặc là biến thành viên?" có thể là tối ưu, nhưng vì tôi thiếu bất kỳ thuật ngữ nào tốt hơn, nên ở đây:

Hiện tại tôi đang cố gắng giải quyết vấn đề liệu các cờ cho một phương thức ( riêng tư ) nhất định có được chuyển qua làm đối số hàm hay thông qua biến thành viên hay không và liệu có một mẫu hoặc tên nào đó bao hàm khía cạnh này và / hoặc cho dù điều này gợi ý ở một số vấn đề thiết kế khác.

Ví dụ (ngôn ngữ có thể là C ++, Java, C #, không thực sự quan trọng IMHO):

class Thingamajig {
  private ResultType DoInternalStuff(FlagType calcSelect) {
    ResultType res;
    for (... some loop condition ...) {
      ...
      if (calcSelect == typeA) {
        ...
      } else if (calcSelect == typeX) {
        ...
      } else if ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker(FlagType calcSelect) {
    ...
    DoInternalStuff(calcSelect);
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    InternalStuffInvoker(typeA);
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    ResultType x = DoInternalStuff(typeX);
    ... some more code ... further process x ...
    return x;
  }
}

Những gì chúng ta thấy ở trên là phương thức InternalStuffInvokernày có một đối số hoàn toàn không được sử dụng bên trong hàm này mà chỉ được chuyển tiếp sang phương thức riêng tư khác DoInternalStuff. (Trường hợp DoInternalStuffsẽ được sử dụng riêng ở những nơi khác trong lớp này, ví dụ: trong DoThatStuffphương thức (công khai).)

Một giải pháp thay thế sẽ là thêm một biến thành viên mang thông tin này:

class Thingamajig {
  private ResultType DoInternalStuff() {
    ResultType res;
    for (... some loop condition ...) {
      ...
      if (m_calcSelect == typeA) {
        ...
      } ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker() {
    ...
    DoInternalStuff();
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    m_calcSelect = typeA;
    InternalStuffInvoker();
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    m_calcSelect = typeX;
    ResultType x = DoInternalStuff();
    ... some more code ... further process x ...
    return x;
  }
}

Đặc biệt đối với các chuỗi cuộc gọi sâu nơi cờ chọn cho phương thức bên trong được chọn bên ngoài, sử dụng biến thành viên có thể làm cho các hàm trung gian sạch hơn, vì chúng không cần phải mang tham số truyền qua.

Mặt khác, biến thành viên này không thực sự đại diện cho bất kỳ trạng thái đối tượng nào (vì nó không được đặt cũng không có sẵn bên ngoài), nhưng thực sự là một đối số bổ sung ẩn cho phương thức riêng tư "bên trong".

Những ưu và nhược điểm của từng phương pháp là gì?


1
Hãy xem xét rằng nếu bạn có nhu cầu chuyển cờ xung quanh, bạn có thể có một loạt các chức năng thực hiện nhiều hơn một việc. Bạn có thể xem xét điều chỉnh cách thức điều khiển chảy, do đó bạn sẽ không cần làm xáo trộn nó bằng cờ.
cHao

Tôi cũng muốn đề cập đến, "DoI INTERNalStuff" và "InternalStuff invoker" là những cái tên thực sự nhảm nhí. :) Vâng, tôi biết đó chỉ là một ví dụ, nhưng thật đáng ngại. Nếu mã thực sự của bạn làm bất cứ điều gì mơ hồ như "nội bộ", thì đó là một vấn đề lớn hơn. Sắp xếp nó ra, và một số cách đơn giản hóa luồng điều khiển có nhiều khả năng tiết lộ chính nó.
cHao

1
Tôi đồng ý với @cHao - nơi trước đây tôi từng gặp tình huống như vậy, cờ tùy chọn thường xuyên được thông qua là một mùi mã mà nguyên tắc trách nhiệm duy nhất đã bị vi phạm.
Bevan

Câu trả lời:


15

Mặt khác, biến thành viên này không thực sự đại diện cho bất kỳ trạng thái đối tượng nào (vì nó không được đặt cũng không có sẵn bên ngoài), nhưng thực sự là một đối số bổ sung ẩn cho phương thức riêng tư "bên trong".

Điều đó củng cố cảm giác ruột của tôi khi đọc câu hỏi của bạn: cố gắng giữ dữ liệu này càng cục bộ càng tốt, tức là đừng thêm nó vào trạng thái đối tượng . Điều này làm giảm không gian trạng thái của lớp của bạn, làm cho mã của bạn đơn giản hơn để hiểu, kiểm tra và duy trì. Chưa kể đến việc tránh trạng thái chia sẻ cũng khiến lớp của bạn trở nên an toàn hơn - điều này có thể hoặc không phải là mối quan tâm của bạn ngay bây giờ, nhưng nó có thể tạo ra sự khác biệt lớn trong tương lai.

Tất nhiên, nếu bạn cần vượt qua những lá cờ như vậy quá mức, nó có thể trở thành một mối phiền toái. Trong trường hợp này, bạn có thể xem xét

  • tạo các đối tượng tham số để nhóm các tham số liên quan lại với nhau thành một đối tượng và / hoặc
  • gói gọn trạng thái này và logic liên quan vào một nhóm chiến lược riêng biệt . Khả năng tồn tại của điều này phụ thuộc vào ngôn ngữ: trong Java, bạn cần xác định một giao diện riêng và lớp triển khai (có thể ẩn danh) cho mỗi chiến lược, trong khi trong C ++ hoặc C #, bạn có thể sử dụng con trỏ hàm, đại biểu hoặc lambdas, để đơn giản hóa mã đáng kể

Cảm ơn đã đề cập đến cách tiếp cận chiến lược / lambda. Tôi sẽ phải giữ giải pháp đó xung quanh khi mọi thứ cần được bật ra :-)
Martin Ba

6

Tôi chắc chắn sẽ bỏ phiếu cho tùy chọn "đối số" - lý do của tôi:

  1. Đồng bộ hóa - điều gì xảy ra nếu phương thức được gọi từ nhiều luồng cùng một lúc? Bằng cách chuyển các cờ làm đối số, bạn sẽ không phải đồng bộ hóa quyền truy cập vào các cờ này.

  2. Khả năng kiểm tra - Hãy tưởng tượng bạn đang viết một bài kiểm tra đơn vị cho lớp của bạn. Làm thế nào bạn sẽ kiểm tra trạng thái nội bộ của thành viên? Điều gì xảy ra nếu mã của bạn ném một ngoại lệ và thành viên sẽ kết thúc trong trạng thái không mong muốn?

  3. Tách các mối quan tâm - Bằng cách thêm các cờ vào các đối số phương thức của bạn, rõ ràng là không có phương pháp nào khác được sử dụng. Bạn sẽ không sử dụng nó do nhầm lẫn trong một số phương pháp khác.


3

Sự thay thế đầu tiên có vẻ ổn với tôi. Một hàm được gọi với một tham số và nó thực hiện một cái gì đó phụ thuộc vào tham số đó . Ngay cả khi bạn chỉ chuyển tiếp tham số đến một chức năng khác, bạn vẫn hy vọng rằng kết quả sẽ bằng cách nào đó phụ thuộc vào tham số.

Sự thay thế thứ hai cảm thấy giống như sử dụng một biến toàn cục, chỉ trong phạm vi lớp thay vì phạm vi ứng dụng. Nhưng đó là cùng một vấn đề ... bạn phải đọc kỹ toàn bộ mã lớp để xác định ai và khi nào đang đọc và viết các giá trị đó.

Do đó, sự thay thế đầu tiên chiến thắng.

Nếu bạn sử dụng quá nhiều tham số (đọc: hai hoặc nhiều hơn) trong phương án đầu tiên, tất cả chỉ được chuyển cùng nhau sang chức năng tiếp theo, bạn có thể xem xét đưa chúng vào một đối tượng (biến nó thành một lớp bên trong, nếu nó không phù hợp với phần còn lại của ứng dụng) và sử dụng đối tượng này làm tham số.


1

Tôi muốn nói "không".

Trước hết, hãy xem xét rằng bạn đã biết những gì bạn sẽ làm khi bạn gọi DoInternalStuff. Lá cờ nói càng nhiều. Vì vậy, bây giờ, thay vì chỉ tiếp tục và thực hiện nó, bạn đang xì hơi gọi một chức năng mà bây giờ phải quyết định phải làm gì.

Nếu bạn muốn nói cho hàm biết phải làm gì, thì hãy nói cho nó biết phải làm gì . Bạn có thể truyền một hàm thực tế mà bạn muốn chạy cho mỗi lần lặp dưới dạng đại biểu C # (hoặc đối tượng hàm C ++ hoặc chạy được Java hoặc tương tự).

Trong C #:

class Thingamajig {
  private delegate WhateverType Behavior(ArgsType args);

  private ResultType DoInternalStuff(Behavior behavior) {
    ResultType res;
    for (... some loop condition ...) {
      ...
      var subResult = behavior(someArgs);
      ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker(Behavior behavior) {
    ...
    DoInternalStuff(behavior);
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    InternalStuffInvoker(doThisStuffPerIteration);
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    ResultType x = DoInternalStuff(doThatStuffPerIteration);
    ... some more code ... further process x ...
    return x;
  }
}

Bạn vẫn kết thúc với cuộc tranh cãi ở khắp mọi nơi, nhưng bạn thoát khỏi hầu hết các if / other tào lao DoInternalStuff. Bạn cũng có thể lưu trữ đại biểu dưới dạng một trường nếu bạn muốn.

Còn về việc có nên chuyển todo-thingie xung quanh hay cất giữ nó không ... nếu bạn phải chọn một thứ, thì hãy chuyển nó đi. Lưu trữ nó có khả năng sẽ cắn vào mông bạn sau này, vì bạn đã bỏ bê khá nhiều vấn đề an toàn. Hãy xem xét rằng nếu bạn có ý định làm bất cứ điều gì với các chủ đề, thì bây giờ bạn phải khóa trong toàn bộ thời gian của vòng lặp, hoặc ai đó có thể chuyển sang làm ngay từ bên dưới bạn. Trong khi đó, nếu bạn vượt qua nó, nó sẽ ít bị hỏng hơn ... và nếu bạn vẫn cần khóa hoặc một cái gì đó, bạn có thể làm điều đó cho một phần của một lần lặp thay vì toàn bộ vòng lặp.

Thành thật mà nói, thật tốt khi nó gây phiền nhiễu. Có lẽ có một cách đơn giản hơn nhiều để làm những gì bạn muốn làm, điều mà tôi thực sự không thể khuyên bạn vì mã rõ ràng không phải là thực tế. Câu trả lời tốt nhất khác nhau cho mỗi trường hợp.

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.