Phương thức đơn có nhiều tham số so với nhiều phương thức phải được gọi theo thứ tự


16

Tôi có một số dữ liệu thô tôi cần làm nhiều việc để (chuyển nó, xoay nó, chia tỷ lệ theo trục nhất định, xoay nó đến vị trí cuối cùng) và tôi không chắc cách tốt nhất để làm điều này là gì để duy trì khả năng đọc mã. Một mặt, tôi có thể tạo một phương thức duy nhất với nhiều tham số (10+) để làm những gì tôi cần, nhưng đây là một cơn ác mộng đọc mã. Mặt khác, tôi có thể tạo nhiều phương thức với 1-3 tham số, nhưng các phương thức này sẽ cần được gọi theo thứ tự rất cụ thể để có kết quả chính xác. Tôi đã đọc rằng tốt nhất là các phương thức để làm một việc và làm tốt, nhưng có vẻ như có nhiều phương thức cần được gọi để mở mã cho các lỗi khó tìm.

Có một mô hình lập trình nào tôi có thể sử dụng để giảm thiểu lỗi và làm cho mã dễ đọc hơn không?


3
Vấn đề lớn nhất không phải là "không gọi họ theo thứ tự", "không biết" rằng bạn (hay chính xác hơn là một lập trình viên tương lai) phải gọi họ theo thứ tự. Hãy chắc chắn rằng bất kỳ lập trình viên bảo trì nào cũng biết các chi tiết (Điều này phần lớn sẽ phụ thuộc vào cách bạn ghi lại các yêu cầu, thiết kế và thông số kỹ thuật). Sử dụng các bài kiểm tra Đơn vị, nhận xét và cung cấp các hàm trợ giúp lấy tất cả các tham số và gọi cho các tham số khác
mattnz

Cũng giống như một nhận xét ngoài tầm tay, giao diện trôi chảymẫu lệnh có thể hữu ích. Tuy nhiên, tùy thuộc vào bạn (với tư cách là chủ sở hữu) và người dùng thư viện của bạn (khách hàng) để quyết định thiết kế nào là tốt nhất. Như những người khác chỉ ra, cần phải thông báo cho người dùng rằng các hoạt động là không giao hoán (rằng chúng nhạy cảm với thứ tự thực hiện), mà không có người dùng của bạn sẽ không bao giờ tìm ra cách sử dụng chúng một cách chính xác.
rwong

Ví dụ về các hoạt động không giao hoán: biến đổi hình ảnh (xoay, chia tỷ lệ và cắt xén), nhân ma trận, v.v.
rwong

Có lẽ bạn có thể sử dụng currying: điều này sẽ khiến không thể áp dụng các phương thức / hàm theo thứ tự sai.
Giorgio

Bộ phương pháp nào bạn đang làm việc ở đây? Ý tôi là, tôi nghĩ rằng tiêu chuẩn là để vượt qua một đối tượng chuyển đổi (như Chuyển đổi affine của Java cho các công cụ 2D) mà bạn truyền cho một số phương thức áp dụng nó. Nội dung của biến đổi là khác nhau tùy theo thứ tự bạn gọi các thao tác ban đầu trên nó, theo thiết kế (vì vậy "bạn gọi nó theo thứ tự bạn cần", chứ không phải "theo thứ tự tôi muốn").
Clockwork-Muse

Câu trả lời:


24

Cẩn thận với khớp nối tạm thời . Tuy nhiên, điều này không phải lúc nào cũng là một vấn đề.

Nếu bạn phải thực hiện các bước theo thứ tự, thì bước 1 sẽ tạo ra một số đối tượng cần thiết cho bước 2 (ví dụ: luồng tệp hoặc cấu trúc dữ liệu khác). Điều này một mình đòi hỏi rằng hàm thứ hai phải được gọi sau hàm thứ nhất, thậm chí không thể gọi chúng theo thứ tự sai một cách vô tình.

Bằng cách chia chức năng của bạn thành các mảnh nhỏ, mỗi phần dễ hiểu hơn và chắc chắn dễ kiểm tra hơn. Nếu bạn có một hàm 100 dòng khổng lồ và một cái gì đó ở giữa, làm thế nào để kiểm tra thất bại của bạn cho bạn biết điều gì là sai? Nếu một trong năm phương thức dòng của bạn bị hỏng, kiểm tra đơn vị thất bại của bạn sẽ hướng bạn ngay lập tức về phía một đoạn mã cần chú ý.

Đây là cách mã phức tạp nên :

public List<Widget> process(File file) throws IOException {
  try (BufferedReader in = new BufferedReader(new FileReader(file))) {
    List<Widget> widgets = new LinkedList<>();
    String line;
    while ((line = in.readLine()) != null) {
      if (isApplicable(line)) { // Filter blank lines, comments, etc.
        Ore o = preprocess(line);
        Ingot i = smelt(o);
        Alloy a = combine(i, new Nonmetal('C'));
        Widget w = smith(a);
        widgets.add(w);
      }
    }
    return widgets;
  }
}

Tại bất kỳ thời điểm nào trong quá trình chuyển đổi dữ liệu thô thành một widget đã hoàn thành, mỗi hàm sẽ trả về một cái gì đó được yêu cầu bởi bước tiếp theo trong quy trình. Người ta không thể tạo thành một hợp kim từ xỉ, trước tiên người ta phải nấu chảy (tinh chế) nó. Người ta không thể tạo một widget mà không có sự cho phép thích hợp (ví dụ như thép) làm đầu vào.

Các chi tiết cụ thể của từng bước được chứa trong các chức năng riêng biệt có thể được kiểm tra: thay vì kiểm tra đơn vị toàn bộ quá trình khai thác đá và tạo vật dụng, kiểm tra từng bước cụ thể. Bây giờ bạn có một cách dễ dàng để đảm bảo rằng nếu quá trình "tạo widget" của bạn thất bại, bạn có thể thu hẹp lý do cụ thể.

Bên cạnh những lợi ích của việc kiểm tra và chứng minh tính đúng đắn, viết mã theo cách này dễ đọc hơn nhiều. Không ai có thể hiểu một danh sách tham số lớn . Chia nó thành những mảnh nhỏ và chỉ ra ý nghĩa của từng mảnh nhỏ: đó là điều đáng kinh ngạc .


2
Cảm ơn, tôi nghĩ rằng đây là một cách tốt để giải quyết vấn đề. Mặc dù nó làm tăng số lượng đối tượng (và điều đó có thể cảm thấy không cần thiết), nó buộc phải có trật tự trong khi duy trì mức độ dễ đọc.
tomsrobots

10

Đối số "phải được thực hiện theo thứ tự" là tranh luận vì gần như tất cả mã của bạn phải được thực hiện theo đúng thứ tự. Rốt cuộc, bạn không thể ghi vào một tập tin, sau đó mở nó và đóng nó, bạn có thể không?

Bạn nên tập trung vào những gì làm cho mã của bạn dễ bảo trì nhất. Điều này thường có nghĩa là các hàm viết nhỏ và dễ hiểu. Mỗi chức năng nên có một mục đích duy nhất và không có tác dụng phụ không lường trước được.


5

Tôi sẽ tạo một » ImageProcesssor « (hoặc bất kỳ tên nào phù hợp với dự án của bạn) và một đối tượng cấu hình ProcessConfiguration , chứa tất cả các tham số cần thiết.

 ImageProcessor p = new ImageProcessor();

 ProcessConfiguration config = new processConfiguration().setTranslateX(100)
                                                         .setTranslateY(100)
                                                         .setRotationAngle(45);
 p.process(image, config);

Bên trong bộ xử lý hình ảnh, bạn gói gọn toàn bộ quá trình đằng sau một mehtod process()

public class ImageProcessor {

    public Image process(Image i, ProcessConfiguration c){
        Image processedImage=i.getCopy();
        shift(processedImage, c);
        rotate(processedImage, c);
        return processedImage;
    }

    private void rotate(Image i, ProcessConfiguration c) {
        //rotate
    }

    private void shift(Image i, ProcessConfiguration c) {
        //shift
    }
}

Phương thức này gọi các phương thức biến đổi theo đúng thứ tự shift(), rotate(). Mỗi phương thức nhận các tham số thích hợp từ ProcessConfiguration được thông qua .

public class ProcessConfiguration {

    private int translateX;

    private int rotationAngle;

    public int getRotationAngle() {
        return rotationAngle;
    }

    public ProcessConfiguration setRotationAngle(int rotationAngle){
        this.rotationAngle=rotationAngle;
        return this;
    }

    public int getTranslateY() {
        return translateY;
    }

    public ProcessConfiguration setTranslateY(int translateY) {
        this.translateY = translateY;
        return this;
    }

    public int getTranslateX() {
        return translateX;
    }

    public ProcessConfiguration setTranslateX(int translateX) {
        this.translateX = translateX;
        return this;
    }

    private int translateY;

}

Tôi đã sử dụng giao diện chất lỏng

public ProcessConfiguration setRotationAngle(int rotationAngle){
    this.rotationAngle=rotationAngle;
    return this;
}

cho phép khởi tạo tiện lợi (như đã thấy ở trên).

Lợi thế rõ ràng, gói gọn các tham số cần thiết trong một đối tượng. Chữ ký phương thức của bạn trở nên dễ đọc:

private void shift(Image i, ProcessConfiguration c)

Đó là về việc dịch chuyển một hình ảnh và các thông số chi tiết được cấu hình bằng cách nào đó .

Ngoài ra, bạn có thể tạo một Chế độ xử lý :

public class ProcessingPipeLine {

    Image i;

    public ProcessingPipeLine(Image i){
        this.i=i;
    };

    public ProcessingPipeLine shift(Coordinates c){
        shiftImage(c);
        return this;
    }

    public ProcessingPipeLine rotate(int a){
        rotateImage(a);
        return this;
    }

    public Image getResultingImage(){
        return i;
    }

    private void rotateImage(int angle) {
        //shift
    }

    private void shiftImage(Coordinates c) {
        //shift
    }

}

Một cuộc gọi phương thức đến một phương thức processImagesẽ khởi tạo một đường ống như vậy và minh bạch hóa những gì và theo thứ tự được thực hiện: thay đổi , xoay

public Image processImage(Image i, ProcessConfiguration c){
    Image processedImage=i.getCopy();
    processedImage=new ProcessingPipeLine(processedImage)
            .shift(c.getCoordinates())
            .rotate(c.getRotationAngle())
            .getResultingImage();
    return processedImage;
}

3

Bạn đã xem xét sử dụng một số loại cà ri ? Hãy tưởng tượng rằng bạn có một lớp Processeevà một lớp Processor:

class Processor
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T1 a1, T2 a2)
    {
        // Process using a1
        // then process using a2
    }
}

Bây giờ bạn có thể thay thế lớp Processorbằng hai lớp Processor1Processor2:

class Processor1
{
    private final Processee _processee;

    public Processor1(Processee p)
    {
        _processee = p;
    }

    public Processor2 process(T1 a1)
    {
        // Process using argument a1

        return new Processor2(_processee);
    }
}

class Processor2
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T2 a2)
    {
        // Process using argument a2
    }
}

Sau đó, bạn có thể gọi các hoạt động theo đúng thứ tự bằng cách sử dụng:

new Processor1(processee).process(a1).process(a2);

Bạn có thể áp dụng mẫu này nhiều lần nếu bạn có nhiều hơn hai tham số. Bạn cũng có thể nhóm các đối số theo ý muốn, tức là bạn không cần phải có mỗi processphương thức lấy chính xác một đối số.


Chúng tôi đã có cùng một ý tưởng;) Sự khác biệt duy nhất là, Đường ống của bạn thi hành một lệnh xử lý nghiêm ngặt.
Thomas Junk

@ThomasJunk: Theo như tôi hiểu, đây là một yêu cầu: "những phương pháp này sẽ cần phải được gọi theo một thứ tự rất cụ thể để có được kết quả chính xác". Có một thứ tự thực hiện nghiêm ngặt nghe rất giống thành phần chức năng.
Giorgio

Và tôi cũng vậy, nhưng, nếu xử lý thay đổi thứ tự, bạn phải thực hiện nhiều thao tác tái cấu trúc;)
Thomas Junk

@ThomasJunk: Đúng. Nó thực sự phụ thuộc vào ứng dụng. Nếu các bước xử lý có thể được hoán đổi rất thường xuyên, thì có lẽ cách tiếp cận của bạn là tốt hơn.
Giorgio
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.