lựa chọn thay thế cho các lần thử bắt lồng cho dự phòng


14

Tôi có một tình huống mà tôi đang cố gắng để lấy một đối tượng. Nếu việc tra cứu thất bại tôi có một vài dự phòng, mỗi trong số đó có thể thất bại. Vì vậy, mã trông như:

try {
    return repository.getElement(x);
} catch (NotFoundException e) {
    try {
        return repository.getSimilarElement(x);
    } catch (NotFoundException e1) {
        try {
            return repository.getParentElement(x);
        } catch (NotFoundException e2) {
            //can't recover
            throw new IllegalArgumentException(e);
        }
    }
}

Điều này trông cực kỳ xấu xí. Tôi ghét trả về null, nhưng điều đó có tốt hơn trong tình huống này không?

Element e = return repository.getElement(x);
if (e == null) {
    e = repository.getSimilarElement(x);
}
if (e == null) {
    e = repository.getParentElement(x);
}
if (e == null) {
    throw new IllegalArgumentException();
}
return e;

Có những lựa chọn thay thế khác?

Là sử dụng các khối thử bắt lồng nhau là một mô hình chống? có liên quan, nhưng câu trả lời có hai dòng "đôi khi, nhưng thường là có thể tránh được", mà không nói khi nào hoặc làm thế nào để tránh nó.


1
NotFoundExceptionmột cái gì đó thực sự đặc biệt?

Tôi không biết, và đó có lẽ là lý do tại sao tôi gặp rắc rối. Đây là trong một bối cảnh thương mại điện tử, nơi các sản phẩm bị ngưng hàng ngày. Nếu ai đó đánh dấu một sản phẩm mà sau đó đã ngừng sản xuất và sau đó cố gắng mở dấu trang ... điều đó có đặc biệt không?
Alex Wittig

@FiveNine theo ý kiến ​​của tôi, chắc chắn là không - nó được mong đợi. Xem stackoverflow.com/questions/729379/ từ
Konrad Morawski

Câu trả lời:


17

Cách thông thường để loại bỏ lồng là sử dụng các hàm:

Element getElement(x) {
    try {
        return repository.getElement(x);
    } catch (NotFoundException e) {
        return fallbackToSimilar(x);
    }  
}

Element fallbackToSimilar(x) {
    try {
        return repository.getSimilarElement(x);
     } catch (NotFoundException e1) {
        return fallbackToParent(x);
     }
}

Element fallbackToParent(x) {
    try {
        return repository.getParentElement(x);
    } catch (NotFoundException e2) {
        throw new IllegalArgumentException(e);
    }
}

Nếu các quy tắc dự phòng này là phổ biến, bạn có thể xem xét thực hiện điều này trực tiếp trong repositoryđối tượng, nơi bạn có thể chỉ cần sử dụng các ifcâu lệnh đơn giản thay vì một ngoại lệ.


1
Trong bối cảnh này, methodsẽ là một từ tốt hơn function.
Sulthan

12

Điều này sẽ thực sự dễ dàng với một cái gì đó giống như một đơn vị Tùy chọn. Thật không may, Java không có những thứ đó. Trong Scala, tôi sẽ sử dụng Tryloại để tìm giải pháp thành công đầu tiên.

Trong tư duy lập trình chức năng của mình, tôi đã thiết lập một danh sách các cuộc gọi lại đại diện cho các nguồn có thể khác nhau và lặp qua chúng cho đến khi chúng tôi tìm thấy cuộc gọi thành công đầu tiên:

interface ElementSource {
    public Element get();
}

...

final repository = ...;

// this could be simplified a lot using Java 8's lambdas
List<ElementSource> sources = Arrays.asList(
    new ElementSource() {
        @Override
        public Element get() { return repository.getElement(); }
    },
    new ElementSource() {
        @Override
        public Element get() { return repository.getSimilarElement(); }
    },
    new ElementSource() {
        @Override
        public Element get() { return repository.getParentElement(); }
    }
);

Throwable exception = new NoSuchElementException("no sources set up");
for (ElementSource source : sources) {
    try {
        return source.get();
    } catch (NotFoundException e) {
        exception = e;
    }
}
// we end up here if we didn't already return
// so throw the last exception
throw exception;

Điều này chỉ có thể được đề xuất nếu bạn thực sự có số lượng nguồn lớn hoặc nếu bạn phải định cấu hình các nguồn khi chạy. Mặt khác, đây là một sự trừu tượng không cần thiết và bạn sẽ kiếm được nhiều tiền hơn từ việc giữ cho mã của bạn đơn giản và ngu ngốc, và chỉ sử dụng những lần thử xấu xí lồng nhau.


+1 để đề cập đến Tryloại trong Scala, để đề cập đến các đơn nguyên và cho giải pháp sử dụng vòng lặp.
Giorgio

Nếu tôi đã sử dụng Java 8, tôi sẽ thực hiện điều này, nhưng như bạn nói, nó hơi nhiều cho một vài dự phòng.
Alex Wittig

1
Trên thực tế, tại thời điểm câu trả lời này được đăng, Java 8 với sự hỗ trợ cho Optionalđơn nguyên ( bằng chứng ) đã được phát hành.
mkalkov

3

Nếu bạn dự đoán rằng sẽ có rất nhiều cuộc gọi kho lưu trữ sẽ được thực hiện NotFoundException, bạn có thể sử dụng trình bao bọc xung quanh kho lưu trữ để hợp lý hóa mã. Tôi sẽ không đề nghị điều này cho các hoạt động bình thường, nhớ bạn:

public class TolerantRepository implements SomeKindOfRepositoryInterfaceHopefully {

    private Repository repo;

    public TolerantRepository( Repository r ) {
        this.repo = r;
    }

    public SomeType getElement( SomeType x ) {
        try {
            return this.repo.getElement(x);
        }
        catch (NotFoundException e) {
            /* For example */
            return null;
        }
    }

    // and the same for other methods...

}

3

Theo đề nghị của @ amon, đây là một câu trả lời đơn điệu hơn. Đây là một phiên bản rất sôi nổi, nơi bạn phải chấp nhận một vài giả định:

  • hàm "unit" hoặc "return" là hàm tạo của lớp

  • hoạt động "liên kết" xảy ra vào thời gian biên dịch, vì vậy nó bị ẩn khỏi lệnh gọi

  • các hàm "hành động" cũng được liên kết với lớp trong thời gian biên dịch

  • mặc dù lớp này là chung chung và bao bọc bất kỳ lớp E tùy ý, tôi nghĩ rằng điều đó thực sự quá mức trong trường hợp này. Nhưng tôi đã để nó theo cách đó như một ví dụ về những gì bạn có thể làm.

Với những cân nhắc đó, đơn nguyên chuyển thành một lớp bao bọc trôi chảy (mặc dù bạn đang từ bỏ rất nhiều sự linh hoạt mà bạn có được bằng một ngôn ngữ hoàn toàn có chức năng):

public class RepositoryLookup<E> {
    private String source;
    private E answer;
    private Exception exception;

    public RepositoryLookup<E>(String source) {
        this.source = source;
    }

    public RepositoryLookup<E> fetchElement() {
        if (answer != null) return this;
        if (! exception instanceOf NotFoundException) return this;

        try {
            answer = lookup(source);
        }
        catch (Exception e) {
            exception = e;
        }

        return this;
    }

    public RepositoryLookup<E> orFetchSimilarElement() {
        if (answer != null) return this; 
        if (! exception instanceOf NotFoundException) return this;

        try {
            answer = lookupVariation(source);
        }
        catch (Exception e) {
            exception = e;
        }

        return this;
    }

    public RepositoryLookup<E> orFetchParentElement() {
        if (answer != null) return this; 
        if (! exception instanceOf NotFoundException) return this;

        try {
            answer = lookupParent(source);
        }
        catch (Exception e) {
            exception = e;
        }

        return this;
    }

    public boolean failed() {
        return exception != null;
    }

    public Exception getException() {
        return exception;
    }

    public E getAnswer() {
        // better to check failed() explicitly ;)
        if (this.exception != null) {
            throw new IllegalArgumentException(exception);
        }
        // TODO: add a null check here?
        return answer;
    }
}

(điều này sẽ không được biên dịch ... một số chi tiết nhất định còn dang dở để giữ cho mẫu nhỏ)

Và lời cầu khẩn sẽ như thế này:

Repository<String> repository = new Repository<String>(x);
repository.fetchElement().orFetchParentElement().orFetchSimilarElement();

if (repository.failed()) {
    throw new IllegalArgumentException(repository.getException());
}

System.err.println("Got " + repository.getAnswer());

Lưu ý rằng bạn có thể linh hoạt soạn các thao tác "tìm nạp" theo ý muốn. Nó sẽ dừng lại khi nhận được câu trả lời hoặc một ngoại lệ khác ngoài không tìm thấy.

Tôi đã làm điều này thực sự nhanh chóng; nó không hoàn toàn đúng, nhưng hy vọng truyền đạt ý tưởng


1
repository.fetchElement().fetchParentElement().fetchSimilarElement();- theo ý kiến ​​của tôi: mã ác (theo nghĩa được đưa ra bởi Jon Skeet)
Konrad Morawski

một số người không thích phong cách đó, nhưng sử dụng return thisđể tạo các cuộc gọi đối tượng chuỗi đã có từ lâu. Vì OO liên quan đến các đối tượng có thể thay đổi, return thisít nhiều tương đương với việc return nullkhông có chuỗi. Tuy nhiên, return new Thing<E>mở ra cánh cửa cho một khả năng khác mà ví dụ này không đi vào, vì vậy điều quan trọng đối với mẫu này nếu bạn chọn đi theo con đường này.
Rob

1
Nhưng tôi thích phong cách đó và tôi không chống lại các cuộc gọi hoặc giao diện trôi chảy như vậy. Tuy nhiên, có một sự khác biệt giữa CustomerBuilder.withName("Steve").withID(403)và mã này, bởi vì chỉ cần nhìn thấy .fetchElement().fetchParentElement().fetchSimilarElement()nó không rõ ràng những gì xảy ra, và đó là điều quan trọng ở đây. Có phải tất cả đều được lấy? Nó không được tích lũy trong trường hợp này, và do đó không trực quan. Tôi cần phải thấy điều đó if (answer != null) return thistrước khi tôi thực sự có được nó. Có lẽ đó chỉ là vấn đề đặt tên thích hợp ( orFetchParent), nhưng dù sao đó cũng là "ma thuật".
Konrad Morawski

1
Nhân tiện (tôi biết mã của bạn được đơn giản hóa và chỉ là bằng chứng của khái niệm), sẽ tốt hơn nếu có thể trả lại một bản sao answervào getAnswervà đặt lại (xóa) answerchính trường đó trước khi trả về giá trị của nó. Mặt khác, nó phá vỡ nguyên tắc phân tách lệnh / truy vấn, bởi vì yêu cầu tìm nạp một phần tử (truy vấn) làm thay đổi trạng thái của đối tượng kho lưu trữ của bạn ( answerkhông bao giờ được đặt lại) và ảnh hưởng đến hành vi fetchElementkhi bạn gọi nó lần sau. Vâng, tôi đang cố gắng một chút, tôi nghĩ câu trả lời là hợp lệ, tôi không phải là người đánh giá thấp nó.
Konrad Morawski

1
đó là một điểm hay. Một cách khác sẽ là "toToFetch ...". Điểm quan trọng là trong trường hợp này, cả 3 phương thức đều được gọi, nhưng trong trường hợp khác, một khách hàng có thể chỉ sử dụng "vorFetch (). LvorFetchParent ()". Ngoài ra, gọi nó là "Kho lưu trữ" là sai, bởi vì nó thực sự mô hình hóa một lần tìm nạp. Có lẽ tôi sẽ loay hoay với những cái tên để gọi nó là "Rep StorageLookup" và "nỗ lực" để làm rõ đây là một tạo tác tạm thời, một cú bắn cung cấp một số ngữ nghĩa nhất định xung quanh việc tra cứu.
Rob

2

Một cách khác để cấu trúc một loạt các điều kiện như thế này là mang cờ hoặc kiểm tra khác bằng null (tốt hơn hết, sử dụng Tùy chọn của Guava để xác định khi nào có câu trả lời hay) để xâu chuỗi các điều kiện lại với nhau.

Element e = null;

try {
    e = repository.getElement(x);
} catch (NotFoundException e) {
    // nope -- try again!
}

if (e == null) {  // or ! optionalElement.isPresent()
    try {
        return repository.getSimilarElement(x);
    } catch (NotFoundException e1) {
        // nope -- try again!
    }
}

if (e == null) {  // or ! optionalElement.isPresent()
    try {
        return repository.getParentElement(x);
    } catch (NotFoundException e2) {
        // nope -- try again!
    }
}

if (e == null) {  // or ! optionalElement.isPresent()
    //can't recover
    throw new IllegalArgumentException(e);
}

return e;

Theo cách đó, bạn đang xem trạng thái của phần tử và thực hiện các cuộc gọi đúng dựa trên trạng thái của nó - nghĩa là, miễn là bạn chưa có câu trả lời.

(Tuy nhiên, tôi đồng ý với @amon. Tôi khuyên bạn nên xem mẫu Monad, với một đối tượng trình bao bọc class Repository<E>có thành viên E answer;Exception error;. Ở mỗi giai đoạn, hãy kiểm tra xem có ngoại lệ nào không và nếu có, hãy bỏ qua từng bước còn lại. cuối cùng, bạn còn lại một câu trả lời, không có câu trả lời hoặc ngoại lệ & bạn có thể quyết định phải làm gì với điều đó.)


-2

Đầu tiên, có vẻ như tôi nên có một hàm như repository.getMostSimilar(x)(bạn nên chọn một tên thích hợp hơn) vì dường như có một logic được sử dụng để tìm phần tử gần nhất hoặc tương tự nhất cho một phần tử nhất định.

Các kho lưu trữ sau đó có thể thực hiện logic như thể hiện trong bài amons. Điều đó có nghĩa là, trường hợp duy nhất một ngoại lệ phải được ném là khi không có yếu tố duy nhất có thể được tìm thấy.

Tuy nhiên điều này là tất nhiên chỉ có thể nếu logic để tìm phần tử gần nhất có thể được gói gọn vào kho lưu trữ. Nếu điều này là không thể, vui lòng cung cấp thêm thông tin về cách (theo tiêu chí nào) yếu tố gần nhất có thể được chọn.


câu trả lời là để trả lời câu hỏi, không yêu cầu làm rõ
gnat

Vâng, câu trả lời của tôi là giải quyết vấn đề của anh ấy vì nó chỉ ra một cách để tránh việc thử / bắt được lồng trong các điều kiện nhất định. Chỉ khi những điều kiện này không được đáp ứng, chúng tôi cần thêm thông tin. Tại sao đây không phải là một câu trả lời hợp lệ?
valenterry
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.