Tôi có một tình huống thú vị và tôi tự hỏi nếu có một cách tốt hơn để làm điều này. Tình hình là điều này, tôi có một cấu trúc cây (một cây cú pháp trừu tượng, cụ thể) và một số nút có thể chứa các nút con của các loại khác nhau nhưng tất cả được mở rộng từ một lớp cơ sở nhất định.Loại an toàn, Java Generics và truy vấn
Tôi muốn thường xuyên thực hiện truy vấn trên cây này và tôi muốn lấy lại các kiểu con cụ thể mà tôi quan tâm. Vì vậy, tôi đã tạo một lớp biến vị ngữ mà sau đó tôi có thể chuyển vào một phương thức truy vấn chung. Lúc đầu, tôi đã có một phương pháp truy vấn trông giống như sau:
public <T extends Element> List<T> findAll(IElementPredicate pred, Class<T> c);
đối số Class
được sử dụng chỉ để chỉ loại trả về. Điều làm tôi băn khoăn về cách tiếp cận này là tất cả các biến vị ngữ của tôi đã sẵn sàng cho các kiểu cụ thể để có thông tin dư thừa ở đây. Một cuộc gọi thông thường có thể trông giống như:
List<Declaration> decls =
scope.findAll(new DeclarationPredicate(), Declaration.class);
Vì vậy, tôi refactored nó như thế này:
public <T extends Element> List<T> findAll(IElementPredicate<T> pred);
Trường hợp giao diện IElementPredicate
trông như thế này:
public interface IElementPredicate<T extends Element> {
public boolean match(T e);
public String getDescription();
public Class<T> getGenericClass();
}
Vấn đề ở đây là vị giao diện được mở rộng để cung cấp đối tượng Class
thay thế. Nó làm cho phương pháp viết thực tế hơn một chút và nó bổ sung thêm một chút công việc bằng cách viết biến vị ngữ, nhưng cả hai đều là những thứ "một lần" cơ bản nhỏ và nó làm cho cuộc gọi truy vấn đẹp hơn nhiều bởi vì bạn không phải thêm đối số thừa (có thể dư thừa), ví dụ
List<Declaration> decls = scope.findAll(new DeclarationPredicate());
Tôi chưa nhận thấy mẫu này trước đây. Đây có phải là cách điển hình để đối phó với ngữ nghĩa của các Generics Java không? Chỉ cần tò mò nếu tôi thiếu một số mô hình tốt hơn.
Cam kết?
CẬP NHẬT:
Một câu hỏi là bạn cần lớp học để làm gì? Đây là việc thực hiện findAll:
public <T extends Element> List<T> findAll(IElementPredicate<T> pred) {
List<T> ret = new LinkedList<T>();
Class<T> c = pred.getGenericClass();
for(Element e: elements) {
if (!c.isInstance(e)) continue;
T obj = c.cast(e);
if (pred.match(obj)) {
ret.add(c.cast(e));
}
}
return ret;
}
Mặc dù đối sánh đúng với T, tôi cần đảm bảo đối tượng là T trước khi tôi có thể gọi nó. Đối với điều đó, tôi cần các phương thức "isInstance" và "cast" của Class (theo như tôi có thể nói).
Tôi thường xuyên sử dụng khách truy cập (trong dự án này rất giống nhau) nhưng trong trường hợp cụ thể này, mẫu khách truy cập sẽ thêm nhiều mã và độ phức tạp hơn tôi nghĩ. –
Tôi đồng ý với bạn, nó là một loại biến thể chuyển đổi theo cách nó có tác dụng phụ lớn. Thật khó để đảo ngược chủ nghĩa chung như bạn muốn. Tôi tin rằng giải pháp của bạn là tốt nhất, bởi vì không có khách truy cập, tôi tin rằng bạn sẽ cần phải ràng buộc trễ hoặc kiểm tra loại rõ ràng (theo cách thức bằng() hoạt động). Giải pháp của bạn tốt hơn vì nó tập trung vào việc kiểm tra kiểu thay vì đặt gánh nặng đó lên những người triển khai. Bạn cũng thấy vấn đề này trong các triển khai Comparable chung. –
Và với sự ràng buộc trễ tôi có nghĩa là Java nên thực hiện độ phân giải phương thức quá tải dựa trên loại đối tượng, không phải trên kiểu tham chiếu. Đây là cơ bản những gì bạn thực hiện trong mã của bạn. –