ฉันไม่รู้เกี่ยวกับความสง่างาม แต่นี่คือการใช้งานโดยใช้ Java ในตัวjava.lang.reflect.Proxy
ซึ่งบังคับให้การเรียกใช้เมธอดทั้งหมดFoo
เริ่มต้นโดยการตรวจสอบenabled
สถานะ
main
วิธี:
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
อินเตอร์เฟซ:
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
ระดับ:
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);
}
}
}
ตามที่คนอื่น ๆ ได้ชี้ให้เห็นมันดูเหมือนจะเกินความจำเป็นสำหรับสิ่งที่คุณต้องการหากคุณมีวิธีการเพียงไม่กี่อย่างที่ต้องกังวล
ที่กล่าวว่ามีประโยชน์อย่างแน่นอน:
- การแยกความกังวลบางอย่างเกิดขึ้นได้เนื่องจาก
Foo
การใช้วิธีการไม่ต้องกังวลเกี่ยวกับการenabled
ตรวจสอบข้ามความกังวล แต่รหัสของวิธีการนั้นต้องกังวลว่าจุดประสงค์หลักของวิธีการนั้นคืออะไรไม่มีอะไรเพิ่มเติม
- ไม่มีวิธีใดสำหรับนักพัฒนาผู้บริสุทธิ์ที่จะเพิ่มวิธีการใหม่ใน
Foo
ชั้นเรียนและ "ลืม" ผิดพลาดเพื่อเพิ่มการenabled
ตรวจสอบ enabled
พฤติกรรมการตรวจสอบคือการสืบทอดโดยอัตโนมัติด้วยวิธีการใด ๆ เพิ่มเข้ามาใหม่
- หากคุณต้องการเพิ่มข้อกังวลข้ามข้อกังวลอื่น ๆ หรือหากคุณต้องการปรับปรุงการ
enabled
ตรวจสอบมันเป็นเรื่องง่ายมากที่จะทำอย่างปลอดภัยและในที่เดียว
- เป็นเรื่องที่ดีที่คุณจะได้รับพฤติกรรมแบบ AOP นี้ด้วยฟังก์ชั่นจาวาในตัว คุณไม่ได้ถูกบังคับให้ต้องรวมกรอบอื่น ๆ เช่น
Spring
แม้ว่ามันจะเป็นตัวเลือกที่ดีเช่นกัน
เพื่อความยุติธรรมข้อเสียบางประการคือ:
- รหัสการนำไปใช้บางอย่างที่จัดการกับการเรียกใช้พร็อกซีนั้นน่าเกลียด บางคนก็บอกว่ามีชั้นในเพื่อป้องกันการ instantiation ของ
FooImpl
คลาสนั้นน่าเกลียด
- หากคุณต้องการเพิ่มวิธีการใหม่
Foo
คุณต้องทำการเปลี่ยนแปลงใน 2 จุด: คลาสการใช้งานและอินเทอร์เฟซ ไม่ใช่เรื่องใหญ่ แต่ก็ยังทำงานได้อีกเล็กน้อย
- การเรียกใช้พร็อกซีไม่ฟรี มีค่าใช้จ่ายประสิทธิภาพบางอย่าง สำหรับการใช้งานทั่วไปแม้ว่าจะไม่เห็นได้ชัด ดูที่นี่สำหรับข้อมูลเพิ่มเติม
แก้ไข:
ความคิดเห็นของ Fabian Streitel ทำให้ฉันคิดเกี่ยวกับการรบกวน 2 อย่างด้วยวิธีแก้ไขปัญหาข้างต้นของฉันซึ่งฉันจะยอมรับว่าฉันไม่มีความสุขกับตัวเอง:
- ตัวจัดการคำร้องขอใช้สตริงมายากลเพื่อข้าม "enabled-check" บนวิธี "getEnabled" และ "setEnabled" สิ่งนี้สามารถแตกหักได้ง่ายหากชื่อเมธอดถูก refactored
- หากมีกรณีที่ต้องเพิ่มวิธีการใหม่ที่ไม่ควรสืบทอดพฤติกรรม "เปิดใช้งานตรวจสอบ" แล้วมันอาจเป็นเรื่องง่ายสำหรับนักพัฒนาที่จะทำสิ่งนี้ผิดและอย่างน้อยที่สุดก็หมายถึงการเพิ่มเวทมนตร์มากขึ้น เงื่อนไข
ในการแก้ไขจุด # 1 และอย่างน้อยก็ช่วยให้ปัญหาง่ายขึ้นด้วยจุด # 2 ฉันจะสร้างคำอธิบายประกอบBypassCheck
(หรือบางอย่างที่คล้ายกัน) ที่ฉันสามารถใช้เพื่อทำเครื่องหมายวิธีในFoo
อินเทอร์เฟซที่ฉันไม่ต้องการทำ " เปิดใช้งานการตรวจสอบ ". ด้วยวิธีนี้ฉันไม่ต้องการสายเวทเลยและมันจะง่ายขึ้นมากสำหรับนักพัฒนาในการเพิ่มวิธีการใหม่ในกรณีพิเศษนี้อย่างถูกต้อง
เมื่อใช้โซลูชันคำอธิบายประกอบโค้ดจะมีลักษณะดังนี้:
main
วิธี:
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
คำอธิบายประกอบ:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}
Foo
อินเตอร์เฟซ:
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
ระดับ:
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);
}
}
}