Saya tidak tahu tentang elegan, tapi di sini ada implementasi yang bekerja menggunakan Java built-in java.lang.reflect.Proxy
yang memberlakukan bahwa semua pemanggilan metode Foo
dimulai dengan memeriksa enabled
keadaan.
main
metode:
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
antarmuka:
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
kelas:
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);
}
}
}
Seperti yang telah ditunjukkan oleh orang lain, sepertinya dibutuhkan terlalu banyak untuk apa yang Anda butuhkan jika Anda hanya memiliki sedikit metode untuk dikhawatirkan.
Yang mengatakan, pasti ada manfaatnya:
- Pemisahan perhatian tertentu tercapai, karena
Foo
implementasi metode tidak perlu khawatir tentang masalah enabled
lintas sektoral. Sebaliknya, kode metode hanya perlu khawatir tentang apa tujuan utama metode ini, tidak lebih.
- Tidak ada cara bagi pengembang yang tidak bersalah untuk menambahkan metode baru ke
Foo
kelas dan keliru "lupa" untuk menambahkan enabled
cek. The enabled
perilaku cek secara otomatis diwarisi oleh metode baru ditambahkan.
- Jika Anda perlu menambahkan masalah lintas sektoral lainnya, atau jika Anda perlu meningkatkan
enabled
pemeriksaan, sangat mudah untuk melakukannya dengan aman dan di satu tempat.
- Sangat menyenangkan bahwa Anda bisa mendapatkan perilaku seperti AOP ini dengan fungsionalitas Java bawaan. Anda tidak dipaksa harus mengintegrasikan beberapa kerangka kerja lain seperti
Spring
, meskipun mereka pasti bisa menjadi pilihan yang baik juga.
Agar adil, beberapa kerugiannya adalah:
- Beberapa kode implementasi yang menangani pemanggilan proxy jelek. Beberapa juga akan mengatakan bahwa memiliki kelas batin untuk mencegah instantiasi
FooImpl
kelas adalah jelek.
- Jika Anda ingin menambahkan metode baru
Foo
, Anda harus membuat perubahan dalam 2 titik: kelas implementasi dan antarmuka. Bukan masalah besar, tapi masih sedikit lebih banyak pekerjaan.
- Doa proxy tidak gratis. Ada overhead kinerja tertentu. Untuk penggunaan umum, itu tidak akan terlihat. Lihat di sini untuk informasi lebih lanjut.
EDIT:
Komentar Fabian Streitel membuat saya berpikir tentang 2 gangguan dengan solusi saya di atas yang, saya akui, saya tidak senang dengan diri saya sendiri:
- Pawang panggilan menggunakan string ajaib untuk melewati "diaktifkan-periksa" pada metode "getEnabled" dan "setEnabled". Ini dapat dengan mudah dipecahkan jika nama metode refactored.
- Jika ada kasus di mana metode baru perlu ditambahkan yang seharusnya tidak mewarisi perilaku "diaktifkan-periksa", maka itu bisa sangat mudah bagi pengembang untuk mendapatkan kesalahan ini, dan setidaknya, itu berarti menambahkan lebih banyak sihir string.
Untuk menyelesaikan poin # 1, dan untuk setidaknya meringankan masalah dengan poin # 2, saya akan membuat anotasi BypassCheck
(atau yang serupa) yang bisa saya gunakan untuk menandai metode di Foo
antarmuka yang saya tidak ingin melakukan " mengaktifkan cek ". Dengan cara ini, saya tidak memerlukan string sihir sama sekali, dan itu menjadi jauh lebih mudah bagi pengembang untuk menambahkan metode baru dengan benar dalam kasus khusus ini.
Menggunakan solusi anotasi, kode akan terlihat seperti ini:
main
metode:
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
anotasi:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}
Foo
antarmuka:
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
kelas:
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);
}
}
}