2012-01-13 23 views
6

Tôi đang thiết kế một API trong Java cho một tập hợp các thuật toán số hoạt động trên mảng tăng gấp đôi (đối với số liệu thống kê tài chính theo thời gian thực khi nó xảy ra). Vì lý do hiệu suất, API phải làm việc với các mảng nguyên thủy, do đó, List<Double> và các loại tương tự không phải là một tùy chọn.Thiết kế API cho các chức năng hoạt động trên mảng

Trường hợp sử dụng điển hình có thể là đối tượng thuật toán lấy hai mảng đầu vào và trả về một mảng đầu ra chứa kết quả tính từ hai đầu vào.

Tôi muốn thiết lập quy ước phù hợp cho cách các tham số mảng được sử dụng trong các API, cụ thể:

  • Tôi có nên bao gồm một offsets với tất cả các chức năng để người dùng có thể hoạt động trên các bộ phận của một mảng lớn hơn ví dụ: someFunction(double[] input, int inputOffset, int length)
  • Nếu một hàm cần cả tham số đầu vào và đầu ra, đầu vào hoặc đầu ra có phải là đầu tiên trong danh sách tham số không?
  • Nếu người gọi phân bổ mảng đầu ra và chuyển nó thành tham số (có khả năng được sử dụng lại), hoặc chức năng tạo và trả về một mảng đầu ra mỗi khi nó được gọi?

Mục tiêu là đạt được sự cân bằng về hiệu quả, đơn giản cho người dùng API và tính nhất quán cả trong API và với các quy ước được thiết lập.

Rõ ràng có rất nhiều tùy chọn, vì vậy thiết kế API tổng thể tốt nhất là gì?

Trả lời

2

Vì vậy, đó thực sự có vẻ như ba câu hỏi, vì vậy đây là ý kiến ​​của tôi.

Tất nhiên, điều này rất chủ quan - vì vậy - mileage của bạn có thể thay đổi:

  1. Yes. Luôn bao gồm độ dài bù đắp &. Nếu hầu hết các trường hợp sử dụng cho một chức năng cụ thể thì không cần các tham số đó, quá tải hàm sao cho không cần phải nhập chiều dài & đầu vào.

  2. Đối với điều này, tôi sẽ làm theo các tiêu chuẩn được sử dụng bởi arraycopy:

    arraycopy (Object src, int srcPos, Object dest, int destPos, int length)

  3. Sự khác biệt hiệu suất ở đây là sẽ không đáng kể trừ khi người gọi liên tục gọi các chức năng tiện ích của bạn. Nếu họ chỉ là một điều, thì không có sự khác biệt. Nếu họ được gọi nhiều lần hơn bạn nên có người gọi gửi cho bạn một mảng được phân bổ.

2
  1. Nếu bạn làm như vậy, hãy cung cấp tùy chọn mặc định quá (bắt đầu từ 0, toàn bộ chiều dài).
  2. Tôi nghĩ hầu hết người dùng sẽ mong đợi kết quả đầu ra thứ 2. Tuy nhiên, nếu bạn có thể sử dụng varargs, điều đó có thể làm thay đổi suy nghĩ của bạn.
  3. Tôi thích người gọi đi qua trong mảng đầu ra, nhưng với một tùy chọn cho null, nghĩa là phương thức sẽ phân bổ.

Xây dựng trên nhận xét vararg, giả sử bạn có phương pháp để thêm hai mảng. Nếu bạn đặt mảng đầu ra arg là arg thứ nhất và 2 mảng đầu vào ở cuối, nó là tầm thường để mở rộng phương thức để thêm mảng N.

Xây dựng trên # 3, cho phép người gọi vượt qua trong mảng đầu ra, đôi khi nó hiệu quả hơn. Và, ngay cả khi mức tăng không đáng kể, người dùng của bạn, giao dịch với các mảng nguyên thủy, có thể đến từ nền C hoặc FORTRAN và cho rằng mức tăng sẽ lớn và sẽ khiếu nại nếu bạn không cho phép chúng "hiệu quả". :-)

2

Giả sử rằng bạn đang làm việc với các mảng đủ nhỏ để được cấp phát trên ngăn xếp hoặc trong Eden, phân bổ cực kỳ nhanh. Vì vậy, không có hại trong việc có chức năng phân bổ mảng riêng của họ để trả về kết quả. Làm điều này là một chiến thắng lớn cho khả năng đọc.

Tôi khuyên bạn nên bắt đầu thực hiện các chức năng của bạn hoạt động trên toàn bộ mảng và giới thiệu một tùy chọn để gọi hàm chỉ với một lát của một mảng nếu bạn phát hiện ra nó hữu ích.

+0

Tôi nghĩ anh ấy đang viết bằng Java, vì vậy nhận xét "trên ngăn xếp" không có ý nghĩa gì trong ngữ cảnh đó. – user949300

+1

Không, HotSpot phân bổ các đối tượng nhỏ không thoát trên ngăn xếp. –

+0

Nhưng vì các mảng này được trả về dưới dạng kết quả, chúng chắc chắn sẽ thoát. – user949300

0

Tôi sẽ sử dụng List<Double> và có những phương pháp trả lại đầu ra như một mới List:

public List<Double> someFunction(List<Double> input) 
+0

thường là lời khuyên tốt nhưng không khả thi trong trường hợp của tôi - vì lý do hiệu suất API phải làm việc với các mảng nguyên thủy – mikera

1

Điều chính về thiết kế API thể hiện nhiều chức năng là tính nhất quán nội bộ của nó. Mọi thứ khác đến như là một thứ hai xa xôi.

Quyết định về việc bạn có chuyển các cặp chỉ mục/chiều dài hay không phụ thuộc vào cách API được dự kiến ​​sẽ được sử dụng. Nếu bạn mong đợi người dùng viết chuỗi các cuộc gọi phương thức lấy hoặc đặt dữ liệu trong các phân đoạn khác nhau của cùng một mảng, như trong System.arrayCopy, thì bạn cần cặp chỉ mục/chiều dài. Nếu không, nó là một quá mức cần thiết.

Đầu vào đầu tiên hoặc đầu ra là quyết định của bạn, nhưng một khi bạn thực hiện nó, hãy gắn nó với tất cả các phương pháp có chữ ký tương tự.

Vượt qua bộ đệm đầu ra là một lựa chọn hợp lý chỉ khi bộ đệm được tái sử dụng trong máy khách. Nếu không, đó là nỗ lực lãng phí trong việc xây dựng và duy trì thêm một bộ phương pháp API. Tất nhiên quyết định này tương quan chặt chẽ với lựa chọn của bạn để đi với các cặp chỉ mục/chiều dài: nếu bạn lấy chỉ mục và độ dài, bạn cũng nên lấy bộ đệm đầu ra.

1

Tôi nghĩ rằng thiết kế API chủ yếu là chủ quan và/hoặc sẽ bị ảnh hưởng nặng nề bởi các "trường hợp sử dụng" API. Các trường hợp sử dụng cho API của bạn, mặt khác, hoàn toàn phụ thuộc vào mã máy khách.

Có nói tất cả những gì, cá nhân, tôi sẽ tận dụng lợi thế của phương pháp quá tải và đi cho cấu trúc sau:

Một phương pháp với tất cả các thông số:

void someFunction(int[] input1, int[] input2, int offset, int length, int[] output)

Đây là hàm main. Tất cả các chức năng khác chỉ cần gọi điều này với các tham số thích hợp.

int[] someFunction(int[] input1, int[] input2, int offset, int length)

này gọi hàm đầu tiên, nhưng giao đất và trả về mảng đầu ra thay mặt cho người gọi.

void someFunction(int[] input1, int[] input2, int[] output)

int[] someFunction(int[] input1, int[] input2)

Lưu ý rằng các chiến lược chung là làm cho danh sách tham số ngắn hơn bằng cách loại bỏ các thông số 'bắt buộc'.

Nói chung, tôi có xu hướng tránh thay đổi hành vi của phương pháp tùy thuộc vào việc tham số (như mảng đầu ra) là null. Nó có thể làm cho nó khó khăn hơn để bắt lỗi tinh tế theo cách đó. Do đó sở thích của tôi cho hai kiểu gọi khác nhau — một kiểu mà thông số đầu ra được cung cấp (và bắt buộc), và một trong đó phương thức trả về kết quả đầu ra của nó.

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