내가 들었던이 "Execute Around"관용구는 무엇입니까? 왜 사용해야합니까? 왜 사용하고 싶지 않습니까?
내가 들었던이 "Execute Around"관용구는 무엇입니까? 왜 사용해야합니까? 왜 사용하고 싶지 않습니까?
답변:
기본적으로 자원 할당 및 정리와 같이 항상 필요한 작업을 수행하고 호출자가 "자원으로 수행하려는 작업"을 수행하도록하는 메소드를 작성하는 패턴입니다. 예를 들면 다음과 같습니다.
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-resources
and AutoClosable
streams 이후 처리 됩니다.
"할당 및 정리"가 일반적인 예이지만 트랜잭션 처리, 로깅, 더 많은 권한으로 일부 코드 실행 등과 같은 다른 가능한 예가 많이 있습니다. 기본적으로 템플릿 메소드 패턴 과 약간 유사 하지만 상속은 없습니다.
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)
이 관용구는 복잡한 중복 코드를 모두 한 곳으로 옮기고 주 프로그램을 훨씬 더 읽기 쉽고 유지 관리 할 수있게합니다.
는 방법은 주위에 실행 하면 설치 및 / 또는 분해 코드를 수행 사이에 코드를 실행할 수있는 방법으로 임의의 코드를 통과 곳입니다.
Java는 내가 선택한 언어가 아닙니다. 인수로 클로저 (또는 람다 식)를 전달하는 것이 더 세련됩니다. 비록 객체는 아마도 클로저와 동일하다 .
Execute Around Method는 메소드 를 호출 할 때마다 ad hoc을 변경할 수있는 Inversion of Control (Dependency Injection) 과 같은 것 같습니다 .
그러나 제어 커플 링의 예로 해석 될 수도 있습니다 (이 경우 말 그대로 인수로 수행 할 방법을 알려줍니다).
여기에 Java 태그가 있으므로 패턴이 플랫폼별로 다르더라도 Java를 예제로 사용합니다.
아이디어는 때때로 코드를 실행하기 전과 코드를 실행 한 후 항상 동일한 상용구를 포함하는 코드를 가지고 있다는 것입니다. 좋은 예는 JDBC입니다. 실제 쿼리를 실행하고 결과 세트를 처리하기 전에 항상 연결을 잡고 명령문 (또는 준비된 명령문)을 작성한 다음 끝에 항상 동일한 상용구를 정리하여 명령문과 연결을 닫습니다.
execute-around를 사용하는 아이디어는 상용구 코드를 제거 할 수 있으면 더 좋습니다. 이렇게하면 타이핑이 절약되지만 그 이유는 더 깊습니다. 여기서는 반복하지 말아야합니다 (DRY) 원칙입니다. 코드를 한 위치로 분리하여 버그가 있거나 변경이 필요하거나 이해해야하는 경우 모두 한 곳에 있습니다.
이런 종류의 팩토링 아웃에서 약간 까다로운 것은 "전"과 "후"부분 모두를 참조해야한다는 참조가 있다는 것입니다. JDBC 예제에서 이는 Connection 및 (Prepared) 문을 포함합니다. 따라서 처리하려면 기본적으로 상용구 코드로 대상 코드를 "랩핑"해야합니다.
Java의 일반적인 경우에 익숙 할 수 있습니다. 하나는 서블릿 필터입니다. 다른 하나는 조언에 관한 AOP입니다. 세 번째는 Spring의 다양한 xxxTemplate 클래스입니다. 각각의 경우에 "흥미로운"코드 (예 : JDBC 쿼리 및 결과 세트 처리)가 삽입되는 일부 랩퍼 오브젝트가 있습니다. 랩퍼 오브젝트는 "이전"부분을 수행하고 흥미로운 코드를 호출 한 다음 "이후"부분을 수행합니다.
많은 프로그래밍 언어에서이 구성을 조사하고 흥미로운 연구 아이디어를 제공하는 Code Sandwiches 도 참조하십시오 . 왜 그것을 사용할 수 있는지에 대한 구체적인 질문과 관련하여 위의 논문은 몇 가지 구체적인 예를 제공합니다.
이러한 상황은 프로그램이 공유 자원을 조작 할 때마다 발생합니다. 잠금, 소켓, 파일 또는 데이터베이스 연결을위한 API에는 이전에 확보 한 자원을 명시 적으로 닫거나 해제하기위한 프로그램이 필요할 수 있습니다. 가비지 수집이없는 언어에서는 프로그래머가 메모리를 사용하기 전에 할당하고 사용한 후에 해제해야합니다. 일반적으로 다양한 프로그래밍 작업에서 프로그램을 변경하여 변경 내용을 적용한 다음 변경 내용을 취소해야합니다. 이러한 상황을 코드 샌드위치라고합니다.
그리고 나중에 :
코드 샌드위치는 많은 프로그래밍 상황에서 나타납니다. 몇 가지 일반적인 예는 잠금, 파일 설명자 또는 소켓 연결과 같은 부족한 리소스의 획득 및 릴리스와 관련이 있습니다. 보다 일반적인 경우, 프로그램 상태의 임시 변경에는 코드 샌드위치가 필요할 수 있습니다. 예를 들어, GUI 기반 프로그램은 사용자 입력을 일시적으로 무시하거나 OS 커널이 일시적으로 하드웨어 인터럽트를 비활성화 할 수 있습니다. 이 경우 이전 상태로 복원하지 않으면 심각한 버그가 발생합니다.
왜 종이 탐구하지 않습니다 하지 이 관용구를 사용하지만, 관용구는 언어 수준의 도움없이 잘못되기 쉽습니다 왜 설명 않습니다 :
결함이있는 코드 샌드위치는 예외 및 관련 보이지 않는 제어 흐름이있을 때 가장 자주 발생합니다. 실제로 코드 샌드위치를 관리하는 특수 언어 기능은 주로 예외를 지원하는 언어에서 발생합니다.
그러나 예외가 코드 샌드위치 결함의 유일한 원인은 아닙니다. 본문 코드 가 변경 될 때마다 이후 코드 를 우회하는 새로운 제어 경로가 발생할 수 있습니다 . 가장 간단한 경우, 관리자
return
는 샌드위치 본체 에 문장을 추가 하면 새로운 결함이 생겨 조용한 오류가 발생할 수 있습니다. 때 본체 코드 대형이며 전 및 후 넓게 분리되어, 이러한 실수는 시각적 검출하기 어려울 수있다.
나는 4 살짜리 아이처럼 설명하려고 노력할 것이다.
실시 예 1
산타가 왔어요. 그의 엘프들은 등 뒤에서 원하는 것을 코딩하고, 변경하지 않으면 조금 반복적입니다.
아니면 이거:
.... 백만 개의 다른 선물로 수백만 번의 오심 : 단계 2만이 다른 점에 유의하십시오. 단계 2 만 다른 것이면, 왜 산타가 코드를 복제합니까, 즉 왜 단계를 복제합니까? 1 백만 번? 백만 개의 선물은 1, 3 단계를 불필요하게 백만 번 반복한다는 의미입니다.
실행은 해당 문제를 해결하는 데 도움이됩니다. 코드를 제거하는 데 도움이됩니다. 1 단계와 3 단계는 기본적으로 일정하므로 2 단계 만 변경할 수 있습니다.
실시 예 # 2
그래도 얻을 수 없다면 다른 예가 있습니다. 모래를 생각해보십시오. 외부의 빵은 항상 동일하지만 내부의 내용은 선택한 모래의 종류 (예 : 햄, 치즈, 잼, 땅콩 버터 등). 빵은 항상 바깥에 있으며, 만드는 모든 종류의 모래에 대해 10 억 번 반복 할 필요가 없습니다.
위의 설명을 읽으면 이해하기 쉬울 것입니다. 이 설명이 도움이 되었기를 바랍니다.
이것은 전략 디자인 패턴을 생각 나게한다 . 내가 가리키는 링크에는 패턴에 대한 Java 코드가 포함되어 있습니다.
초기화 및 정리 코드를 작성하고 전략을 전달함으로써 "Execute Around"를 수행 할 수있을 것입니다. 그러면 전략이 초기화 및 정리 코드로 항상 래핑됩니다.
코드 반복을 줄이는 데 사용되는 기술과 마찬가지로 필요한 경우가 최소 2 가지, 아마도 3 개 (YAGNI 원칙)가 될 때까지 사용해서는 안됩니다. 코드 반복을 제거하면 유지 관리가 줄어들고 (코드 수가 적을수록 각 복사본에서 수정 사항을 복사하는 데 소요되는 시간이 줄어 듭니다) 유지 관리도 향상됩니다 (총 코드가 더 많음). 따라서이 트릭의 비용은 더 많은 코드를 추가하는 것입니다.
이 유형의 기술은 초기화 및 정리 그 이상에 유용합니다. 또한 함수 호출을 더 쉽게하고 싶을 때 유용합니다 (예를 들어 마법사에서 사용할 수 있으므로 "다음"및 "이전"버튼에 수행 할 작업을 결정하기 위해 대소 문자가 필요하지 않음) 다음 / 이전 페이지
그루비 관용구를 원한다면 다음과 같습니다.
//-- 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(); }