Phong cách nào tốt hơn (Biến thể thay đổi so với giá trị trả về) trong Java


32

Tôi thường thấy mình phải vật lộn để quyết định sử dụng cách nào trong hai cách này khi tôi yêu cầu sử dụng dữ liệu chung trên một số phương thức trong các lớp học của mình. Điều gì sẽ là một lựa chọn tốt hơn?

Trong tùy chọn này, tôi có thể tạo một biến đối tượng để tránh phải khai báo các biến bổ sung và cũng để tránh xác định các tham số phương thức, nhưng có thể không rõ ràng về những biến đó đang được khởi tạo / sửa đổi:

public class MyClass {
    private int var1;

    MyClass(){
        doSomething();
        doSomethingElse();
        doMoreStuff();
    }

    private void doSomething(){
        var1 = 2;
    }

    private void doSomethingElse(){
        int var2 = var1 + 1;
    }

    private void doMoreStuff(){
        int var3 = var1 - 1;
    }
}

Hoặc chỉ khởi tạo các biến cục bộ và chuyển chúng thành đối số?

public class MyClass {  
    MyClass(){
        int var1 = doSomething();
        doSomethingElse(var1);
        doMoreStuff(var1);
    }

    private int doSomething(){
        int var = 2;
        return var;
    }

    private void doSomethingElse(int var){
        int var2 = var + 1;
    }

    private void doMoreStuff(int var){
        int var3 = var - 1;
    }
}

Nếu câu trả lời là cả hai đều đúng, cái nào được nhìn thấy / sử dụng thường xuyên hơn? Ngoài ra, nếu bạn có thể cung cấp các ưu / nhược điểm bổ sung cho mỗi tùy chọn sẽ rất có giá trị.


1
Reletad (trùng lặp?): Lập trình
Martin Ba

1
Tôi không nghĩ rằng ai đó đã chỉ ra rằng việc đưa các kết quả trung gian vào các biến thể hiện có thể làm cho việc đồng thời trở nên khó khăn hơn, vì khả năng tranh chấp giữa các luồng cho các biến này.
sdenham

Câu trả lời:


107

Tôi ngạc nhiên khi điều này chưa được đề cập ...

Nó phụ thuộc nếu var1thực sự là một phần của trạng thái đối tượng của bạn .

Bạn cho rằng cả hai cách tiếp cận này đều đúng và đó chỉ là vấn đề về phong cách. Bạn sai rồi.

Đây là hoàn toàn về làm thế nào để mô hình đúng.

Tương tự, privatecác phương thức cá thể tồn tại để thay đổi trạng thái của đối tượng của bạn . Nếu đó không phải là những gì phương pháp của bạn đang làm thì nó nên được private static.


7
@mucaho: transientkhông liên quan gì đến vấn đề này, vì đó transientlà về trạng thái liên tục , như trong các phần của đối tượng được lưu khi bạn làm một cái gì đó như tuần tự hóa đối tượng. Ví dụ: cửa hàng sao lưu của ArrayList transientmặc dù nó hoàn toàn quan trọng đối với trạng thái của ArrayList, bởi vì khi bạn tuần tự hóa một ArrayList, bạn chỉ muốn lưu một phần của cửa hàng sao lưu chứa các phần tử ArrayList thực tế, không phải là không gian trống ở cuối dành cho bổ sung yếu tố hơn nữa.
user2357112 hỗ trợ Monica

4
Thêm vào câu trả lời - nếu var1cần thiết cho một vài phương thức nhưng không phải là một phần của trạng thái MyClass, có lẽ đã đến lúc đưa var1và các phương thức đó vào một lớp khác được sử dụng MyClass.
Mike Partridge

5
@CandiedOrange Chúng ta đang nói về mô hình chính xác ở đây. Khi chúng ta nói "một phần trạng thái của đối tượng", chúng ta sẽ không nói về các phần nghĩa đen trong mã. Chúng tôi đang thảo luận một khái niệm khái niệm về nhà nước, những gì nên được trạng thái của đối tượng để mô hình hóa các khái niệm đúng. "Tuổi thọ hữu ích" có thể là yếu tố quyết định lớn nhất trong đó.
jpmc26

4
@CandiedOrange Tiếp theo và Trước đó rõ ràng là các phương thức công khai. Nếu giá trị của var1 chỉ có liên quan trong một chuỗi các phương thức riêng tư thì rõ ràng nó không phải là một phần của trạng thái bền bỉ của đối tượng và do đó nên được chuyển qua làm đối số.
Taemyr

2
@CandiedOrange Bạn đã làm rõ ý của bạn, sau hai bình luận. Tôi đang nói rằng bạn dễ dàng có thể có trong lần trả lời đầu tiên. Ngoài ra, yeah, tôi quên rằng không chỉ có đối tượng; Tôi đồng ý rằng đôi khi các phương thức cá nhân có một mục đích. Tôi không thể mang bất kỳ suy nghĩ nào. EDIT: Tôi chỉ nhận ra rằng bạn không phải là người đầu tiên trả lời tôi. Lỗi của tôi. Tôi đã bối rối về lý do tại sao một câu trả lời là một câu cộc lốc, một câu, không trả lời, trong khi câu trả lời thực sự tiếp theo.
Vụ kiện của Quỹ Monica

17

Tôi không biết cái nào phổ biến hơn, nhưng tôi sẽ luôn làm cái sau. Nó truyền đạt rõ ràng hơn luồng dữ liệu và thời gian tồn tại và nó không làm phình to mọi trường hợp của lớp bạn với một trường có thời gian duy nhất có liên quan là trong quá trình khởi tạo. Tôi muốn nói rằng cái trước chỉ gây nhầm lẫn và làm cho việc đánh giá mã trở nên khó khăn hơn đáng kể, bởi vì tôi phải xem xét khả năng mà bất kỳ phương thức nào cũng có thể sửa đổi var1.


7

Bạn nên giảm phạm vi của các biến của bạn càng nhiều càng tốt (và hợp lý). Không chỉ trong phương pháp, mà nói chung.

Đối với câu hỏi của bạn có nghĩa là nó phụ thuộc vào việc biến đó có phải là một phần của trạng thái của đối tượng hay không. Nếu có, bạn có thể sử dụng nó trong phạm vi đó, tức là toàn bộ đối tượng. Trong trường hợp này đi với tùy chọn đầu tiên. Nếu không, hãy chọn tùy chọn thứ hai vì nó làm giảm khả năng hiển thị của các biến và do đó độ phức tạp tổng thể.


5

Phong cách nào tốt hơn (Biến thể thay đổi so với giá trị trả về) trong Java

Có một phong cách khác - sử dụng bối cảnh / trạng thái.

public static class MyClass {
    // Hold my state from one call to the next.
    public static final class State {
        int var1;
    }

    MyClass() {
        State state = new State();
        doSomething(state);
        doSomethingElse(state);
        doMoreStuff(state);
    }

    private void doSomething(State state) {
        state.var1 = 2;
    }

    private void doSomethingElse(State state) {
        int var2 = state.var1 + 1;
    }

    private void doMoreStuff(State state) {
        int var3 = state.var1 - 1;
    }
}

Có một số lợi ích cho phương pháp này. Các đối tượng trạng thái có thể thay đổi độc lập với đối tượng chẳng hạn, tạo ra nhiều chỗ ngọ nguậy cho tương lai.

Đây là một mẫu cũng hoạt động tốt trong hệ thống phân tán / máy chủ nơi một số chi tiết phải được lưu giữ trong các cuộc gọi. Bạn có thể lưu trữ chi tiết người dùng, kết nối cơ sở dữ liệu, vv trong stateđối tượng.


3

Đó là về tác dụng phụ.

Hỏi liệu var1một phần của nhà nước bỏ lỡ điểm của câu hỏi này. Chắc chắn nếu var1phải kiên trì, nó phải là một ví dụ. Một trong hai cách tiếp cận có thể được thực hiện để làm việc cho dù sự kiên trì là cần thiết hay không.

Cách tiếp cận tác dụng phụ

Một số biến thể hiện chỉ được sử dụng để giao tiếp giữa các phương thức riêng tư từ cuộc gọi đến cuộc gọi. Loại biến thể hiện này có thể được cấu trúc lại từ sự tồn tại nhưng nó không phải như vậy. Đôi khi mọi thứ rõ ràng hơn với họ. Nhưng điều này không phải là không có rủi ro.

Bạn đang để một biến ngoài phạm vi của nó bởi vì nó được sử dụng trong hai phạm vi riêng tư khác nhau. Không phải vì nó cần thiết trong phạm vi bạn đang đặt nó. Điều này có thể gây nhầm lẫn. "Quả cầu là xấu xa!" mức độ khó hiểu. Điều này có thể làm việc nhưng nó sẽ không có quy mô tốt. Nó chỉ hoạt động trong nhỏ. Không có vật thể lớn. Không có chuỗi thừa kế dài. Đừng gây ra hiệu ứng yo yo .

Cách tiếp cận chức năng

Bây giờ, ngay cả khi var1phải kiên trì, không có gì nói rằng bạn phải sử dụng nếu với mọi giá trị nhất thời có thể xảy ra trước khi nó đạt đến trạng thái bạn muốn được bảo tồn giữa các cuộc gọi công khai. Điều đó có nghĩa là bạn vẫn có thể thiết lập một var1cá thể bằng cách sử dụng không có gì ngoài các phương thức chức năng hơn.

Vì vậy, một phần của trạng thái hay không, bạn vẫn có thể sử dụng một trong hai cách tiếp cận.

Trong các ví dụ này, 'var1' được gói gọn không có gì ngoài trình gỡ lỗi của bạn biết nó tồn tại. Tôi đoán bạn đã cố tình làm điều đó bởi vì bạn không muốn thiên vị chúng tôi. May thay tôi không quan tâm cái nào.

Nguy cơ tác dụng phụ

Điều đó nói rằng, tôi biết câu hỏi của bạn đến từ đâu. Tôi đã làm việc dưới khổ sở yo yo 'ing thừa kế mà đột biến một biến Ví dụ ở nhiều cấp độ trong nhiều phương pháp và đi squirrelly cố gắng làm theo nó. Đây là rủi ro.

Đây là nỗi đau đưa tôi đến một cách tiếp cận chức năng hơn. Một phương thức có thể ghi lại các phụ thuộc và đầu ra của nó trong chữ ký của nó. Đây là một cách tiếp cận mạnh mẽ, rõ ràng. Nó cũng cho phép bạn thay đổi những gì bạn vượt qua phương thức riêng tư làm cho nó có thể tái sử dụng nhiều hơn trong lớp.

Mặt trái của tác dụng phụ

Nó cũng hạn chế. Chức năng thuần túy không có tác dụng phụ. Đó có thể là một điều tốt nhưng nó không hướng đối tượng. Một phần lớn của định hướng đối tượng là khả năng tham chiếu đến một bối cảnh bên ngoài phương thức. Làm điều đó mà không làm rò rỉ toàn cầu khắp nơi và biến mất là thế mạnh của OOP. Tôi có được sự linh hoạt của toàn cầu nhưng nó được chứa trong lớp. Tôi có thể gọi một phương thức và biến đổi mọi biến đối tượng cùng một lúc nếu tôi thích. Nếu tôi làm điều đó, tôi bắt buộc phải đặt cho phương thức một cái tên rõ ràng để làm gì để mọi người không ngạc nhiên khi điều đó xảy ra. Bình luận cũng có thể giúp đỡ. Đôi khi những bình luận này được chính thức hóa là "điều kiện đăng".

Nhược điểm của phương pháp riêng chức năng

Cách tiếp cận chức năng làm cho một số phụ thuộc rõ ràng. Trừ khi bạn trong một ngôn ngữ chức năng thuần túy, nó không thể loại trừ các phụ thuộc ẩn. Bạn không biết, chỉ nhìn vào một chữ ký phương thức, rằng nó không che giấu một tác dụng phụ từ bạn trong phần còn lại của mã. Bạn không cần.

Bài có điều kiện

Nếu bạn và mọi người khác trong nhóm, tài liệu đáng tin cậy về các tác dụng phụ (điều kiện trước / sau) trong các bình luận, thì mức tăng từ cách tiếp cận chức năng sẽ ít hơn nhiều. Vâng tôi biết, mơ về.

Phần kết luận

Cá nhân tôi có xu hướng hướng tới các phương thức riêng tư chức năng trong bất kỳ trường hợp nào nếu tôi có thể, nhưng thành thật mà nói chủ yếu là vì những bình luận về hiệu ứng phụ có điều kiện trước / sau không gây ra lỗi trình biên dịch khi chúng bị lỗi thời hoặc khi các phương thức bị lỗi. Trừ khi tôi thực sự cần sự linh hoạt của các tác dụng phụ, tôi chỉ muốn biết rằng mọi thứ hoạt động.


1
"Một số biến đối tượng chỉ được sử dụng để giao tiếp giữa các phương thức riêng tư từ cuộc gọi đến cuộc gọi." Đó là một mùi mã. Khi các biến thể hiện được sử dụng theo cách này, đó là một dấu hiệu lớp quá lớn. Trích xuất các biến và phương thức đó vào một lớp mới.
kevin cline

2
Điều này thực sự không có ý nghĩa gì. Mặc dù OP có thể viết mã theo mô hình chức năng (và điều này thường sẽ là một điều tốt), nhưng rõ ràng đó không phải là bối cảnh của câu hỏi. Cố gắng nói với OP rằng họ có thể tránh lưu trữ trạng thái của đối tượng bằng cách thay đổi mô hình không thực sự phù hợp ...
jpmc26

@kevincline ví dụ của OP chỉ có một biến đối tượng và 3 phương thức. Về cơ bản, nó đã được trích xuất thành một lớp mới. Không phải là ví dụ làm bất cứ điều gì hữu ích. Lớp học không liên quan gì đến cách các phương thức trợ giúp riêng của bạn giao tiếp với nhau.
MagicWindow

@ jpmc26 OP đã chỉ ra rằng họ có thể tránh lưu trữ var1dưới dạng biến trạng thái bằng cách thay đổi mô hình. Phạm vi lớp KHÔNG chỉ là nơi lưu trữ trạng thái. Nó cũng là một phạm vi kèm theo. Điều đó có nghĩa là có hai động lực có thể để đặt một biến ở cấp độ lớp. Bạn có thể yêu cầu làm việc đó chỉ dành cho các phạm vi bao quanh và không nhà nước là ác nhưng tôi nói đó là một đánh đổi với arity đó cũng là điều ác. Tôi biết điều này bởi vì tôi đã phải duy trì mã thực hiện điều này. Một số đã được thực hiện tốt. Một số là một cơn ác mộng. Ranh giới giữa hai trạng thái không. Đó là khả năng đọc.
MagicWindow

Quy tắc trạng thái tăng cường khả năng đọc: 1) tránh có kết quả tạm thời của các hoạt động trông giống như trạng thái 2) tránh che giấu sự phụ thuộc của các phương thức. Nếu tính chất của một phương thức cao, thì nó có thể thể hiện một cách không thể tin được và thực sự đại diện cho sự phức tạp của phương pháp đó hoặc thiết kế hiện tại có độ phức tạp không cần thiết.
sdenham

1

Biến thể đầu tiên trông không trực quan và có khả năng gây nguy hiểm cho tôi (hãy tưởng tượng vì bất kỳ lý do gì ai đó làm cho phương thức riêng tư của bạn công khai).

Tôi muốn khởi tạo các biến của bạn khi xây dựng lớp hoặc chuyển chúng thành đối số. Cái sau cung cấp cho bạn tùy chọn sử dụng các thành ngữ chức năng và không dựa vào trạng thái của đối tượng chứa.


0

Đã có câu trả lời nói về trạng thái đối tượng và khi phương thức thứ hai được ưa thích. Tôi chỉ muốn thêm một trường hợp sử dụng chung cho mẫu đầu tiên.

Mẫu đầu tiên hoàn toàn hợp lệ khi tất cả những gì lớp bạn làm là nó gói gọn một thuật toán . Một trường hợp sử dụng cho điều này là nếu bạn viết thuật toán cho một phương thức duy nhất thì nó sẽ quá lớn. Vì vậy, bạn chia nó thành các phương thức nhỏ hơn làm cho nó trở thành một lớp và làm cho các phương thức con trở nên riêng tư.

Bây giờ việc chuyển tất cả trạng thái của thuật toán thông qua các tham số có thể trở nên tẻ nhạt vì vậy bạn sử dụng các trường riêng. Nó cũng phù hợp với quy tắc trong đoạn đầu tiên vì về cơ bản nó là một trạng thái của thể hiện. Bạn chỉ cần lưu ý và ghi lại đúng tài liệu rằng thuật toán sẽ không được cấp lại nếu bạn sử dụng các trường riêng cho việc này . Điều này không phải là một vấn đề hầu hết thời gian nhưng nó có thể có thể cắn bạn.


0

Hãy thử một ví dụ làm một cái gì đó. Hãy tha thứ cho tôi vì đây là javascript không phải java. Điểm nên giống nhau.

Truy cập https://blockly-games.appspot.com/pond-duck?lang=en , nhấp vào tab javascript và dán vào đây:

hunt = lock(-90,1), 
heading = -135;

while(true) {
  hunt()  
  heading += 2
  swim(heading,30)
}

function lock(direction, width) {
  var dir = direction
  var wid = width
  var dis = 10000

  //hunt
  return function() {
    //randomize() //Calling this here makes the state of dir meaningless
    scanLock()
    adjustWid()
    if (isSpotted()) {
      if (inRange()) {
        if (wid <= 4) {
          cannon(dir, dis)
        }
      }
    } else {
      if (!left()) {
        right()
      }
    }
  }

  function scanLock() {
    dis = scan(dir, wid);
  }

  function adjustWid() {
    if (inRange()) {
      if (wid > 1)
        wid /= 2;
    } else {
      if (wid < 16) {
        wid *= 2; 
      }
    }
  }

  function isSpotted() {
    return dis < 1000;
  }

  function left() {
    dir += wid;
    scanLock();
    return isSpotted();
  }

  function right() {
    dir -= wid*2;
    scanLock();
    return isSpotted()
  }

  function inRange() {
    return dis < 70;
  }

  function randomize() {
    dir = Math.random() * 360
  }
}

Bạn nên chú ý rằng dir, widdissẽ không được thông qua nhiều. Bạn cũng nên lưu ý rằng mã trong hàm lock()trả về trông rất giống mã giả. Vâng, đó là mã thực tế. Tuy nhiên, nó rất dễ đọc. Chúng tôi có thể thêm chuyển và gán nhưng điều đó sẽ thêm lộn xộn mà bạn sẽ không bao giờ thấy trong mã giả.

Nếu bạn muốn lập luận rằng không thực hiện gán và chuyển là tốt vì ba vars đó là trạng thái bền bỉ thì hãy xem xét thiết kế lại gán dirgiá trị ngẫu nhiên mỗi vòng lặp. Không kiên trì bây giờ, phải không?

Chắc chắn, bây giờ chúng ta có thể thả dirxuống phạm vi ngay bây giờ nhưng không phải là không bị buộc phải làm lộn xộn mã giả như mã với việc truyền và cài đặt.

Vì vậy, không, trạng thái không phải là lý do tại sao bạn quyết định sử dụng hoặc không sử dụng các tác dụng phụ hơn là vượt qua và trở lại. Cũng không có tác dụng phụ một mình có nghĩa là mã của bạn là không thể đọc được. Bạn không nhận được lợi ích của mã chức năng thuần túy . Nhưng được thực hiện tốt, với tên tốt, họ thực sự có thể tốt đẹp để đọc.

Điều đó không có nghĩa là họ không thể biến thành một spaghetti như cơn ác mộng. Nhưng sau đó, những gì không thể?

Chúc vịt săn vui vẻ.


1
Các chức năng bên trong / bên ngoài là một mô hình khác với những gì OP mô tả. - Tôi đồng ý rằng sự đánh đổi giữa các biến cục bộ trong một contra hàm bên ngoài đã truyền các đối số cho các hàm bên trong tương tự như các biến riêng tư trên một contra lớp đã chuyển argumetns sang các hàm riêng. Tuy nhiên, nếu bạn thực hiện so sánh này, thời gian tồn tại của đối tượng sẽ tương ứng với một lần chạy duy nhất của hàm, vì vậy các biến trong hàm ngoài là một phần của trạng thái liên tục.
Taemyr

@Taemyr OP mô tả một phương thức riêng được gọi trong một hàm tạo công khai, có vẻ giống như bên trong bên ngoài đối với tôi. Trạng thái không thực sự 'dai dẳng' nếu bạn bước vào trạng thái đó mỗi khi bạn nhập một chức năng. Đôi khi trạng thái được sử dụng như một phương thức chia sẻ địa điểm có thể viết và đọc. Không có nghĩa là nó cần phải được kiên trì. Thực tế là nó vẫn tồn tại dù sao cũng không phải là thứ gì đó phải phụ thuộc vào.
candied_orange

1
Nó vẫn tồn tại ngay cả khi bạn giẫm lên nó mỗi khi bạn vứt bỏ vật thể.
Taemyr

1
Điểm tốt, nhưng thể hiện chúng bằng JavaScript thực sự làm xáo trộn cuộc thảo luận. Ngoài ra, nếu bạn loại bỏ cú pháp JS, bạn sẽ kết thúc với đối tượng bối cảnh được truyền xung quanh (đóng hàm ngoài)
fdreger

1
@sdenham Tôi cho rằng có sự khác biệt giữa các thuộc tính của lớp và kết quả trung gian tạm thời của một hoạt động. Chúng không tương đương. Nhưng cái này có thể được lưu trữ trong cái khác. Nó chắc chắn không phải là. Câu hỏi là nếu nó nên bao giờ. Vâng, có những vấn đề ngữ nghĩa và suốt đời. Ngoài ra còn có vấn đề arity. Bạn không nghĩ rằng họ đủ quan trọng. Đó là tốt. Nhưng để nói rằng việc sử dụng cấp độ lớp cho bất kỳ trạng thái nào khác ngoài trạng thái đối tượng là mô hình hóa không chính xác đang nhấn mạnh vào một cách mô hình hóa. Tôi đã duy trì mã chuyên nghiệp đã làm điều đó theo cách khác.
candied_orange
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.