Bir sınıftaki her yöntemin belirli bir kod bloğuyla başlatılmasının zarif bir yolu var mı?


143

Her yöntemin aynı şekilde başladığı bir sınıf var:

class Foo {
  public void bar() {
    if (!fooIsEnabled) return;
    //...
  }
  public void baz() {
    if (!fooIsEnabled) return;
    //...
  }
  public void bat() {
    if (!fooIsEnabled) return;
    //...
  }
}

fooIsEnabledSınıftaki her genel yöntemin bir parçasını talep etmenin (ve umarım her seferinde yazmamanın) güzel bir yolu var mı ?


45
En Boy Odaklı Programlamaya bakın (özellikle tavsiyeden önce ).
Sotirios Delimanolis

15
Bu kazan plakasını kaç yöntem kullanmanız gerekiyor? AOP'yi tanıma yolunda ilerlemeden önce, sadece bu küçük tekrar koduna sahip olmayı düşünebilirsiniz. Bazen küçük bir kopyala yapıştır en basit çözümdür.
bhspencer

51
Gelecekteki bakımcınızın, bir AOP çerçevesi öğrenmek zorunda kalmadan ekstra kazan plakası ile daha mutlu olacağından şüpheleniyorum.
bhspencer

7
Sınıfın her yöntemi ilk kod satırlarında aynı şeyi yapmak zorundaysa, o zaman kötü bir tasarıma sahibiz.
Tulains Córdova

7
@ user1598390: Soru burada konu dışı değil ve Programcı'nın kapsamı hakkında soruyu özellikle anlamlı kılan hiçbir şey yok.
Robert Harvey

Yanıtlar:


90

Zarif hakkında bir şey bilmiyorum, ama burada Java'nın yerleşik yöntemini kullanarak tüm yöntem çağrılarının durumunu kontrol ederek başlamasını java.lang.reflect.Proxysağlayan bir uygulama var .Fooenabled

main yöntem:

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 arayüz:

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 sınıf:

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);
        }
    }
}

Diğerlerinin de belirttiği gibi, endişelenmeniz gereken bir avuç yönteminiz varsa ihtiyacınız olan şey için aşırıya kaçmış gibi görünüyor.

Bununla birlikte, kesinlikle faydaları var:

  • Endişelerin belirli bir şekilde ayrılması sağlanır, çünkü Foometot uygulamalarının enabledçapraz kesişme endişesini kontrol etmesine gerek yoktur . Bunun yerine, yöntemin kodu yalnızca yöntemin birincil amacının ne olduğu konusunda endişelenmelidir, başka bir şey değildir.
  • Masum bir geliştiricinin Foosınıfa yeni bir yöntem eklemesinin ve yanlışlıkla enabledçek eklemeyi "unutmanın" bir yolu yoktur . enabledKontrol davranışı otomatik olarak yeni eklenen bir yöntem tarafından devralınır.
  • Başka bir çapraz kesilme endişesi eklemeniz gerekiyorsa veya enabled kontrolü , bunu güvenli bir şekilde ve tek bir yerde yapmak çok kolaydır.
  • Yerleşik Java işlevselliğiyle bu AOP benzeri davranışı elde edebilmeniz hoş bir şey. SpringKesinlikle iyi seçenekler olabilmesine rağmen, başka bir çerçeveyi entegre etmek zorunda kalmazsınız .

Adil olmak gerekirse, bazı dezavantajları:

  • Proxy çağrılarını işleyen uygulama kodlarından bazıları çirkin. Bazıları, FooImplsınıfın örneklenmesini önlemek için iç sınıflara sahip olmanın çirkin olduğunu da söyleyebilir .
  • Yeni bir yöntem eklemek istiyorsanız Foo, 2 noktada değişiklik yapmanız gerekir: uygulama sınıfı ve arabirim. Büyük bir anlaşma değil, ama yine de biraz daha fazla iş.
  • Proxy çağrıları ücretsiz değildir. Belirli bir performans yükü vardır. Genel kullanım için, fark edilmeyecektir. Daha fazla bilgi için buraya bakın .

DÜZENLE:

Fabian Streitel'in yorumu, yukarıdaki çözümümle 2 sıkıntıyı düşünmemi sağladı, itiraf edeceğim, kendimden mutlu değilim:

  1. Çağırma işleyici, "getEnabled" ve "setEnabled" yöntemlerindeki "etkin denetim" i atlamak için sihirli dizeler kullanır. Yöntem adları yeniden düzenlenirse bu kolayca kırılabilir.
  2. "Etkinleştir-kontrol" davranışını miras almaması gereken yeni yöntemlerin eklenmesi gerektiğinde, geliştiricinin bunu yanlış anlaması oldukça kolay olabilir ve en azından daha fazla büyü eklemek anlamına gelebilir. Teller.

Nokta # 1'i çözmek ve en azından nokta # 2 ile ilgili sorunu hafifletmek için BypassCheck,Foo arabirimdeki " etkinleştirilmiş kontrol ". Bu şekilde, sihirli dizelere hiç ihtiyacım yok ve bir geliştiricinin bu özel durumda doğru bir şekilde yeni bir yöntem eklemesi çok daha kolay hale geliyor.

Ek açıklama çözümünü kullanarak kod şöyle görünür:

main yöntem:

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 açıklama:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}

Foo arayüz:

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 sınıf:

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);
        }
    }
}

11
Bunun akıllıca bir çözüm olduğunu anlıyorum ama bunu gerçekten kullanır mısın?
bhspencer

1
hoş bir çözümdür, nesneyi her yöntemin başında bulunan bu ortak davranışla süslemek için dinamik proxy kalıbını kullanıyorsunuzdur.
Victor

11
@bhspencer: Çok meşru bir soru. Aslında istisna işleme, günlüğe kaydetme, işlem işleme vb. Yapmak için birçok kez kullandım. Ancak sınıfın karmaşıklıkta çok büyümesini ve tüm yöntemler arasında tutarlı davranışı sağlamak istiyorsam, bu çözümü umursamıyorum.
sstan

1
Buradaki kötülüğün% 97'sinin bir parçası olmamakla birlikte, bu proxy sınıfının performans sonuçları nelerdir?
corsiKa

5
@corsiKa: Güzel soru. Hiç şüphe yok ki, dinamik proxy kullanmanın doğrudan yöntem çağrılarından daha yavaş olması. Genel kullanım için, genel performans performansı algılanamaz olacaktır. İlgili SO iş parçacığı ilginizi çekiyorsa: Java dinamik proxy'nin performans maliyeti
sstan

51

Pek çok iyi öneri var .. sorununuzu çözmek için neler yapabileceğiniz Devlet Paterninde düşünmek ve uygulamaktır.

Bu kod snippet'ine bir göz atın .. belki de size bir fikir verecektir. Bu senaryoda, nesnenin iç durumuna göre tüm yöntemler uygulamasını değiştirmek istediğiniz gibi görünüyor. Lütfen bir nesnedeki yöntemlerin toplamının davranış olarak bilindiğini unutmayın.

public class Foo {

      private FooBehaviour currentBehaviour = new FooEnabledBehaviour (); // or disabled, or use a static factory method for getting the default behaviour

      public void bar() {
        currentBehaviour.bar();
      }
      public void baz() {
        currentBehaviour.baz();
      }
      public void bat() {
        currentBehaviour.bat();
      }

      public void setFooEnabled (boolean fooEnabled) { // when you set fooEnabel, you are changing at runtime what implementation will be called.
        if (fooEnabled) {
          currentBehaviour = new FooEnabledBehaviour ();
        } else {
          currentBehaviour = new FooDisabledBehaviour ();
        }
      }

      private interface FooBehaviour {
        public void bar();
        public void baz();
        public void bat();
      }

      // RENEMBER THAT instance method of inner classes can refer directly to instance members defined in its enclosing class
      private class FooEnabledBehaviour implements FooBehaviour {
        public void bar() {
          // do what you want... when is enabled
        }
        public void baz() {}
        public void bat() {}

      }

      private class FooDisabledBehaviour implements FooBehaviour {
        public void bar() {
          // do what you want... when is desibled
        }
        public void baz() {}
        public void bat() {}

      }
}

Umarım beğenirsin!

PD: Devlet Paterninin bir uygulamasıdır (bağlama bağlı olarak Strateji olarak da bilinir .. ancak ilkeler aynıdır).


1
OP her yöntemin başında aynı kod satırını tekrarlamak istemez ve çözümünüz her yöntemin başında aynı kod satırını tekrar etmeyi içerir.
Tulains Córdova

2
@ user1598390 Değerlendirmeyi tekrarlamaya gerek yoktur, FooEnabledBehaviour içinde bu nesnenin istemcisinin fooEnabled öğesini true olarak ayarladığını varsayıyorsunuz, bu yüzden çekmeye gerek yok. Aynı şey FooDisabledBehaviour sınıfına da gider. Tekrar kontrol edin, kodlayın.
Victor

2
Teşekkürler @ bayou.io, OP cevabına kadar bekleyelim. Topluluğun burada bir iş cehennemi yaptığını düşünüyorum, burada birçok iyi ipucu var!
Victor

2
@Dyesdyes ile aynı fikirdeyim bunu gerçekten önemsiz bir sınıftan başka bir şey için uygulamayı hayal bile edemiyorum. Bu düşünüyor, sadece çok sorunlu bar()bölgesi FooEnabledBehaviorve bar()içinde FooDisabledBehaviorikisi arasında belki de bir tek satır farklı olan, aynı kodu bir sürü paylaşabilir. Çok kolay olabilirsiniz, özellikle bu kod junior devs tarafından (benim gibi) korunacaksa, sürdürülemez ve test edilemez dev bir saçmalıkla sonuçlanabilir. Bu herhangi bir kodla olabilir, ancak bu gerçekten hızlı bir şekilde vidalamak çok kolay görünüyor. +1 olsa da, çünkü iyi bir öneri.
Chris Cirefice

1
Mmmmm ... çocuklar değilim ... ama önce yorumlar için teşekkürler. Benim için kodun boyutu "temiz" ve "okunabilir" olduğu sürece sorun değildir. Benim lehime herhangi bir dış sınıf kullanmıyorum .. bu şeyler daha erişilebilir hale getirmek gerektiğini iddia etmeliyim. Ve bazı ortak davranışlar varsa, bunu bir CommonBehaviourClass içinde kapsülleyelim ve ihtiyaç duyduğu yere delege edelim. GOF kitabında (öğrenme için en sevdiğim kitap değil, iyi tarifler var) bu örneği buldunuz: en.wikipedia.org/wiki/… . Burada yaptığım az çok aynı şey.
Victor

14

Evet, ama biraz iş, bu yüzden sizin için ne kadar önemli olduğuna bağlı.

Sınıfı bir arabirim olarak tanımlayabilir, bir temsilci uygulaması yazabilir ve daha sonra java.lang.reflect.Proxypaylaşılan bölümü yapan yöntemlerle arabirimi uygulamak ve daha sonra koşullu olarak temsilci çağırmak için kullanabilirsiniz.

interface Foo {
    public void bar();
    public void baz();
    public void bat();
}

class FooImpl implements Foo {
    public void bar() {
      //... <-- your logic represented by this notation above
    }

    public void baz() {
      //... <-- your logic represented by this notation above
    }

    // and so forth
}

Foo underlying = new FooImpl();
InvocationHandler handler = new MyInvocationHandler(underlying);
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
     new Class[] { Foo.class },
     handler);

Sizin MyInvocationHandlerböyle kutu bakmak şey (atlanmış hata işleme ve sınıf iskele, varsayarak fooIsEnablederişilebilir bir yerde tanımlanır):

public Object invoke(Object proxy, Method method, Object[] args) {
    if (!fooIsEnabled) return null;
    return method.invoke(underlying, args);
}

İnanılmaz güzel değil. Ancak, çeşitli yorumculardan farklı olarak, tekrarlamanın bu tür yoğunluktan daha önemli bir risk olduğunu ve gerçek sınıfınızın "hissini" üretebildiğiniz için bunu yapardım, bu biraz anlaşılmaz sarıcı eklendi. çok yerel olarak sadece birkaç satır kod.

Dinamik proxy sınıflarıyla ilgili ayrıntılar için Java belgelerine bakın .


14

Bu soru en boy yönelimli programlama ile yakından ilgilidir . AspectJ, Java'nın bir AOP uzantısıdır ve biraz terleme elde etmek için bir görünüm verebilirsiniz.

Bildiğim kadarıyla Java'da AOP için doğrudan destek yok. Bununla ilgili bazı GOF kalıpları vardır, örneğin Şablon Yöntemi ve Strateji, ancak size gerçekten kod satırlarını kaydetmeyecektir.

Java ve diğer birçok dilde, işlevlerde ihtiyacınız olan tekrarlayan mantığı tanımlayabilir ve doğru zamanda çağırdığınız disiplinli kodlama yaklaşımını benimseyebilirsiniz.

public void checkBalance() {
    checkSomePrecondition();
    ...
    checkSomePostcondition();
}

Ancak bu durum sizin durumunuza uymayacaktır çünkü çarpanlara ayrılmış kodun geri dönmesini istersiniz checkBalance. Makroları destekleyen dillerde (C / C ++ gibi) makro olarak tanımlayabilirsiniz checkSomePreconditionve checkSomePostconditionderleyici çağrılmadan önce ön işlemciyle değiştirilirler:

#define checkSomePrecondition \
    if (!fooIsEnabled) return;

Java bunu kutudan çıkarmaz. Bu birini rahatsız edebilir, ancak geçmişte tekrarlayan kodlama görevlerini otomatikleştirmek için otomatik kod oluşturma ve şablon motorları kullandım. Java dosyalarınızı uygun bir ön işlemciyle, örneğin Jinja2 ile derlemeden önce işlerseniz, C'de mümkün olana benzer bir şey yapabilirsiniz.

Olası saf Java yaklaşımı

Saf bir Java çözümü arıyorsanız, bulabileceğiniz şey muhtemelen özlü olmayacaktır. Ancak, yine de programınızın ortak bölümlerini etkisiz hale getirebilir ve kod çoğaltma ve hatalardan kaçınabilir. Böyle bir şey yapabilirsiniz (bu bir çeşit Strateji esinlenerek oluşturulmuştur). C # ve Java 8'de ve işlevlerin işlenmesinin biraz daha kolay olduğu diğer dillerde, bu yaklaşımın gerçekten hoş görünebileceğini unutmayın.

public interface Code {
    void execute();
}

...

public class Foo {
  private bool fooIsEnabled;

  private void protect(Code c) {
      if (!fooIsEnabled) return;
      c.execute();
  }

  public void bar() {
    protect(new Code {
      public void execute() {
        System.out.println("bar");
      }
    });
  }

  public void baz() {
    protect(new Code {
      public void execute() {
        System.out.println("baz");
      }
    });
  }

  public void bat() {
    protect(new Code {
      public void execute() {
        System.out.println("bat");
      }
    });
  }
}

Gerçek dünya senaryosu gibi

Endüstriyel bir robota veri çerçeveleri göndermek için bir sınıf geliştiriyorsunuz. Robotun bir komutu tamamlaması zaman alır. Komut tamamlandığında, size bir kontrol çerçevesi geri gönderir. Robot, bir önceki yürütülürken yeni bir komut alırsa hasar görebilir. Programınız DataLink, robota / çerçeveden çerçeve göndermek ve almak için bir sınıf kullanır . DataLinkÖrneğe erişimi korumanız gerekir .

Kullanıcı arayüzü iplik aramaları RobotController.left, right, upveya downkullanıcı düğmeleri tıkladığında, aynı zamanda çağırdığında BaseController.tickise gizli olarak yeniden etkinleştirmek komut yönlendirme amacıyla düzenli aralıklarla DataLinkmesela.

interface Code {
    void ready(DataLink dataLink);
}

class BaseController {
    private DataLink mDataLink;
    private boolean mReady = false;
    private Queue<Code> mEnqueued = new LinkedList<Code>();

    public BaseController(DataLink dl) {
        mDataLink = dl;
    }

    protected void protect(Code c) {
        if (mReady) {
            mReady = false;
            c.ready(mDataLink);
        }
        else {
            mEnqueue.add(c);
        }
    }

    public void tick() {
        byte[] frame = mDataLink.readWithTimeout(/* Not more than 50 ms */);

        if (frame != null && /* Check that it's an ACK frame */) {
          if (mEnqueued.isEmpty()) {
              mReady = true;
          }
          else {
              Code c = mEnqueued.remove();
              c.ready(mDataLink);
          }
        }
    }
}

class RobotController extends BaseController {
    public void left(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'left' by amount */);
        }});
    }

    public void right(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'right' by amount */);
        }});
    }

    public void up(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'up' by amount */);
        }});
    }

    public void down(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'down' by amount */);
        }});
    }
}

4
Bu sadece kutuyu yoldan aşağı atmıyor mu? Yani gelecekteki bakıcı (! FooIsEnabled) 'in geri dönüp dönmediğini hatırlamak zorundaydı; her fonksiyonun başında ve şimdi korumayı hatırlamaları gerekir (her fonksiyonun başında yeni kod {.... Bu nasıl yardımcı olur?
bhspencer

Ben analiz ve arka plan bağlam damix911 gibi ... i kod zaman içinde değişmeyecek ve bir koşul argümanı olarak geçen "executeIf" için yeniden adlandırmak varsayarak derleme zamanında (özel statik üyeler kullanarak) yeni Kod örnekleri inşa ediyorum (Predicate sınıfı gibi) ve Kurallar. Ama bu daha kişisel bir kesim ve lezzet.
Victor

1
@bhspencer Java'da oldukça sakar görünüyor ve çoğu durumda bu strateji, aksi takdirde basit bir kodun gerçekten aşırı bir mühendisliğidir. Pek çok program böyle bir modelden yararlanamaz. Ancak havalı bir şey protect, yeniden kullanımı ve dokümantasyonu daha kolay olan yeni bir sembol yaratmamızdır. Gelecekteki bakıcıya kritik kodun korunması gerektiğini söylerseniz, protectona ne yapacağını zaten söylüyorsunuz. Koruma kuralları değişirse, yeni kod korunmaya devam eder. Bu tam olarak işlevleri tanımlamanın arkasındaki mantıktır, ancak OP'nin returnişlevlerin yapamayacağı bir " geri" döndürmesi gerekiyordu.
damix911

11

Yeniden düzenleme yapmayı düşünürdüm. Bu desen kuru KURU desen kırıyor (Kendinizi tekrarlamayın). Bunun bu sınıf sorumluluğunu kırdığına inanıyorum. Ancak bu, kod kontrolünüze bağlıdır. Sorunuz çok açık - nereden çağrı yapıyorsunuz Foo?

Sanırım kodun var

foo.bar(); // does nothing if !fooEnabled
foo.baz(); // does also nothing
foo.bat(); // also

belki böyle bir şey demelisin:

if (fooEnabled) {
   foo.bat();
   foo.baz();
   ...
}

Ve temiz tut. Örneğin, günlük kaydı:

this.logger.debug(createResourceExpensiveDump())

a hata ayıklama etkinleştirilmişse logger kendi kendine sormaz . Sadece günlüğe kaydeder.

Bunun yerine, çağıran sınıfın bunu kontrol etmesi gerekir:

if (this.logger.isDebugEnabled()) {
   this.logger.debug(createResourceExpensiveDump())
}

Bu bir kütüphane ise ve bu sınıfın çağrısını kontrol edemiyorsanız IllegalStateException, bu çağrının yasa dışı olup olmadığını neden açıklayan bir soruyu atın .


6
Gözlerde kesinlikle daha basit ve daha kolay. Ancak OP'nin amacı, yeni yöntemler eklendikçe, etkin mantığın hiçbir zaman atlanmadığından emin olmaksa, bu yeniden düzenleme bunu zorlamayı kolaylaştırmaz.
sstan

4
Ayrıca günlük örneğiniz için, bunun çok daha fazla tekrarlama içerdiğini söyleyebilirim - her oturum açmak istediğinizde, kaydedicinin etkin olup olmadığını kontrol etmeniz gerekir. Herhangi bir sınıftaki yöntem sayısından daha fazla satır kaydetme eğilimindeyim ...
T. Kiley

4
Bu modülerliği bozar, çünkü artık arayan kişi foo'nun iç kısımları hakkında bir şeyler bilmek zorundadır (bu durumda fooEnabled olup olmadığı). Bu, en iyi uygulama kurallarına uymanın sorunu çözmediği, kuralların çakıştığı klasik bir örnektir. (Hala birinin "neden böyle düşünmedim?"
Diye

2
Bu, mantıklı olup olmadığı bağlamına büyük ölçüde bağlıdır.
Ian Goldby

3
Günlüğe kaydetme tam olarak benim örnekte tekrarlama istemiyorum örneğidir. Sadece LOG.debug ("....") yazmak istiyorum; - Ve kaydedici gerçekten hata ayıklamak isteyip istemediğimi kontrol etmelidir. - Başka bir örnek kapatma / temizleme. - AutoClosable kullanıyorsam, zaten kapalıysa bir istisna istemiyorum, sadece hiçbir şey yapmamalı.
Falco

6

IMHO'nun en zarif ve en iyi performans gösteren çözümü, bir tane oluşturmak için fabrika yöntemiyle birlikte birden fazla Foo uygulamasına sahip olmaktır:

class Foo {
  protected Foo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }

  public static void getFoo() {
    return fooEnabled ? new Foo() : new NopFoo();
  }
}

class NopFoo extends Foo {
  public void bar() {
    // Do nothing
  }
}

Veya bir varyasyon:

class Foo {
  protected Foo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }

  public static void getFoo() {
    return fooEnabled ? new Foo() : NOP_FOO;
  }

  private static Foo NOP_FOO = new Foo() {
    public void bar() {
      // Do nothing
    }
  };
}

Sstan'ın belirttiği gibi, bir arayüz kullanmak daha da iyi olurdu:

public interface Foo {
  void bar();

  static Foo getFoo() {
    return fooEnabled ? new FooImpl() : new NopFoo();
  }
}

class FooImpl implements Foo {
  FooImpl() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }
}

class NopFoo implements Foo {
  NopFoo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do nothing
  }
}

Bunu koşullarınızın geri kalanına uyarlayın (her seferinde yeni bir Foo oluşturuyor musunuz veya aynı örneği yeniden mi kullanıyorsunuz, vb.)


1
Bu Konrad'ın cevabı ile aynı şey değil. Bunu beğendim, ancak sınıf mirasını kullanmak yerine, başkalarının cevaplarında önerdiği gibi bir arayüz kullandıysanız daha güvenli olacağını düşünüyorum. Nedeni basit: Bir geliştiricinin Foogenişletilmiş sınıfta yöntemin op-olmayan sürümünü eklemesi ve eklemeyi unutması çok kolaydır , böylece istenen davranışı atlar.
sstan

2
@sstan Haklısın, bu daha iyi olurdu. Dikkatin dağılmasını önlemek için kristina'nın orijinal örneğini olabildiğince az değiştirmeyi böyle yaptım, ancak bu alakalı. Cevabınıza önerinizi ekleyeceğim.
Pepijn Schmitz

1
Bence bir noktayı kaçırıyorsun. isFooEnabledÖrneklemede olup olmadığını siz belirlersiniz Foo. Çok erken. Orijinal kodda, bu bir yöntem çalıştırıldığında yapılır. Bu isFooEnabledarada değeri değişebilir.
Nicolas Barbulesco

1
@NicolasBarbulesco Bunu bilmiyorsunuz, fooIsEnabled sabit olabilir. Veya etkili bir şekilde sabit hale getirecek şekilde kullanılabilir. Ya da her seferinde yeni bir Foo örneği almanın kolay olması için iyi tanımlanmış birkaç yere yerleştirilebilir. Veya her kullanıldığında yeni bir Foo örneği almak kabul edilebilir. Bilmiyorsunuz, bu yüzden yazdım: "bunu koşullarınızın geri kalanına uyarlayın."
Pepijn Schmitz

@PepijnSchmitz - Tabii ki, sabit fooIsEnabled olabilir . Ama hiçbir şey bize bunun sabit olduğunu söylemiyor. Bu yüzden genel durumu düşünüyorum.
Nicolas Barbulesco

5

Başka bir yaklaşımım var:

interface Foo {
  public void bar();
  public void baz();
  public void bat();
}

class FooImpl implements Foo {
  public void bar() {
    //...
  }
  public void baz() {
    //...
  }
  public void bat() {
    //...
  }
}

class NullFoo implements Foo {
  static NullFoo DEFAULT = new NullFoo();
  public void bar() {}
  public void baz() {}
  public void bat() {}
}

}

ve sonra yapabilirsin

(isFooEnabled ? foo : NullFoo.DEFAULT).bar();

Belki de, kullanılacak veya değişkenini isFooEnablediçeren bir Foodeğişkenle de değiştirebilirsiniz . Sonra çağrı tekrar daha basit:FooImplNullFoo.DEFAULT

Foo toBeUsed = isFooEnabled ? foo : NullFoo.DEFAULT;
toBeUsed.bar();
toBeUsed.baz();
toBeUsed.bat();

BTW, buna "Boş desen" denir.


Genel yaklaşım iyidir, ancak ifadeyi kullanmak (isFooEnabled ? foo : NullFoo.DEFAULT).bar();biraz sakar görünmektedir. Mevcut uygulamalardan birine yetki veren üçüncü bir uygulamaya sahip olun. Bir alanın değerini değiştirmek yerine isFooEnabledyetki verme hedefi değiştirilebilir. Bu, koddaki dal sayısını azaltır
SpaceTrucker

1
Ama Fooarama kodunda sınıfın iç pişirme sınırdışı ediyor ! Olup olmadığını nasıl bilebilirizisFooEnabled ? Bu, sınıftaki dahili bir alandır Foo.
Nicolas Barbulesco

3

@ Colin'in cevabına benzer fonksiyonel bir yaklaşımda, Java 8'in lambda işlevleriyle , koşullu özellik geçişini etkinleştirme / devre dışı bırakma kodunu, executeIfEnabledlambda'yı kabul eden ve koşullu olarak yürütülecek kodun uygulanabileceği bir koruma yöntemine ( ) sarmak mümkündür. geçti.

Sizin durumunuzda, bu yaklaşım herhangi bir kod satırını kaydetmeyecek olsa da, bunu KURUYLA, şimdi diğer özellik geçiş endişelerini artı AOP veya günlük kaydı, tanılama, profilleme ve diğerleri gibi hata ayıklama endişelerini merkezileştirme seçeneğiniz vardır.

Burada lambda kullanmanın bir yararı, executeIfEnabledyöntemin aşırı yüklenmesi ihtiyacını önlemek için kapakların kullanılabilmesidir .

Örneğin:

class Foo {
    private Boolean _fooIsEnabled;

    public Foo(Boolean isEnabled) {
        _fooIsEnabled = isEnabled;
    }

    private void executeIfEnabled(java.util.function.Consumer someAction) {
        // Conditional toggle short circuit
        if (!_fooIsEnabled) return;

        // Invoke action
        someAction.accept(null);
    }

    // Wrap the conditionally executed code in a lambda
    public void bar() {
        executeIfEnabled((x) -> {
            System.out.println("Bar invoked");
        });
    }

    // Demo with closure arguments and locals
    public void baz(int y) {
        executeIfEnabled((x) -> {
            System.out.printf("Baz invoked %d \n", y);
        });
    }

    public void bat() {
        int z = 5;
        executeIfEnabled((x) -> {
            System.out.printf("Bat invoked %d \n", z);
        });
    }

Bir testle:

public static void main(String args[]){
    Foo enabledFoo = new Foo(true);
    enabledFoo.bar();
    enabledFoo.baz(33);
    enabledFoo.bat();

    Foo disabledFoo = new Foo(false);
    disabledFoo.bar();
    disabledFoo.baz(66);
    disabledFoo.bat();
}

Ayrıca Damix'in yaklaşımına benzer, bir arabirime ve yöntem geçersiz kılınan anonim sınıf uygulamalarına gerek kalmadan.
StuartLC

2

Diğer cevaplarda da belirtildiği gibi, Strateji Tasarım Modeli bu kodu basitleştirmek için izlenmesi gereken uygun bir tasarım modelidir. Burada yansıma yoluyla yöntem çağırma kullanarak gösterdim, ancak aynı etkiyi elde etmek için kullanabileceğiniz herhangi bir mekanizma vardır.

class Foo {

  public static void main(String[] args) {
      Foo foo = new Foo();
      foo.fooIsEnabled = false;
      foo.execute("bar");
      foo.fooIsEnabled = true;
      foo.execute("baz");
  }

  boolean fooIsEnabled;

  public void execute(String method) {
    if(!fooIsEnabled) {return;}
    try {
       this.getClass().getDeclaredMethod(method, (Class<?>[])null).invoke(this, (Object[])null);
    }
    catch(Exception e) {
       // best to handle each exception type separately
       e.printStackTrace();
    }
  }

  // Changed methods to private to reinforce usage of execute method
  private void bar() {
    System.out.println("bar called");
    // bar stuff here...
  }
  private void baz() {
    System.out.println("baz called");
    // baz stuff here...
  }
  private void bat() {
    System.out.println("bat called");
    // bat stuff here...
  }
}

Daha önce de belirtildiği gibi, sizin için bunu yapan sınıflar varsa, düşünmeyle uğraşmak biraz gariptir Proxy.
SpaceTrucker

Nasıl yapabildin foo.fooIsEnabled ...? Bir a priori, bu nesnenin bir iç alanıdır, onu dışarıda göremeyiz ve istemiyoruz.
Nicolas Barbulesco

2

Sadece java işlevsel olmakta biraz daha iyi olsaydı. En OOO çözümünün, sadece foo etkinleştirildiğinde çağrılması için tek bir işlevi saran sınıf oluşturmaktır.

abstract class FunctionWrapper {
    Foo owner;

    public FunctionWrapper(Foo f){
        this.owner = f;
    }

    public final void call(){
        if (!owner.isEnabled()){
            return;
        }
        innerCall();
    }

    protected abstract void innerCall();
}

ve daha sonra uygulamak bar, bazve batanonim sınıflar olarak bu uzatmak olduğunu FunctionWrapper.

class Foo {
    public boolean fooIsEnabled;

    public boolean isEnabled(){
        return fooIsEnabled;
    }

    public final FunctionWrapper bar = new FunctionWrapper(this){
        @Override
        protected void innerCall() {
            // do whatever
        }
    };

    public final FunctionWrapper baz = new FunctionWrapper(this){
        @Override
        protected void innerCall() {
            // do whatever
        }
    };

    // you can pass in parms like so 
    public final FunctionWrapper bat = new FunctionWrapper(this){
        // some parms:
        int x,y;
        // a way to set them
        public void setParms(int x,int y){
            this.x=x;
            this.y=y;
        }

        @Override
        protected void innerCall() {
            // do whatever using x and y
        }
    };
}

Diğer bir fikir

Glglgl'nin null edilebilir çözümünü kullanın , ancak aşağıdaki sınıfın marka FooImplve NullFooiç sınıflarını (özel inşaatçılar ile) kullanın:

class FooGateKeeper {

    public boolean enabled;

    private Foo myFooImpl;
    private Foo myNullFoo;

    public FooGateKeeper(){
        myFooImpl= new FooImpl();
        myNullFoo= new NullFoo();
    }

    public Foo getFoo(){
        if (enabled){
            return myFooImpl;
        }
        return myNullFoo;
    }  
}

bu şekilde kullanmayı hatırlamak konusunda endişelenmenize gerek kalmaz (isFooEnabled ? foo : NullFoo.DEFAULT).


Söylediğinizi söyleyin: Foo foo = new Foo()Aramak bariçin yazarsınızfoo.bar.call()
Colin

1

Görünüşe göre Foo etkinleştirilmediğinde sınıf hiçbir şey yapmıyor, bu yüzden bunu neden Foo örneğini oluşturduğunuz veya aldığınız daha yüksek bir düzeyde ifade etmiyorsunuz?

class FooFactory
{
 static public Foo getFoo()
 {
   return isFooEnabled ? new Foo() : null;
 }
}
 ...
 Foo foo = FooFactory.getFoo();
 if(foo!=null)
 {
   foo.bar();
   ....
 }     

Bu sadece isFooEnabled sabitse çalışır. Genel bir durumda, kendi ek açıklamanızı oluşturabilirsiniz.


Konrad, not üzerinde gelişebilir misin?
Nicolas Barbulesco

Orijinal kod fooIsEnabled, bir yöntemin çağrılıp çağrılmadığını belirler. Bunu, somutlaştırılmadan önce yaparsınız Foo. Çok erken. Bu arada değer değişebilir.
Nicolas Barbulesco

Bence bir noktayı kaçırıyorsun. Bir a priori, nesnelerin isFooEnabledbir örnek alanıdır Foo.
Nicolas Barbulesco

1

Java sözdizimine aşina değilim. Java'da polimorfizm, statik özellik, soyut sınıf ve yöntem olduğu varsayımı:

    public static void main(String[] args) {
    Foo.fooIsEnabled = true; // static property, not particular to a specific instance  

    Foo foo = new bar();
    foo.mainMethod();

    foo = new baz();
    foo.mainMethod();

    foo = new bat();
    foo.mainMethod();
}

    public abstract class Foo{
      static boolean fooIsEnabled;

      public void mainMethod()
      {
          if(!fooIsEnabled)
              return;

          baMethod();
      }     
      protected abstract void baMethod();
    }
    public class bar extends Foo {
        protected override baMethod()
        {
            // bar implementation
        }
    }
    public class bat extends Foo {
        protected override baMethod()
        {
            // bat implementation
        }
    }
    public class baz extends Foo {
        protected override baMethod()
        {
            // baz implementation
        }
    }

Kim ediliyor söylüyor etkin olan statik sınıfının özellik?
Nicolas Barbulesco

Ne new bar()anlama geliyor?
Nicolas Barbulesco

Java'da Namebüyük harfli bir sınıf yazıyoruz .
Nicolas Barbulesco

Bunun için arama kodunun çok fazla değiştirilmesi gerekiyor. Biz normalde yöntemini çağırın: bar(). Eğer bunu değiştirmeniz gerekiyorsa, mahvoldunuz.
Nicolas Barbulesco

1

Temel olarak, ayarlanmışsa, işlev çağrısının atlanması gereken bir işaretiniz vardır. Bu yüzden çözümümün saçma olacağını düşünüyorum, ama işte burada.

Foo foo = new Foo();

if (foo.isEnabled())
{
    foo.doSomething();
}

Herhangi bir işlevi yürütmeden önce bazı kodları yürütmek istemeniz durumunda, basit bir Proxy uygulaması aşağıda açıklanmıştır.

class Proxy<T>
{
    private T obj;
    private Method<T> proxy;

    Proxy(Method<T> proxy)
    {
        this.ojb = new T();
        this.proxy = proxy;
    }

    Proxy(T obj, Method<T> proxy)
    {
        this.obj = obj;
        this.proxy = proxy;
    }

    public T object ()
    {
        this.proxy(this.obj);
        return this.obj;
    }
}

class Test
{
    public static void func (Foo foo)
    {
        // ..
    }

    public static void main (String [] args)
    {
        Proxy<Foo> p = new Proxy(Test.func);

        // how to use
        p.object().doSomething();
    }
}

class Foo
{
    public void doSomething ()
    {
        // ..
    }
}

İlk kod bloğunuz için görünür bir yönteme ihtiyacınız vardır isEnabled(). Bir priori, etkinleştirilen iç pişirme Foo, maruz değil.
Nicolas Barbulesco

Çağıran kod olamaz ve istemiyor , nesne olup olmadığını bilmek etkin .
Nicolas Barbulesco

0

Temsilci (işlev gösterici) kullanarak başka bir çözüm var. Önce doğrulamayı yapan ve sonra çağrılacak işleve (parametreye) göre ilgili yöntemi çağıran benzersiz bir yöntem olabilir. C # kodu:

internal delegate void InvokeBaxxxDelegate();

class Test
{
    private bool fooIsEnabled;

    public Test(bool fooIsEnabled)
    {
        this.fooIsEnabled = fooIsEnabled;
    }

    public void Bar()
    {
        InvokeBaxxx(InvokeBar);
    }

    public void Baz()
    {
        InvokeBaxxx(InvokeBaz);
    }

    public void Bat()
    {
        InvokeBaxxx(InvokeBat);
    }

    private void InvokeBaxxx(InvokeBaxxxDelegate invoker)
    {
        if (!fooIsEnabled) return;
        invoker();
    }

    private void InvokeBar()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Bar");
    }

    private void InvokeBaz()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Baz");
    }

    private void InvokeBat()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Bat");
    }
}

2
Doğru, Java olarak işaretlenmiş ve bu yüzden Java bilmiyorum çünkü vurgulamak ve "C # Kod" yazdı. Bir Tasarım Deseni sorusu olduğundan dil önemli değildir.
ehh

Ah! Bunun için üzgünüm, sadece yardım etmeye ve bir çözüm bulmaya çalıştığımı anlıyorum. Teşekkür ederim
ehh
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.