Tôi không biết về thanh lịch, nhưng đây là một triển khai hoạt động bằng cách sử dụng sẵn Java java.lang.reflect.Proxy
để thực thi rằng tất cả các yêu cầu phương thức Foo
bắt đầu bằng cách kiểm tra enabled
trạng thái.
main
phương pháp:
public static void main(String[] args) {
Foo foo = Foo.newFoo();
foo.setEnabled(false);
foo.bar(); // won't print anything.
foo.setEnabled(true);
foo.bar(); // prints "Executing method bar"
}
Foo
giao diện:
public interface Foo {
boolean getEnabled();
void setEnabled(boolean enable);
void bar();
void baz();
void bat();
// Needs Java 8 to have this convenience method here.
static Foo newFoo() {
FooFactory fooFactory = new FooFactory();
return fooFactory.makeFoo();
}
}
FooFactory
lớp học:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class FooFactory {
public Foo makeFoo() {
return (Foo) Proxy.newProxyInstance(
this.getClass().getClassLoader(),
new Class[]{Foo.class},
new FooInvocationHandler(new FooImpl()));
}
private static class FooImpl implements Foo {
private boolean enabled = false;
@Override
public boolean getEnabled() {
return this.enabled;
}
@Override
public void setEnabled(boolean enable) {
this.enabled = enable;
}
@Override
public void bar() {
System.out.println("Executing method bar");
}
@Override
public void baz() {
System.out.println("Executing method baz");
}
@Override
public void bat() {
System.out.println("Executing method bat");
}
}
private static class FooInvocationHandler implements InvocationHandler {
private FooImpl fooImpl;
public FooInvocationHandler(FooImpl fooImpl) {
this.fooImpl = fooImpl;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Foo.class &&
!method.getName().equals("getEnabled") &&
!method.getName().equals("setEnabled")) {
if (!this.fooImpl.getEnabled()) {
return null;
}
}
return method.invoke(this.fooImpl, args);
}
}
}
Như những người khác đã chỉ ra, có vẻ như quá mức cho những gì bạn cần nếu bạn chỉ có một số phương pháp để lo lắng.
Điều đó nói rằng, chắc chắn có lợi ích:
- Một sự tách biệt các mối quan tâm đã đạt được, bởi vì
Foo
việc triển khai phương pháp không phải lo lắng vềenabled
mối quan tâm xuyên suốt kiểm tra. Thay vào đó, mã của phương thức chỉ cần lo lắng về mục đích chính của phương thức là gì, không có gì nữa.
- Không có cách nào để một nhà phát triển vô tội thêm một phương thức mới vào
Foo
lớp và nhầm "quên" để thêm enabled
kiểm tra. Cácenabled
hành vi kiểm tra được tự động thừa hưởng bởi bất kỳ phương pháp mới được bổ sung.
- Nếu bạn cần thêm một mối quan tâm xuyên suốt khác, hoặc nếu bạn cần tăng cường
enabled
kiểm tra, rất dễ dàng để thực hiện một cách an toàn và ở một nơi.
- Thật tuyệt khi bạn có thể có hành vi giống AOP này với chức năng Java tích hợp. Bạn không bị buộc phải tích hợp một số khung công tác khác
Spring
, mặc dù chúng chắc chắn cũng có thể là những lựa chọn tốt.
Công bằng mà nói, một số nhược điểm là:
- Một số mã thực thi xử lý các yêu cầu proxy là xấu. Một số người cũng nói rằng có các lớp bên trong để ngăn chặn việc khởi tạo
FooImpl
lớp là xấu.
- Nếu bạn muốn thêm một phương thức mới vào
Foo
, bạn phải thực hiện thay đổi ở 2 điểm: lớp triển khai và giao diện. Không phải là một vấn đề lớn, nhưng nó vẫn còn một chút công việc.
- Yêu cầu proxy không miễn phí. Có một hiệu suất nhất định trên đầu. Đối với sử dụng chung, nó sẽ không được chú ý. Xem ở đây để biết thêm thông tin.
BIÊN TẬP:
Nhận xét của Fabian Streitel khiến tôi suy nghĩ về 2 điều phiền toái với giải pháp trên của tôi rằng, tôi sẽ thừa nhận, tôi không hài lòng về bản thân:
- Trình xử lý lời gọi sử dụng các chuỗi ma thuật để bỏ qua "kiểm tra kích hoạt" trên các phương thức "getEnables" và "setEnables". Điều này có thể dễ dàng phá vỡ nếu tên phương thức được tái cấu trúc.
- Nếu có một trường hợp cần thêm các phương thức mới mà không nên kế thừa hành vi "kiểm tra kích hoạt", thì nhà phát triển có thể dễ dàng hiểu sai điều này, và ít nhất, điều đó có nghĩa là thêm nhiều phép thuật dây.
Để giải quyết điểm # 1 và ít nhất là giải quyết vấn đề với điểm # 2, tôi sẽ tạo một chú thích BypassCheck
(hoặc một cái gì đó tương tự) mà tôi có thể sử dụng để đánh dấu các phương thức trong Foo
giao diện mà tôi không muốn thực hiện " kích hoạt kiểm tra ". Bằng cách này, tôi hoàn toàn không cần chuỗi ma thuật và nhà phát triển sẽ dễ dàng hơn rất nhiều để thêm một phương thức mới trong trường hợp đặc biệt này.
Sử dụng giải pháp chú thích, mã sẽ như thế này:
main
phương pháp:
public static void main(String[] args) {
Foo foo = Foo.newFoo();
foo.setEnabled(false);
foo.bar(); // won't print anything.
foo.setEnabled(true);
foo.bar(); // prints "Executing method bar"
}
BypassCheck
chú thích:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}
Foo
giao diện:
public interface Foo {
@BypassCheck boolean getEnabled();
@BypassCheck void setEnabled(boolean enable);
void bar();
void baz();
void bat();
// Needs Java 8 to have this convenience method here.
static Foo newFoo() {
FooFactory fooFactory = new FooFactory();
return fooFactory.makeFoo();
}
}
FooFactory
lớp học:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class FooFactory {
public Foo makeFoo() {
return (Foo) Proxy.newProxyInstance(
this.getClass().getClassLoader(),
new Class[]{Foo.class},
new FooInvocationHandler(new FooImpl()));
}
private static class FooImpl implements Foo {
private boolean enabled = false;
@Override
public boolean getEnabled() {
return this.enabled;
}
@Override
public void setEnabled(boolean enable) {
this.enabled = enable;
}
@Override
public void bar() {
System.out.println("Executing method bar");
}
@Override
public void baz() {
System.out.println("Executing method baz");
}
@Override
public void bat() {
System.out.println("Executing method bat");
}
}
private static class FooInvocationHandler implements InvocationHandler {
private FooImpl fooImpl;
public FooInvocationHandler(FooImpl fooImpl) {
this.fooImpl = fooImpl;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Foo.class
&& !method.isAnnotationPresent(BypassCheck.class) // no magic strings
&& !this.fooImpl.getEnabled()) {
return null;
}
return method.invoke(this.fooImpl, args);
}
}
}