유사한 코드 융합하기
인터페이스로 리팩터링 패턴: if 문 결합
내용이 동일한 연속적인 if 문을 결합해서 중복을 제거
내용이 동일한 연속적인 if 문의 코드를 마주하게 되는 일은 드물텐데, 일부 리팩터링 중에 이러한 상태를 의도적으로 만든 사례를 마주하게 된다.
이 패턴은
||
을 추가해서 두 식의 관계를 드러내기 때문에 유용하다.많은 개발자는 이를 상식이라 생각한다.ㅎㅎ
절차
본문이 실제로 동일한지 체크
첫 번째 if 문의 닫는 괄호와 else if 문의 여는 괄호 사이 코드를 선택하고 삭제 후
||
삽입
복잡한 조건 통합하기
복잡한 조건을 통합하다 보면 결과 조건이 이전보다 더 복잡해진다.
이런 조건을 어떻게 다룰지 얘기해보자.
조건을 위한 산술 규칙 사용
||
은+
더하기 처럼 동작&
은x
곱하기 처럼 동작
순수 조건 사용
조건은 항상 순수 조건이어야 한다.
조건이란
if
또는while
뒤에 오는것과for
루프의 가운데에 있는 것을 의미순수라는 말은 조건에 부수적인 동작이 없음을 의미
부수적인 동작이란 조건이 변수에 값을 할당하거나 예외를 발생시키거나 I/O와 상호작용하는 것을 의미
Cache
부수저긴 동작에서 반환(return)을 분리할 수 없을 경우 캐시를 사용
캐시를 구현하는 방법으로 여러 가지가 있지만 모든 메서드에 사용가능하고 부수적인 동작을 반환 부분과 분리할 수 있는 범용 캐시를 만들어보자.
"메서드는 한 가지 작업을 해야 한다"
부수적인 동작은 한 가지 작업이고 무언가를 반환하는 것은 별개이다.
부수적인 동작을 하거나 무언가를 반환하는 것 중 하나만
조건 산술 적용
먼저 수학 방정식으로 변환한 다음 익숙한 산술 법칙을 사용해서 단순화하고 다시 코드로 변환
조건을 수학 방정식으로 변환하고 단순화한 후 머릿속에서 다시 코드로 바꾸는 과정을 연습하자.
UML 클래스 다이어그램 (클래스 관계 묘사)
대부분의 경우 클래스의 공용 인터페이스의 관심이 있다.
비공개 항목은 일반적으로 포함하지 않는다.
대부분의 필드는 비공개이다.
흔히 공개 메서드만을 묘사하기 때문에 가시성을 신경쓸 필요 없다.
전체 프로그램에 대한 클래스 다이어그램을 만드는것은 순식간에 거대해져 전혀 도움이 되지 않음
주로 디자인 패턴이나 소프트웨어 아키텍처의 작은 부분을 설명하는데 사용하기 때문에 중요한 메서드만 포함됨
클래스 간의 관계
이것을 더 단순화해보자면 "인터페이스에서만 상속받을 것" 규칙 때문에 상속(Inheritance, Generalization) 관계는 사용할 수 없다.
의존(Dependency)과 연관(Association) 화살표는 일반적으로 관계가 무엇인지 모르거나 신경 쓰지 않을 때 사용됨
컴포지션(Composition)과 집합(Aggregation)의 차이는 어떻게 표현하는가의 문제
따라서 대부분의 경우 컴포지션과 구현(Implements, Realization)이라는 두가지 관계 유형을 사용함
구현 (Implements)
합성 (Composition)
다른 클래스를 인스턴스화해서 변형(variance)을 도입하는 개념을 전략 패턴이라 한다.
많은 패턴이 전략 패턴의 다른 형태이다.
전략이 필드를 가지고 있는 경우 이를 상태 패턴(state pattern)이라 한다.
이러한 구분은 이론적인것이으로 실제로는 정확한 이름을 아는것이 의사소통에 많은 도움은 되지 않는다.
전략 클래스가 완료된 후 메서드를 추가하는 경우는 거의 없고, 대신 기능을 변경해야 하는 경우 새로운 클래스를 만드는 것을 선호한다.
변형(전략)은 전략 패턴의 목적이기 때문에 항상 상속(Inheritance)으로 묘사된다.
전략 패턴의 변형(variance)은 늦은 바인딩의 궁극적인 형태이다.
런타임에 전략 패턴을 사용하면 코드에 사용자 정의 클래스를 적재하고 이를 제어 흐름에 원활하게 통합할 수 있다.
두 가지 상황에서 전략 패턴을 도입한다.
코드에 변형을 도입하고 싶어서 리팩토링을 수행하는 경우
이 경우 결국 인터페이스가 있어야하는데 리팩터링을 최대한 빠르게하려면 인터페이스는 나중에 만드는 것이좋음
단지 클래스 간의 동작을 통합하려는 경우
구현체가 하나뿐인 인터페이스를 만들지 말것이라는 규칙
인터페이스가 필요할 때 바로 혹은 나중에 구현에서 인터페이스 추출이라는 리팩터링을 사용한다.
절차
분리하려는 코드에 대해 메서드 추출을 수행 (다른것과 통합하려면 메서드가 동일한지 체크)
새로운 클래스 만듦
생성자에서 새로운 클래스를 인스턴스화
메서드를 새로운 클래스로 옮김
필드에 종속성이 있을 경우 다음을 수행
필드를 새로운 클래스로 옮기고 옮긴 필드에 대한 접근자를 만듦
새로운 접근자를 사용해서 원래 클래스에서 발생하는 오류를 바로잡음
새로운 클래스의 나머지 오류에 대한 해당 값을 대체할 매개변수를 추가
메서드의 인라인화를 사용해서 1단계의 추출을 반대로 수행
구현체가 하나뿐인 인터페이스를 만들지 말 것
구현체가 하나뿐인 인터페이스를 사용하지 마라
구현 클래스가 하나밖에 없는 인터페이스는 가독성에 도움이 되지 않는다.
구현 클래스를 수정하려는 경우 인터페이스를 수정해야 해서 오버헤드를 발생시킴
아무런 구현체가 없는 인터페이스를 갖는 것이 합리적인 경우가 있다.
비교자(comparator)와 같은 항목에 대해 익명 클래스를 사용하거나
익명의 내부 클래스를 통해 더 엄격한 캡슐화를 수행하려는 경우 유용
스멜
"컴퓨터 과학의 모든 문제는 간접 레이어를 도입함으로써 해결할 수 있다."는 유명한 말이 있다.
이것이 바로 인터페이스이다.
세부적인 내용은 추상화 아래에 숨김
"추상화는 인지된 복잡성의 감소를 위해 실제의 복잡성의 증가를 허용하는 것"
추상화에 신중해야 한다는것을 암시
의도
불필요한 코드의 상용구(boilerplate)를 제한하는 것
인터페이스는 상용구의 일반적인 원인
많은 사람이 인터페이스는 항상 바람직하다고 배웠기 때문에 특히 위험하다
그래서 애플리케이션의 크기를 부풀리는 경향이 있음
리팩토링 패턴: 구현에서 인터페이스 추출
인터페이스를 만드는 것을 필요할 때(변형을 도입하고 싶을 때)까지 연기할 수 있어 유용하다
추출할 클래스와 동일한 이름으로 새로운 인터페이스를 만듬
인터페이스를 추출할 클래스의 이름을 변경하고 새로운 인터페이스를 구현하게 한다
컴파일하고 오류를 검토합니다.
new 때문에 오류가 발생하면 인스턴스화하는 부분을 새로운 클래스의 이름으로 변경
그렇지 않으면 오류를 일으키는 메서드를 인터페이스에 추가한다.
유사 함수 통합하기
결론적으로 이것들을 통합하는데 전략 패턴 도입을 활용 가능
두 함수가 동일하지 않으므로 첫 번째 것은 이미 존재하고 두 번째 것을 도입해야 한다는 가정으로 처리
즉 변형을 도입
신규 클래스 생성
이 경우 새로운 클래스를 인스턴스화 할 수 있는 생성자가 없다.
대신 함수에서 직접 인스턴스화
메서드 옮김
전략을 도입했기 때문에 변형을 추가할 수 있도록 구현에서 인터페이스 추출을 사용
이제 RemoveLock1의 복사본에서 RemoveLock2를 만드는것은 간단하다.
만든 다음 shouldRemove만 매개변수로 옮기면 된다.
1. removeLock1에서 첫번째 줄을 제외한 모든 것을 추출해서 remove를 얻는다.
2. shouldRemove 지역변수는 한 번만 사용되므로 인라인화
3. removeLock1에 메서드의 인라인화를 수행해 제거
리팩터링의 결과로 단 하나의 remove만 남게 된다.
remove()
를 좀 더 일반적으로 만들게됨또한 추가를 통해 변경을 적용할 수 있는데, 다른 유형의 타일을 제거하려면 수정 없이 RemoveStrategy를 구현하는 다른 클래스를 간단히 만들면 된다.
어떤 애플리케이션에서는 루프 내에서 new를 호출하는 것을 꺼린다.
그렇게하면 애플리케이션의 속도가 느려질 수 있기 때문.. 이 경우 RemoveLock 전략을 인스턴스 변수에 간단히 저장하고 생성자에서 초기화할 수 있다.
요약하기
모아야할 코드가 있을 때 우리는 그것을 통합해야 한다.
유사 클래스 통합, if 문 결합을 사용해서 클래스를 통합하고 전략 패턴의 도입을 사용해서 메서드를 통합할 수 있다.
순수 조건 사용 규칙은 조건에 부수적인 동작이 없어야 한다고 명시한다.
부수적인 동작이 없는 경우 조건부 산술을 사용할 수 있기 때문
또한 부수적인 동작을 조건과 분리하기 위해 캐시를 사용하는 방법도 있음
UML 클래스 다이어그램은 일반적으로 코드베이스에 대한 특정 아키텍처의 변경을 설명하기 위해 사용됨
구현 클래스가 하나뿐인 인터페이스는 불필요한 일반화의 한 형태이다.
구현체가 하나뿐인 인터페이스를 만들지 말것 규칙에는 이런 항목이 없어야한다고 명시함
대신 구현에서 인터페이스 추출 리팩터링 패턴을 사용해 인터페이스를 나중에 도입해야 한다.
Last updated