“Execute Around”관용구는 무엇입니까?


151

내가 들었던이 "Execute Around"관용구는 무엇입니까? 왜 사용해야합니까? 왜 사용하고 싶지 않습니까?


9
당신 인 걸 몰랐어요 그렇지 않으면 나는 내 대답에 더 냉소적되었을 수 있습니다)
존 소총

1
이것이 기본적으로 측면 입니까? 그렇지 않다면 어떻게 다릅니 까?
Lucas

답변:


147

기본적으로 자원 할당 및 정리와 같이 항상 필요한 작업을 수행하고 호출자가 "자원으로 수행하려는 작업"을 수행하도록하는 메소드를 작성하는 패턴입니다. 예를 들면 다음과 같습니다.

public interface InputStreamAction
{
    void useStream(InputStream stream) throws IOException;
}

// Somewhere else    

public void executeWithFile(String filename, InputStreamAction action)
    throws IOException
{
    InputStream stream = new FileInputStream(filename);
    try {
        action.useStream(stream);
    } finally {
        stream.close();
    }
}

// Calling it
executeWithFile("filename.txt", new InputStreamAction()
{
    public void useStream(InputStream stream) throws IOException
    {
        // Code to use the stream goes here
    }
});

// Calling it with Java 8 Lambda Expression:
executeWithFile("filename.txt", s -> System.out.println(s.read()));

// Or with Java 8 Method reference:
executeWithFile("filename.txt", ClassName::methodName);

호출 코드는 개방 / 정리 측면에 대해 걱정할 필요가 없습니다 executeWithFile.

Java 8 람다 식으로 시작하여 클로저가 너무 까다로워 다른 많은 언어 (예 : C # 람다 식 또는 Groovy)와 같이 구현 될 수 있기 때문에 Java에서 솔직히 고통 스럽습니다.이 특별한 경우는 Java 7 with try-with-resourcesand AutoClosablestreams 이후 처리 됩니다.

"할당 및 정리"가 일반적인 예이지만 트랜잭션 처리, 로깅, 더 많은 권한으로 일부 코드 실행 등과 같은 다른 가능한 예가 많이 있습니다. 기본적으로 템플릿 메소드 패턴 과 약간 유사 하지만 상속은 없습니다.


4
결정적입니다. Java의 종료자는 결정적으로 호출되지 않습니다. 또한 마지막 단락에서 말했듯이 리소스 할당 및 정리 에만 사용되는 것은 아닙니다 . 새 객체를 전혀 만들지 않아도됩니다. 일반적으로 "초기화 및 해제"이지만 리소스 할당이 아닐 수 있습니다.
Jon Skeet

3
그래서 C에서와 같이 일부 작업을 수행하기 위해 함수 포인터를 전달하는 함수가 있습니까?
Paul Tomblin

3
또한 Jon, Java에서 클로저를 참조하십시오. 여전히 그리워하지 않는 한. 당신이 묘사하는 것은 익명의 내부 클래스입니다. 진정한 클로저 지원 (제안 된 바와 같이-내 블로그 참조)은 그 구문을 상당히 단순화시킬 것입니다.
philsquared

8
@ 필 : 나는 그것이 정도의 문제라고 생각합니다. Java 익명 내부 클래스는 제한된 의미로 주변 환경 액세스 할 수 있습니다. 따라서 "완전한"클로저는 아니지만 "제한된"클로저입니다. 확인되었지만 (계속) Java에서 적절한 클로저를보고 싶습니다.
Jon Skeet

4
Java 7은 try-with-resource를 추가했으며 Java 8은 람다를 추가했습니다. 나는 이것이 오래된 질문 / 답변이라는 것을 알고 있지만 5 년 반 후에이 질문을 보는 사람에게 이것을 지적하고 싶었습니다. 이 언어 도구는 모두이 패턴으로 해결 된 문제를 해결하는 데 도움이됩니다.

45

Execute Around 관용구는 다음과 같은 작업을 수행해야 할 때 사용됩니다.

//... chunk of init/preparation code ...
task A
//... chunk of cleanup/finishing code ...

//... chunk of identical init/preparation code ...
task B
//... chunk of identical cleanup/finishing code ...

//... chunk of identical init/preparation code ...
task C
//... chunk of identical cleanup/finishing code ...

//... and so on.

실제 작업 주위에서 항상 실행되는이 중복 코드를 모두 반복하지 않으려면 자동으로 처리하는 클래스를 만들어야합니다.

//pseudo-code:
class DoTask()
{
    do(task T)
    {
        // .. chunk of prep code
        // execute task T
        // .. chunk of cleanup code
    }
};

DoTask.do(task A)
DoTask.do(task B)
DoTask.do(task C)

이 관용구는 복잡한 중복 코드를 모두 한 곳으로 옮기고 주 프로그램을 훨씬 더 읽기 쉽고 유지 관리 할 수있게합니다.

한 번 봐 가지고 이 게시물에 C #을 예를 들어, 그리고 이 문서 는 C ++ 예를 들어.


7

방법은 주위에 실행 하면 설치 및 / 또는 분해 코드를 수행 사이에 코드를 실행할 수있는 방법으로 임의의 코드를 통과 곳입니다.

Java는 내가 선택한 언어가 아닙니다. 인수로 클로저 (또는 람다 식)를 전달하는 것이 더 세련됩니다. 비록 객체는 아마도 클로저와 동일하다 .

Execute Around Method는 메소드 를 호출 할 때마다 ad hoc을 변경할 수있는 Inversion of Control (Dependency Injection) 과 같은 것 같습니다 .

그러나 제어 커플 링의 예로 해석 될 수도 있습니다 (이 경우 말 그대로 인수로 수행 할 방법을 알려줍니다).


7

여기에 Java 태그가 있으므로 패턴이 플랫폼별로 다르더라도 Java를 예제로 사용합니다.

아이디어는 때때로 코드를 실행하기 전과 코드를 실행 한 후 항상 동일한 상용구를 포함하는 코드를 가지고 있다는 것입니다. 좋은 예는 JDBC입니다. 실제 쿼리를 실행하고 결과 세트를 처리하기 전에 항상 연결을 잡고 명령문 (또는 준비된 명령문)을 작성한 다음 끝에 항상 동일한 상용구를 정리하여 명령문과 연결을 닫습니다.

execute-around를 사용하는 아이디어는 상용구 코드를 제거 할 수 있으면 더 좋습니다. 이렇게하면 타이핑이 절약되지만 그 이유는 더 깊습니다. 여기서는 반복하지 말아야합니다 (DRY) 원칙입니다. 코드를 한 위치로 분리하여 버그가 있거나 변경이 필요하거나 이해해야하는 경우 모두 한 곳에 있습니다.

이런 종류의 팩토링 아웃에서 약간 까다로운 것은 "전"과 "후"부분 모두를 참조해야한다는 참조가 있다는 것입니다. JDBC 예제에서 이는 Connection 및 (Prepared) 문을 포함합니다. 따라서 처리하려면 기본적으로 상용구 코드로 대상 코드를 "랩핑"해야합니다.

Java의 일반적인 경우에 익숙 할 수 있습니다. 하나는 서블릿 필터입니다. 다른 하나는 조언에 관한 AOP입니다. 세 번째는 Spring의 다양한 xxxTemplate 클래스입니다. 각각의 경우에 "흥미로운"코드 (예 : JDBC 쿼리 및 결과 세트 처리)가 삽입되는 일부 랩퍼 오브젝트가 있습니다. 랩퍼 오브젝트는 "이전"부분을 수행하고 흥미로운 코드를 호출 한 다음 "이후"부분을 수행합니다.


7

많은 프로그래밍 언어에서이 구성을 조사하고 흥미로운 연구 아이디어를 제공하는 Code Sandwiches 도 참조하십시오 . 왜 그것을 사용할 수 있는지에 대한 구체적인 질문과 관련하여 위의 논문은 몇 가지 구체적인 예를 제공합니다.

이러한 상황은 프로그램이 공유 자원을 조작 할 때마다 발생합니다. 잠금, 소켓, 파일 또는 데이터베이스 연결을위한 API에는 이전에 확보 한 자원을 명시 적으로 닫거나 해제하기위한 프로그램이 필요할 수 있습니다. 가비지 수집이없는 언어에서는 프로그래머가 메모리를 사용하기 전에 할당하고 사용한 후에 해제해야합니다. 일반적으로 다양한 프로그래밍 작업에서 프로그램을 변경하여 변경 내용을 적용한 다음 변경 내용을 취소해야합니다. 이러한 상황을 코드 샌드위치라고합니다.

그리고 나중에 :

코드 샌드위치는 많은 프로그래밍 상황에서 나타납니다. 몇 가지 일반적인 예는 잠금, 파일 설명자 또는 소켓 연결과 같은 부족한 리소스의 획득 및 릴리스와 관련이 있습니다. 보다 일반적인 경우, 프로그램 상태의 임시 변경에는 코드 샌드위치가 필요할 수 있습니다. 예를 들어, GUI 기반 프로그램은 사용자 입력을 일시적으로 무시하거나 OS 커널이 일시적으로 하드웨어 인터럽트를 비활성화 할 수 있습니다. 이 경우 이전 상태로 복원하지 않으면 심각한 버그가 발생합니다.

왜 종이 탐구하지 않습니다 하지 이 관용구를 사용하지만, 관용구는 언어 수준의 도움없이 잘못되기 쉽습니다 왜 설명 않습니다 :

결함이있는 코드 샌드위치는 예외 및 관련 보이지 않는 제어 흐름이있을 때 가장 자주 발생합니다. 실제로 코드 샌드위치를 ​​관리하는 특수 언어 기능은 주로 예외를 지원하는 언어에서 발생합니다.

그러나 예외가 코드 샌드위치 결함의 유일한 원인은 아닙니다. 본문 코드 가 변경 될 때마다 이후 코드 를 우회하는 새로운 제어 경로가 발생할 수 있습니다 . 가장 간단한 경우, 관리자 return는 샌드위치 본체 에 문장을 추가 하면 새로운 결함이 생겨 조용한 오류가 발생할 수 있습니다. 때 본체 코드 대형이며 넓게 분리되어, 이러한 실수는 시각적 검출하기 어려울 수있다.


좋은 지적입니다. 나는 그 자체로 스스로 독립적 인 답변이되도록 답변을 수정하고 확장했다. 이것을 제안 해 주셔서 감사합니다.
Ben Liblit

4

나는 4 살짜리 아이처럼 설명하려고 노력할 것이다.

실시 예 1

산타가 왔어요. 그의 엘프들은 등 뒤에서 원하는 것을 코딩하고, 변경하지 않으면 조금 반복적입니다.

  1. 포장지 받기
  2. 가져 오기 슈퍼 닌텐도 .
  3. 포장하다.

아니면 이거:

  1. 포장지 받기
  2. 바비 인형을 받으세요 .
  3. 포장하다.

.... 백만 개의 다른 선물로 수백만 번의 오심 : 단계 2만이 다른 점에 유의하십시오. 단계 2 만 다른 것이면, 왜 산타가 코드를 복제합니까, 즉 왜 단계를 복제합니까? 1 백만 번? 백만 개의 선물은 1, 3 단계를 불필요하게 백만 번 반복한다는 의미입니다.

실행은 해당 문제를 해결하는 데 도움이됩니다. 코드를 제거하는 데 도움이됩니다. 1 단계와 3 단계는 기본적으로 일정하므로 2 단계 만 변경할 수 있습니다.

실시 예 # 2

그래도 얻을 수 없다면 다른 예가 있습니다. 모래를 생각해보십시오. 외부의 빵은 항상 동일하지만 내부의 내용은 선택한 모래의 종류 (예 : 햄, 치즈, 잼, 땅콩 버터 등). 빵은 항상 바깥에 있으며, 만드는 모든 종류의 모래에 대해 10 억 번 반복 할 필요가 없습니다.

위의 설명을 읽으면 이해하기 쉬울 것입니다. 이 설명이 도움이 되었기를 바랍니다.


상상력을 위해 + : D
선생님. 고슴도치

3

이것은 전략 디자인 패턴을 생각 나게한다 . 내가 가리키는 링크에는 패턴에 대한 Java 코드가 포함되어 있습니다.

초기화 및 정리 코드를 작성하고 전략을 전달함으로써 "Execute Around"를 수행 할 수있을 것입니다. 그러면 전략이 초기화 및 정리 코드로 항상 래핑됩니다.

코드 반복을 줄이는 데 사용되는 기술과 마찬가지로 필요한 경우가 최소 2 가지, 아마도 3 개 (YAGNI 원칙)가 될 때까지 사용해서는 안됩니다. 코드 반복을 제거하면 유지 관리가 줄어들고 (코드 수가 적을수록 각 복사본에서 수정 사항을 복사하는 데 소요되는 시간이 줄어 듭니다) 유지 관리도 향상됩니다 (총 코드가 더 많음). 따라서이 트릭의 비용은 더 많은 코드를 추가하는 것입니다.

이 유형의 기술은 초기화 및 정리 그 이상에 유용합니다. 또한 함수 호출을 더 쉽게하고 싶을 때 유용합니다 (예를 들어 마법사에서 사용할 수 있으므로 "다음"및 "이전"버튼에 수행 할 작업을 결정하기 위해 대소 문자가 필요하지 않음) 다음 / 이전 페이지


0

그루비 관용구를 원한다면 다음과 같습니다.

//-- the target class
class Resource { 
    def open () { // sensitive operation }
    def close () { // sensitive operation }
    //-- target method
    def doWork() { println "working";} }

//-- the execute around code
def static use (closure) {
    def res = new Resource();
    try { 
        res.open();
        closure(res)
    } finally {
        res.close();
    }
}

//-- using the code
Resource.use { res -> res.doWork(); }

내 열림이 실패하면 (예 : 재진입 잠금 획득) 닫힘이 호출됩니다 (예 : 일치하는 열림에도 불구하고 재진입 잠금 해제).
Tom Hawtin-tackline
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.