나를 기록하다
article thumbnail
반응형

우테코

if-else문의 문제점

  1. 변경 또는 확장이 될수록 코드가 복잡해진다.
  2. 코드를 수정하거나 수정할 위치를 찾는데 점점 오래 걸린다.
  3. 실수로 추가하지 않고 누락하는 부분이 생길 가능성이 있다.

즉, 유지보수가 점점 어려워진다.

 

 


참고자료

https://www.youtube.com/watch?v=90ZDvHl8ROE&list=PLgXGHBqgT2TvpJ_p9L_yZKPifgdBOzdVH&index=374&t=589s 

 

예시

1) 초기코드

public class LottoNumbersAutoGenerator {

	public List<Integer> generate() {
		List<Integer> numbers = new ArrayList<>();
		for (int i = LottoNumber.MIN; i <= LottoNumber.MAX; i++) {
			numbers.add(i);
		}

		Collections.shuffle(numbers);
		
		return numbers.subList(0, Lotto.LOTTO_NUMBER_SIZE);
	}
}

2) 기능 추가(if-else)

public class LottoNumbersAutoGenerator {

	public List<Integer> generate() {
		List<Integer> numbers = new ArrayList<>();
		for (int i = LottoNumber.MIN; i <= LottoNumber.MAX; i++) {
			numbers.add(i);
		}

		if(shuffle == RANDOM) {
			Collections.shuffle(numbers);
		} else if (shuffle == NOTHING) {
			Collections.sort(numbers);
		} else if (shuffle == REVERSE) {
			Collections.reverse(numbers);
		}
		
		return numbers.subList(0, Lotto.LOTTO_NUMBER_SIZE);
	}
}

최초에는 if-else 블럭이 크지 않기 때문에  이 방법으로 빠르게 구현 가능

 

...계속되는 기능 추가...

 

계속되는 기능이 추가 → 복잡도 증가 → 추가 및 수정이 힘들어진다.

 

또한 A, B, C라는 세개의 클래스가 공통되는 if else를 사용하고 있는 경우(빠르게 copy and paste) 누락하는 사고 발생

기능을 추가하는 경우 A, B에는 적용했으나 C에는 깜박하고 빠뜨리는 경우 발생

컴파일 단계에서 발견하면 다행이지만 런타임 단계에서 발견하면 큰 버그가 발생할 수도 있기에 If-else 사용하는 것을 자제하는 것이 좋다.

 

OCP(Open Close Principle): 개방폐쇄의 원칙

소프트웨어 구성 요소(컴포넌트, 클래스, 모듈, 함수)는 확장에 대해서는 개방돼야 하지만 변경에 대해서는 폐쇄되어야 한다.

기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계가 되어야 한다는 뜻이다.

 

적용 방법 2가지

  1. 상속(is-a)
  2. 컴포지션(has-a)

컴포지션을 추천. 상속같은 경우에는 상위 클래스와 하위 클래스가 강력하게 밀접되어 있기 때문에 상위 클래스가 변경된다면 하위 클래스에도 강력하게 영향을 미친다.

→ 이것을 깨지기 쉬운 상위 클래스 문제라고 한다.

 

컴포지션 적용방법 3가지

  1. 변경(확장)될 것과 변하지 않을 것을 엄격히 구분
  2. 이 두 모듈이 만나는 지점에 인터페이스를 정의
  3. 구현에 의존하기보다 정의한 인터페이스에 의존하도록 코드를 작성

1. 변경될 것과 변하지 않을 부분을 구분

A와 C는 변경되지 않을 부분. 변경이 일어나는 B를 인터페이스로 추출

public class LottoNumbersAutoGenerator {
//======================== A 구간 ========================//
	public List<Integer> generate() {
		List<Integer> numbers = new ArrayList<>();
		for (int i = LottoNumber.MIN; i <= LottoNumber.MAX; i++) {
			numbers.add(i);
		}
//======================== A 구간 ========================//

//======================== B 구간 ========================//
		if(shuffle == RANDOM) {
			Collections.shuffle(numbers);
		} else if (shuffle == NOTHING) {
			Collections.sort(numbers);
		} else if (shuffle == REVERSE) {
			Collections.reverse(numbers);
		}
//======================== B 구간 ========================//

//======================== C 구간 ========================//
		return numbers.subList(0, Lotto.LOTTO_NUMBER_SIZE);
//======================== C 구간 ========================//        
	}
}

 

2. 두 모듈이 만나는 지점에 인터페이스 정의

인터페이스 정의

주의할 점: 인터페이스에 의존해야함. 다른 것에 의존하면 안됨.

기능 변경을 위해 코드를 수정해야 한다.

3. 인터페이스에 의존하도록 코드를 작성

인터페이스에 의존하는 코드

예시: List <---> ArrayList, LinkedList의 관계

똑같이 사용은 되지만 작동되는 기능은 조금씩 다르다.

직접 자신이 new에서 생성하지 않고 생성자 파라미터에 인스턴스를 넣어줘서 외부에서 인스턴스를 받는 것을 의존주입이라 한다.

영어로는 DI(Dependency Injection).

 

OCP의 장점
1. 기능이 추가, 변경되어도 기존 코드는 변경되지 않는다 → 확장이 쉽다!
2. 구현체를 주입해서 테스트가 가능하다.

 

전략 패턴

OCP를 준수하기 위해 여태 한 방식이 전략 패턴

전략이란?
어떤 목적을 달성하기 위해 일을 수행하는 방식.
비즈니스 규칙, 문제를 해결하는 알고리즘(예제의 Random, Reverse, Nothing 등) 등...
  1. 디자인 패턴의 꽃
  2. 전략을 쉽게 바꿀 수 있도록 해주는 디자인 패턴
  3. 행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 해주는 패턴
  4. 새로운 기능의 추가가 기존의 코드에 영향을 미치지 못하게 하므로 OCP를 만족

위 예시에서 로또 번호를 만들어주는 generate() 메서드에 현재는 ShuffleRandomStrategy로 랜덤하게 구현했지만,

Shuffle 조작 Strategy class라는 코드를 조작해서 생성하는 기능을 만든 구현체를 주입하더라도 generate() 메서드의 코드 자체에 영향을 미치지 않고 기능 자체는 동작하게 된다.

 

Context
  • 전략 패턴을 이용하는 역할을 수행
  • 필요에 따라 동적으로 구체적인 전략을 바꿀 수 있도록 한다.
Strategy
  • 인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시
ConcreateStrategy
  • 전략 패턴에서 명시한 알고리즘을 실제로 구현한 클래스

 

  1. 위 예시에서 LottoNumbersAutoGenerator가 Context
  2. ShuffleStrategy 인터페이스가 Strategy 인터페이스
  3. ShuffleRandomStrategy 같은 구현체가 ConcreateStrategy에 해당

 

전략 패턴이란,
기존의 코드 변경없이 행위를 자유롭게 바꿀 수 있게 해주는 OCP의 디자인 패턴이다.

 

 

템플릿 메서드 패턴 vs 전략 패턴

상속을 이용한 방법이 템플릿 메서드 패턴

컴포지션을 사용한 방법이 전략 패턴

https://happy-coding-day.tistory.com/entry/Template-Pattern%ED%85%9C%ED%94%8C%EB%A6%BF-%ED%8C%A8%ED%84%B4-VS-Strategy-Pattern%EC%A0%84%EB%9E%B5-%ED%8C%A8%ED%84%B4

 

Template Pattern(템플릿 패턴) VS Strategy Pattern(전략 패턴)

Photo by Mike Meyers on Unsplash 들어가기 대부분의 디자인 패턴 책에서는 이 두가지를 비교해서 설명합니다. 왜 일까요? 이 두가지 패턴은 데이터를 은닉화 시켜 구현될 수 있도록 도와주는 패턴입니

happy-coding-day.tistory.com

 

연습문제 추천

  • 추상화, 다형성
  • 인터페이스
  • Map
  • Enum
  • 람다
  • 전략 패턴

을 이용하여 계산기를 구현하라.

public class Calculator {
 
    public int calculate(final String operator, final int operand1, final int operand2) {
        if ("+".equals(operator)) {
            return operand1 + operand2;
        } else if ("-".equals(operator)) {
            return operand1 - operand2;
        } else if ("*".equals(operator)) {
            return operand1 * operand2;
        } else if ("/".equals(operator)) {
            return operand1 / operand2;
        }
    }
}

순서

  1. 인터페이스로 추출 후 구현 클래스 만들기
  2. 익명 클래스 + enum으로 구현
  3. 람다로 구현

구현

https://dublin-java.tistory.com/38

 

if문 없이 문자열 계산하기 (feat. 전략패턴, Enum)

우아한테크코스 첫 주차 서브미션은 문자열 계산기를 if문 없이 해결하기로 단위 테스트 연습을 위한 서브미션이었습니다. 문제는 간단합니다. 3 + 2 * 4 / 10 문자열을 space 기준으로 split하고 계산

dublin-java.tistory.com

https://rutgo-letsgo.tistory.com/167

 

OCP와 전략패턴

OCP와 전략패턴 if-else문의 문제점 변경 또는 확장이 될 수록 코드가 복잡해진다. 리팩토링 시 리팩토링 부분을 찾는데 점점 오래 걸리게 된다. 실수로 추가하지 않고 누락하는 부분이 생길 가능

rutgo-letsgo.tistory.com

 


참고자료

https://0soo.tistory.com/24

 

OCP와 전략 패턴

유튜브 우아한 Tech 채널에서 10분 테코톡을 듣고 공부한 내용입니다. 10분 테코톡 - 베디의 OCP와 전략 패턴 목차 if-else의 문제점 OCP OCP란? 적용 장점 전략패턴 연습문제 추천 1. If-else의 문제점 변

0soo.tistory.com

 

 

 

반응형

'Review > Techotalk' 카테고리의 다른 글

[테코톡] Stream - 스트림, 알고 쓰자!  (2) 2023.10.24
[테코톡] MVC 패턴  (1) 2023.10.14
[테코톡] DTO와 VO  (0) 2023.09.12
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...