확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.
위의 정의를 쉽게 말하면 확장은 쉽게, 변경은 어렵게
설계하자는 것이다.
더 이해하기 쉽게 현실 세계에서의 OCP 적용 사례를 찾아보도록 하자.
세상에는 수많은 종류의 PC와 주변기기들 (키보드, 마우스 , 스피커 등등)이 있다.
서로 기능도 다르고 생김새도 다르지만 각 PC와 각 주변기기들이 모두 다 연결이 가능하다.
그게 가능한 이유는 연결 단자 규격(인터페이스)
이 같기 때문이다.
여기서 PC와 주변기기는 '확장-쉽게'
에 해당되고, 연결 단자는 '변경-어렵게'
에 해당한다.
PC와 주변기기의 기능들은 얼마든지 확장하고 추가해도 되지만 연결 단자 규격은 절대 변경하면 안 된다.
세상에는 수많은 종류의 휴대폰과 충전기가 있다.
서로 기능도 다르고 생김새도 다르지만 각 휴대폰과 각 충전기가 모두 다 연결이 가능하다.
그게 가능한 이유 또한, 연결 단자 규격(인터페이스)
이 같기 때문이다.
여기서 휴대폰과 충전기는 '확장-쉽게'
에 해당되고, 연결 단자는 '변경-어렵게'
에 해당한다.
휴대폰과 충전기의 기능은 얼마든지 확장하고 추가해도 되지만 연결 단자 규격은 절대 변경하면 안 된다.
간단한 소스 코드와 함께 알아보자.
public class Information {
public String getInformation(Long itemId) {
if (itemId == 1L) {
return "앨범명: AAA, 가수: Tom, 가격: 1000";
} else if (itemId == 2L) {
return "도서명: BBB, 저자: Bill, 가격: 2000";
} else if (itemId == 3L) {
return "영화제목: CCC, 감독: Steve, 가격: 3000";
}
// ...
}
}
getInformation()
는 상품에 대한 정보를 반환하는 메서드이다.
위의 소스 코드는 여러 가지 문제점을 가지고 있다.
if-else
문을 계속해서 추가해야 하므로 점점 코드가 복잡해진다.결론적으로, 위의 소스코드는 OCP를 위반한 코드이다.
OCP 원칙을 적용하여 소스 코드를 리팩토링 해보자.
앨범, 도서, 영화 이 세 가지 품목은 모두 상품이라는 카테고리에 속한다.
그 외의 추가될 다른 품목들 또한 상품이라는 카테고리에 속하게 되므로
상품이라는 큰 틀을 먼저 만들자.
public interface Product {
String getInformation();
}
상품 정보를 반환하는 메서드를 포함하고 있는 상품 인터페이스를 만들었다.
그리고, 앨범, 도서, 영화 등을 Product
인터페이스를 상속받아 구현한다.
public class Album implements Product{
private String title;
private String singer;
private Integer price;
public Album(String title, String singer, Integer price) {
this.title = title;
this.singer = singer;
this.price = price;
}
@Override
public String getInformation() {
String result = String.format("앨범이름: %s, 가수: %s, 가격: %d", this.title, this.singer, this.price);
return result;
}
}
public class Book implements Product{
private String title;
private String author;
private Integer price;
public Book(String title, String author, Integer price) {
this.title = title;
this.author = author;
this.price = price;
}
@Override
public String getInformation() {
String result = String.format("도서명: %s, 저자: %s, 가격: %d", this.title, this.author, this.price);
return result;
}
}
public class DVD implements Product{
private String title;
private String director;
private Integer price;
public DVD(String title, String director, Integer price) {
this.title = title;
this.director = director;
this.price = price;
}
@Override
public String getInformation() {
String result = String.format("상품명: %s, 가수: %s, 가격: %d", this.title, this.director, this.price);
return result;
}
}
그리고, 아까의 if-else
문을 반복 해야하는 코드를 수정해보자.
public class Information {
public String getInformation(Product product) {
return product.getInformation();
}
}
Product
를 구현한 상품을 인자로 전달 받아 getInformation()
메서드를 실행하여 상품 정보를 반환하도록 소스 코드를 수정하였다.
이제 더 이상 if-else
문을 반복하지 않을 뿐더러, 새로운 상품이 추가 되더라도 기존의 소스 코드를 변경하지 않아도 된다.
Product
인터페이스를 구현하여 새로운 상품을 만들기만 하면 되는 것이다.
위의 예시에서는 인터페이스를 사용하여 OCP를 적용했지만 이 방법 말고도 OCP를 적용할 수 있는 여러가지 방법들이 있다.
OCP 원칙을 지키면서 코드를 작성해야 하는 이유를 요약 해보자.
무언가 같은 코드가 3번 이상 반복된다면, 느낌상 '이건 좀 비효율적인거같은데...'
라는 생각이 든다면 OCP 원칙을 떠올리면서 소스 코드를 유연하게 바꾸는 습관을 들이도록 해보자.