클래스의 모든 메소드를 특정 코드 블록으로 시작하는 우아한 방법이 있습니까?


143

모든 방법이 동일한 방식으로 시작되는 클래스가 있습니다.

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

fooIsEnabled수업의 모든 공개 방법에 대한 부분 을 요구하고 (매번 작성하지 않기를 바랍니다) 좋은 방법이 있습니까?


45
Aspect 지향 프로그래밍을 살펴보십시오 (특히 조언 전에 ).
Sotirios Delimanolis 2016 년

15
이 상용구를 몇 개나 사용해야합니까? AOP 도입 경로를 따라 가기 전에 반복되는이 작은 코드를 고려하는 것이 좋습니다. 때로는 약간의 복사 및 붙여 넣기가 가장 간단한 솔루션입니다.
bhspencer

51
미래의 관리자가 AOP 프레임 워크를 배우는 것보다 보일러 판이 더 좋을 것 같습니다.
bhspencer

7
클래스의 모든 메소드가 첫 번째 코드 행에서 동일한 작업을 수행 해야하는 경우 나쁜 디자인이 있습니다.
Tulains Córdova 2016

7
@ user1598390 :이 질문은 여기서 논외 주제가 아니며 프로그래머의 범위에 대해 질문이 특히 의미있게 만드는 것은 없습니다.
Robert Harvey

답변:


90

나는 우아한 것에 대해 잘 모르지만 여기에 모든 메소드 호출 이 상태 를 확인하여 시작 java.lang.reflect.Proxy하도록 강제 하는 Java 내장 기능을 사용하여 작동하는 구현이 있습니다.Fooenabled

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점검 을 강화해야하는 경우 안전하고 한 곳에서 쉽게 수행 할 수 있습니다.
  • 내장 된 Java 기능으로이 AOP와 유사한 동작을 얻을 수 있다는 것은 좋은 일입니다. 와 같은 다른 프레임 워크를 통합하지 않아도 Spring되지만 좋은 옵션 일 수도 있습니다.

공정하게하기 위해 몇 가지 단점은 다음과 같습니다.

  • 프록시 호출을 처리하는 일부 구현 코드는보기 흉하다. 일부는 클래스의 인스턴스화를 막기 위해 내부 클래스를 갖는 FooImpl것이 추악 하다고 말합니다 .
  • 에 새로운 메소드를 추가 Foo하려면 구현 클래스와 인터페이스의 두 지점을 변경해야합니다. 큰 문제는 아니지만 여전히 조금 더 많은 작업입니다.
  • 프록시 호출은 무료가 아닙니다. 특정 성능 오버 헤드가 있습니다. 그러나 일반적인 용도로는 눈에 띄지 않습니다. 자세한 내용은 여기 를 참조하십시오.

편집하다:

Fabian Streitel의 의견은 위의 해결책으로 2 가지 성가심에 대해 생각하게 만들었습니다.

  1. 호출 핸들러는 매직 문자열을 사용하여 "getEnabled"및 "setEnabled"메소드에서 "enabled-check"를 건너 뜁니다. 메소드 이름이 리팩터링되면 쉽게 중단 될 수 있습니다.
  2. "활성화 된 검사"동작을 상속하지 않아야하는 새로운 방법을 추가해야하는 경우 개발자가이 문제를 쉽게 파악할 수 있으며 최소한 마법을 더 추가하는 것을 의미합니다. 문자열.

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

11
나는 이것이 영리한 해결책이라는 것을 알지만 실제로 이것을 사용할 것입니까?
bhspencer

1
좋은 해결 방법입니다. 동적 프록시 패턴을 사용하여 각 방법의 시작 부분에서 발견되는 이러한 일반적인 동작으로 객체를 장식합니다.
Victor

11
@bhspencer : 매우 합법적 인 질문입니다. 실제로 예외 처리, 로깅, 트랜잭션 처리 등을 수행하는 데 여러 번 사용했습니다. 작은 클래스의 경우 과도한 것으로 보이며 매우 좋을 수도 있습니다. 그러나 클래스가 훨씬 복잡해질 것으로 예상하고 모든 방법에서 일관된 동작을 보장하려면이 솔루션을 신경 쓰지 않습니다.
sstan 2016 년

1
여기서 악의 97 %에 속하지는 않지만이 프록시 클래스의 성능 영향은 무엇입니까?
corsiKa

5
@ corsiKa : 좋은 질문입니다. 동적 프록시 사용이 직접 메소드 호출보다 느리다는 것은 의심의 여지가 없습니다. 그러나 일반적인 용도로는 성능 오버 헤드가 눈에 띄지 않습니다. 관심있는 경우 관련 SO 스레드 : Java 동적 프록시의 성능 비용
sstan

51

좋은 제안이 많이 있습니다. 문제를 해결하기 위해 할 수있는 일은 상태 패턴에서 생각하고 구현하는 것입니다.

이 코드 스 니펫을 살펴보십시오. 아마도 아이디어를 얻을 수 있습니다. 이 시나리오에서는 객체의 내부 상태를 기반으로 전체 메소드 구현을 수정하려는 것으로 보입니다. 객체의 메소드 합계는 동작이라고합니다.

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() {}

      }
}

네가 좋아하길 바래!

PD : 상태 패턴을 구현 한 것입니다 (상황에 따라 전략이라고도합니다. 그러나 원리는 동일합니다).


1
OP는 모든 방법의 시작 부분에서 동일한 코드 줄을 반복하지 않아도되기를 원하며 솔루션에는 모든 방법의 시작 부분에서 동일한 코드 줄을 반복해야합니다.
Tulains Córdova 2016

2
@ user1598390 FooEnabledBehaviour 내에서이 재귀를 리 펫팅 할 필요가 없습니다.이 객체의 클라이언트가 fooEnabled를 true로 설정했다고 가정하면 청크가 필요하지 않습니다. FooDisabledBehaviour 클래스도 마찬가지입니다. 다시 확인하고 코드를 작성하십시오.
Victor

2
@ bayou.io에게 감사드립니다. OP 답변이 나올 때까지 기다리겠습니다. 나는 커뮤니티가 여기서 일을하고 있다고 생각합니다. 여기에 좋은 팁이 많이 있습니다!
Victor

2
@dyesdyes와 동의하여 실제로 사소한 클래스를 제외하고는 이것을 구현하는 것을 상상할 수 없습니다. bar()in FooEnabledBehaviorbar()in FooDisabledBehavior이 동일한 코드를 많이 공유 할 수 있다는 점 을 고려하면 너무 문제가 될 수 있습니다. 특히이 코드가 주니어 개발자 (예 : 나 자신)에 의해 유지 관리되는 경우 유지 관리가 불가능하고 테스트 할 수없는 거대한 쓰레기로 끝날 수 있습니다. 그것은 모든 코드에서 발생할 수 있지만 실제로는 너무 빨리 망칠 수 있습니다. 좋은 제안이기 때문에 +1.
Chris Cirefice

1
음 .. 난 사람이 아니에요.하지만 먼저 의견을 보내 주셔서 감사합니다. 나에게있어서 코드의 크기는 "깨끗하고"읽을 수있는 한 문제가되지 않는다. 내 호의에 따라 나는 외부 클래스를 사용하지 않는다고 주장해야한다. 그리고 일반적인 행동이 있다면 그것을 CommonBehaviourClass에 캡슐화하여 필요한 곳에 위임 해 봅시다. GoF의 책 (나의 마음에 드는 학습을위한 책,하지만 좋은 요리법이있다) 당신은이 예제를 발견 en.wikipedia.org/wiki/...를 . 내가 여기서하는 것과 거의 동일합니다.
Victor

14

예,하지만 약간의 일이므로, 그것이 당신에게 얼마나 중요한지에 달려 있습니다.

클래스를 인터페이스로 정의하고 대리자 구현을 작성한 다음 java.lang.reflect.Proxy공유 부분을 수행하고 조건부 대리자를 호출하는 메소드로 인터페이스를 구현하는 데 사용할 수 있습니다.

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

귀하의 MyInvocationHandler이 같은 캔 모양의 무언가 (생략 오류 처리 및 클래스 비계는 가정하에 fooIsEnabled곳 접근 정의) :

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

엄청 예쁘지 않습니다. 그러나 다른 논평자와는 달리 반복은 이러한 종류의 밀도보다 더 중요한 위험이라고 생각하므로 다소 어설픈 래퍼를 추가하여 실제 클래스의 "느낌"을 만들 수 있습니다. 몇 줄의 코드로 매우 로컬에 있습니다.

동적 프록시 클래스에 대한 자세한 내용은 Java 설명서 를 참조하십시오 .


14

이 질문은 측면 지향 프로그래밍 과 밀접한 관련이 있습니다. AspectJ는 Java의 AOP 확장이며, 약간의 영감을 얻을 수 있습니다.

내가 아는 한 Java에서는 AOP를 직접 지원하지 않습니다. 템플릿 방법전략 과 같이 관련 GOF 패턴이 있지만 실제로 코드 줄을 저장하지는 않습니다.

Java 및 대부분의 다른 언어에서는 함수에 필요한 반복 논리를 정의하고 적절한 시간에 호출하는 소위 규칙 화 된 코딩 방식을 채택 할 수 있습니다.

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

그러나 인수 분해 된 코드가에서 반환되기를 원하기 때문에 이것은 귀하의 경우에 맞지 않습니다 checkBalance. 매크로를 지원하는 언어 (C / C ++ checkSomePreconditioncheckSomePostcondition같은)에서 매크로를 정의 하고 매크로로 사용할 수 있으며 컴파일러가 호출되기 전에 전처리기로 대체됩니다.

#define checkSomePrecondition \
    if (!fooIsEnabled) return;

Java는 이것을 즉시 사용할 수 없습니다. 이것은 누군가를 화나게 할 수 있지만 자동 코드 생성 및 템플릿 엔진을 사용하여 과거의 반복적 인 코딩 작업을 자동화했습니다. 적합한 프리 프로세서 (예 : Jinja2)로 컴파일하기 전에 Java 파일을 처리하는 경우 C에서 가능한 것과 유사한 작업을 수행 할 수 있습니다.

가능한 순수한 자바 접근

순수한 Java 솔루션을 찾고 있다면 간결하지 않을 것입니다. 그러나 여전히 프로그램의 공통 부분을 제외하고 코드 복제 및 버그를 피할 수 있습니다. 이런 식으로 할 수 있습니다 ( 전략- 영감을 얻은 패턴입니다). C # 및 Java 8 및 함수를 처리하기가 더 쉬운 다른 언어에서는이 방법이 실제로 멋지게 보일 수 있습니다.

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

실제 시나리오의 종류

산업용 로봇에 데이터 프레임을 전송하는 클래스를 개발 중입니다. 로봇이 명령을 완료하는 데 시간이 걸립니다. 명령이 완료되면 제어 프레임을 다시 보냅니다. 이전 명령이 아직 실행되는 동안 새 명령을 받으면 로봇이 손상 될 수 있습니다. 프로그램은 DataLink클래스를 사용 하여 로봇과 프레임을주고받습니다. DataLink인스턴스에 대한 액세스를 보호해야 합니다.

사용자 인터페이스 스레드 호출 RobotController.left, right, up또는 down사용자는 버튼을 클릭 할뿐만 아니라, 통화시 BaseController.tick전용 명령을 재실행 전달하기 위해, 정기적으로 DataLink예.

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
이것은 단지 길을 따라 캔을 차는 것이 아닙니다. 다시 말해서 미래의 관리자는 if (! fooIsEnabled) 반환을 기억해야했습니다. 모든 기능의 시작 부분에서 이제는 모든 기능의 시작 부분에서 새로운 코드 {...를 보호해야한다는 것을 기억해야합니다.
bhspencer

분석 및 배경 컨텍스트 damix911이 마음에 듭니다 ... 시간이 지나도 코드가 변경되지 않고 인수를 조건으로 전달하는 "executeIf"로 변경 이름을 바꾼다고 가정하고 컴파일 할 때 (개인 정적 멤버를 사용하여) 새로운 코드 인스턴스를 작성합니다. (Predicate 클래스와 유사) 및 코드. 그러나 그것은 더 개인적인 벌목과 맛입니다.
Victor

1
@bhspencer Java에서는 다소 어색해 보이지만 대부분의 경우이 전략은 단순한 코드를 과도하게 엔지니어링 한 것입니다. 이러한 패턴으로 많은 프로그램을 활용할 수있는 것은 아닙니다. 멋진 점은 protect재사용과 문서화가 더 쉬운 새로운 심볼 을 만든 것 입니다. 미래의 관리자에게 중요한 코드를 보호해야한다고 protect지시하면 이미 수행 할 작업을 말하고있는 것입니다. 보호 규칙이 변경되면 새 코드는 계속 보호됩니다. 이것이 바로 함수 정의의 근거이지만 OP return는 함수가 수행 할 수없는 "반환 "이 필요했습니다.
damix911

11

리팩토링을 고려할 것입니다. 이 패턴은 DRY 패턴을 크게 깨뜨리고 있습니다 (반복하지 마십시오). 나는 이것이이 계급의 책임을 어 기고 있다고 믿는다. 그러나 이것은 코드 제어에 달려 있습니다. 귀하의 질문은 매우 개방적입니다-어디에서 Foo인스턴스 를 호출 합니까?

나는 당신이 같은 코드를 가지고 있다고 가정

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

어쩌면 다음과 같이 호출해야 할 수도 있습니다.

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

그리고 깨끗하게 유지하십시오. 예를 들어, 로깅 :

this.logger.debug(createResourceExpensiveDump())

디버그가 활성화 된 경우 a logger 는 자신에게 묻지 않습니다 . 그냥 기록합니다.

대신, 호출 클래스는 이것을 확인해야합니다.

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

이것이 라이브러리이고이 클래스의 호출을 제어 할 수없는 IllegalStateException경우이 호출이 불법이고 문제를 일으키는 이유를 설명 하는를 던지십시오 .


6
눈에 아주 간단하고 쉽습니다. 그러나 OP의 목표가 새로운 방법이 추가 될 때 활성화 된 로직을 우회하지 않도록하는 것이라면이 리팩토링으로이를 쉽게 수행 할 수 없습니다.
sstan 2016 년

4
또한 로그 예제의 경우 훨씬 더 많은 반복이 필요하다고 말하고 싶습니다. 로그를 기록 할 때마다 로거가 활성화되어 있는지 확인해야합니다. 나는 어떤 클래스에서든 메소드의 수보다 많은 행을 기록하는 경향이 있습니다.
T. Kiley

4
이제 호출자가 foo의 내부 (이 경우 fooEnabled인지 여부)에 대해 알아야하기 때문에 모듈성이 손상됩니다. 규칙이 충돌하기 때문에 모범 사례 규칙을 따르면 문제가 해결되지 않는 전형적인 예입니다. (나는 아직도 누군가가 "왜 그렇게 생각하지 않습니까?"라고 대답하기를
바라고

2
글쎄, 그것은 그것이 의미가 있는지에 대한 맥락에 크게 의존합니다.
Ian Goldby 2016 년

3
로깅은 코드에서 반복하지 않으려는 정확한 예입니다. LOG.debug ( "....");를 작성하고 싶습니다. -로거는 내가 정말로 디버깅하고 싶은지 확인해야합니다. -다른 예는 닫기 / 정리입니다. -AutoClosable을 사용하는 경우 이미 닫힌 경우 예외를 원하지 않습니다. 아무것도하지 않아야합니다.
팔코

6

이를위한 가장 우아하고 성능이 뛰어난 IMHO 솔루션은 하나 이상의 Foo 구현 및 하나의 팩토리 메소드를 작성하는 것입니다.

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

또는 변형 :

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이 지적했듯이 인터페이스를 사용하는 것이 더 좋습니다.

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

이 상황을 나머지 상황에 맞게 조정하십시오 (매번 새 Foo를 생성하거나 동일한 인스턴스를 재사용하는 등).


1
이것은 Konrad의 답변과 동일하지 않습니다. 나는 이것을 좋아하지만 클래스 상속을 사용하는 대신 다른 사람들이 제안한대로 인터페이스를 사용하면 더 안전하다고 생각합니다. 이유는 간단합니다. 개발자가에 메소드 Foo를 추가하고 확장 된 클래스에 메소드의 no-op 버전을 추가하는 것을 잊어서 원하는 동작을 무시하는 것이 너무 쉽습니다 .
sstan

2
@sstan 당신 말이 맞아요. 혼란을 피하기 위해 크리스티나의 원래 예제를 가능한 한 적게 수정하는 것이 좋았지 만 이는 관련이 있습니다. 나는 당신의 제안을 내 대답에 추가 할 것입니다.
Pepijn Schmitz 2016 년

1
난 당신이 요점을 그리워 생각합니다. isFooEnabled의 인스턴스화 시점을 결정합니다 Foo. 너무 이르다. 원래 코드에서는 메소드가 실행될 때 수행됩니다. 그 isFooEnabled동안 의 값은 변경 될 수 있습니다.
Nicolas Barbulesco

1
@NicolasBarbulesco 당신은 fooIsEnabled가 상수 일 수 있다는 것을 모른다. 또는 효과적으로 일정하게 사용할 수 있습니다. 또는 잘 정의 된 몇몇 장소에 설정하여 매번 새로운 Foo 인스턴스를 쉽게 얻을 수 있습니다. 또는 Foo가 사용될 때마다 새 Foo 인스턴스를 얻는 것이 허용 될 수 있습니다. 당신은 모른다, 그래서 내가 쓴 이유는 "이것은 당신의 다른 상황에 적응하십시오."
Pepijn Schmitz

@PepijnSchmitz-물론 일정 fooIsEnabled 할 수 있습니다. 그러나 그것이 일정하다는 말은 없습니다. 그래서 나는 일반적인 경우를 고려합니다.
Nicolas Barbulesco

5

다른 접근법이 있습니다.

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() {}
}

}

그리고 당신은 할 수 있습니다

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

어쩌면 사용할 또는를 보유하고 isFooEnabled있는 Foo변수로를 대체 할 수도 FooImpl있습니다 NullFoo.DEFAULT. 그런 다음 통화가 다시 간단 해집니다.

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

BTW, 이것을 "널 패턴"이라고합니다.


전반적인 접근 방식은 좋지만 표현을 사용하는 (isFooEnabled ? foo : NullFoo.DEFAULT).bar();것은 약간 어색해 보입니다. 기존 구현 중 하나에 위임하는 세 번째 구현이 있습니다. 필드 값을 변경하는 대신 isFooEnabled위임 대상을 변경할 수 있습니다. 이 코드에서 지점의 수를 감소
SpaceTrucker

1
그러나 당신은 Foo호출 코드에서 클래스의 내부 요리를 추방하고 있습니다! 우리는 어떻게 알 수 isFooEnabled있습니까? 이것은 클래스 의 내부 필드입니다 Foo.
Nicolas Barbulesco

3

Java 8의 람다 함수를 사용 하여 @Colin의 답변과 비슷한 기능적 접근 방식 사용하면 조건부 기능 토글 활성화 / 비활성화 코드를 executeIfEnabled동작 람다를 허용 하는 가드 메소드 ( ) 로 랩핑 할 수 있으며 조건부로 실행할 코드는 다음과 같습니다. 통과했다.

귀하의 경우에는이 방법으로 코드를 저장하지 않지만이를 건조하면 다른 기능 토글 문제와 AOP 또는 로깅, 진단, 프로파일 링 등과 같은 디버깅 문제를 중앙 집중화 할 수 있습니다.

람다를 사용할 때의 이점 중 하나는 클로저를 사용하여 과부하를 피할 수 있다는 것입니다. executeIfEnabled 메서드 것입니다.

예를 들면 다음과 같습니다.

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

테스트로 :

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

또한 메소드 오버라이드를 통한 인터페이스 및 익명 클래스 구현이 필요없는 Damix의 접근 방식과 유사합니다.
StuartLC

2

다른 답변에서 지적했듯이 전략 디자인 패턴 은이 코드를 단순화하기 위해 따라야 할 적절한 디자인 패턴입니다. 여기에서는 리플렉션을 통한 메서드 호출을 사용하여 설명했지만 동일한 효과를 얻는 데 사용할 수있는 메커니즘에는 여러 가지가 있습니다.

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...
  }
}

이미 언급 한 것처럼 이미이 클래스를 가지고 있다면 리플렉션을 다루는 것이 다소 어색 Proxy합니다.
SpaceTrucker 2016 년

어떻게 할 수 foo.fooIsEnabled ...있습니까? 우선, 이것은 객체의 내부 필드이므로 외부에서 볼 수 없으며 원하지 않습니다.
Nicolas Barbulesco

2

자바만이 기능적으로 조금 나아 졌다면. 가장 OOO 솔루션은 단일 함수를 래핑하는 클래스를 만드는 것이므로 foo가 활성화 된 경우에만 호출됩니다.

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

다음 구현 bar, baz그리고 bat익명 클래스로 그 확장 것을 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
        }
    };
}

또 다른 아이디어

사용 glglgl의 널 (NULL) 솔루션 하지만, 메이크업 FooImplNullFoo클래스 아래의 (개인 생성자와) 내부 클래스 :

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

이 방법을 사용하면 기억하는 것에 대해 걱정할 필요가 없습니다 (isFooEnabled ? foo : NullFoo.DEFAULT).


당신이 말 : Foo foo = new Foo()전화 bar당신이 쓰는 것foo.bar.call()
콜린

1

Foo가 활성화되어 있지 않으면 클래스가 아무것도하지 않는 것처럼 보이므로 Foo 인스턴스를 만들거나 얻는 더 높은 수준에서 이것을 표현하지 않는 이유는 무엇입니까?

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

isFooEnabled가 상수 인 경우에만 작동합니다. 일반적으로 고유 한 주석을 만들 수 있습니다.


Konrad, 주석을 개발할 수 있습니까?
Nicolas Barbulesco

원본 코드 fooIsEnabled는 메서드 호출시기를 결정합니다 . 인스턴스화하기 전에이 작업을 수행하십시오 Foo. 너무 이르다. 그 동안 값이 변경 될 수 있습니다.
Nicolas Barbulesco

난 당신이 요점을 그리워 생각합니다. 선험 isFooEnabledFoo객체 의 인스턴스 필드입니다 .
Nicolas Barbulesco

1

Java 구문에 익숙하지 않습니다. Java에는 다형성, 정적 속성, 추상 클래스 및 메소드가 있다고 가정합니다.

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

누가되는 것을 말한다 활성화가 있습니다 정적 클래스의 속성은?
Nicolas Barbulesco

무슨 new bar()뜻입니까?
Nicolas Barbulesco

Java에서는 Name대문자 로 클래스 를 작성합니다 .
Nicolas Barbulesco

호출 코드를 너무 많이 변경해야합니다. 우리는 일반적으로 메소드를 호출합니다 bar(). 당신이 그것을 변경 해야하는 경우, 당신은 운명입니다.
Nicolas Barbulesco

1

기본적으로 설정된 경우 함수 호출을 건너 뛰어야한다는 플래그가 있습니다. 그래서 내 솔루션은 어리석은 것이라고 생각하지만 여기에 있습니다.

Foo foo = new Foo();

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

다음은 함수를 실행하기 전에 코드를 실행하려는 경우를 대비 한 간단한 프록시 구현입니다.

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 ()
    {
        // ..
    }
}

첫 번째 코드 블록에는 가시적 인 방법이 필요합니다 isEnabled(). 우선 순위는 활성화Foo 되지 않은 내부 요리입니다 .
Nicolas Barbulesco

호출하는 코드는 할 수없는 , 그리고 원치 않는 개체가 있는지 여부를 알고 사용 가능 .
Nicolas Barbulesco

0

대리자를 사용하는 또 다른 해결책이 있습니다 (포인터 포인터). 먼저 유효성 검사를 수행 한 다음 호출 할 함수 (매개 변수)에 따라 관련 메서드를 호출하는 고유 한 메서드를 가질 수 있습니다. C # 코드 :

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
맞습니다. Java로 표시되어 있으므로 Java를 모르기 때문에 "Code in C #"을 강조하고 작성했습니다. 디자인 패턴 질문이므로 언어는 중요하지 않습니다.

오! 죄송합니다. 해결책을 찾고자 노력했습니다. 감사합니다
ehh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.