일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- DesignPattern
- 객체지향프로그래밍
- Swfit
- 함수형프로그래밍
- 클린소프트웨어
- 부스트코스
- 의도
- 인터페이스
- protocol
- interface
- 개발
- 디자인패턴
- fp
- 협업
- 고차함수
- Solid
- iOS프로그래밍
- 함수형패러다임
- CleanCode
- OOP
- 아이폰프로그래밍
- 개발자
- 이름
- SWIFT
- 네이밍
- 클린코드
- 의존성
- 문법
- POP
- IOS
- Today
- Total
밤에 쓴 코드
OOP ) OCP -개방폐쇄 원칙(Open-closed principle) 본문
OCP -개방폐쇄 원칙
(Open-closed principle)
확장에 개방적이고 , 변경에 폐쇄적이어야한다.
음? 처음에 확장과 변경이 그렇게 다른 것인지 의문을 가졌었다.
일단 확장은 기능의 추가이고 , 변경은 기능구현의 변경이다.
기능을 추가할때는 딱 기능만 추가할 수 있게 , 즉 기존의 다른 부분들에 수정이 필요 없게 ,
기능이 변경이 , 이 기능을 가져다 쓰는 다른 코드들에 영향을 주지않게 해야한다는 것이다.
개발을 접하지 않은 사람들이 보기에 어찌보면 당연하게 들릴 것이다.
기능을 추가하면 기능만 추가 되면 그만이지. 변경되면 변경만 되면 그만이지 라고 생각 할 수 있다 .
이 과정은 생각보다 쉽지 않다 . 하지만 우리는 저 어렵지만 당연한 생각 을 실천해야한다.
변경에 폐쇄적이려면 어떻게 해야할까?
일단 변경될수 있는 부분과 변경이 안될 부분을 나누어 보자.
일반적으로 인터페이스는 무엇일까?
나는 인터페이스는 변하지 않을 것이라고 외부에게 알려주는 일종의 약속이라고 생각한다.
통용적으로 이해할 수 있는 인터페이스는 UI (User Interface ) 같은 것이 있을 것이다.
파란색으로 표시된 영역을 보자 . 아마 이 홈페이지를 처음보는 사람들도 알 수 있을 것이다 .
IOS_Training 이라는 파란색글자는 클릭시 , 저기와 관련된 어떤 페이지로. 이동할 것이라는 것을 누가 알려주지 않아도 알 수있다.
또 그 밑에 꼼꼼한 재은씨 - 실전편 공부 라고 주황색으로 표시된 부분을 보면 , 클릭시 이동은 하지않고 , 저 노란색으로 감싸진 영역에 대한 설명 을 하고 있다는 것을 누가 알려주지 않아도 모두 알 것이다.
이것이 인터페이스이고 , 이것은 변하지 않는 약속일 것이다 .
하지만 페이지가 이동되는 원리에 대해 이 페이지를 보는 사람은 알 필요가 없다.
또 페이지를 이동하는 원리가 바뀐다해도 , 이동은 무조건 할 것이기 때문에 사용자는 원리에 대해 자세히 알 필요가 없고 ,
누르면 이동한다는 사실만 알면된다.
여기서 변하지 않는 부분은 저 파란색글씨를 누르면 이동할 것이다라는 약속이다.
변하는 부분은 어디일까 ? 브라우저가 페이지를 이동하는 원리는 , 기술의 발전에 따라서 더 좋은 성능을 위해 다른 방식을 사용 할 수도있다. 저 원리가 바뀐다고 , 파란색글씨를 누르면 이동한다는 약속을 바뀌지않는다.
위의 예가 바로 변하는 부분과 변하지 않는 부분이 잘 나뉘어있어서 사용자가 혼란스럽지 않은 예라고 생각한다.
코드로도 비슷한 적용이 가능하다.
롤 캐릭터로 설명할 것이니 , 모른다면 한번해보고 마저 읽어보자.
struct 블리츠크랭크{
public func 그랩(){
print("👋")
}
public func 전자기장(){
print("⚡️⚡️⚡️⚡️")
}
}
// InGame
func 게임하기(내캐릭터: 블리츠크랭크){
내캐릭터.그랩()
내캐릭터.정전기장()
}
이런 식으로 게임을 하고있다.
하지만 어느날 업데이트를 통해 캐릭터의 수정에 생겼다.
struct 블리츠크랭크{
// public func 그랩(){
// print("👋")
// }
public func 강철주먹(){
print("👊")
}
public func 정전기장(){
print("⚡️⚡️⚡️⚡️")
}
}
이 수정은 기존의 코드를 부서뜨릴 것이다.
// InGame
func 게임하기(내캐릭터: 블리츠크랭크){
내캐릭터.그랩() // 컴파일에러
내캐릭터.정전기장()
}
이것은 그랩()
이라는 변경될 수있는 부분을 public
으로 만들어둔 개발자의 잘못이다.
변경될 부분을 숨겨두고 , 변경되지 않는 부분만 밖에 노출시켜줘야하는 데 , 그걸 어긴 대가는 이런 코드의 수정을 야기한다.
인터페이스 즉 - 변하지 않을 부분을 분리해보자
protocol 롤캐릭터 {
func Q()
func R()
} // 롤캐릭터가 무조건 해야하는 약속 / 변경될 수 없는 부분
struct 블리츠크랭크: 롤캐릭터 {
public func Q() {
그랩()
}
public func R() {
정전기장()
}
private func 그랩(){
print("👋")
}
private func 정전기장(){
print("⚡️⚡️⚡️⚡️")
}
}
코드의 양이 늘어났다. 여기서 주목해야할 것은 변하지 않을 부분과 변하는 부분을 나누었고 , 그에따른 접근제한자 또한 그에 맞게 설정 되었다는 것이다.
변하지 않을 부분에 대해서는 protocol 이라는 외부와의 약속으로 분리했고 , 변하는 부분에 대해서는 private
로 외부에서 모르게 만들어 주었다 .
이렇게 외부에서는 약속해둔곳 ( 변하지 않을 부분 ) 만 알 수있고 , 변할 부분은 모르게 되었다.
struct 블리츠크랭크: 롤캐릭터 {
public func Q() {
강철주먹()
}
public func R() {
정전기장()
}
// private func 그랩(){
// print("👋")
// }
private func 강철주먹(){
print("👊")
}
private func 정전기장(){
print("⚡️⚡️⚡️⚡️")
}
}
이렇게 패치를 통해 스킬이 변경된 다고 해도 , 위처럼 코드가 고장나지는 않는다 .
약속한 메시지를 보내면 , 구현부가 달라져서 다른 구현을 할 뿐 이다.
이렇게 변하지 않는 부분과 변하는 부분을 나눔으로써 , 변경에 폐쇄적이게 되었다.
그런 데 또 이번에는 캐릭터가 추가가 되었다.
struct 티모: 롤캐릭터 {
public func Q() {
맹독다트()
}
public func R() {
버섯함정()
}
private func 맹독다트(){
print("🧪")
}
private func 버섯함정(){
print("🍄")
}
}
func 게임하기(내캐릭터: 블리츠크랭크){
내캐릭터.Q()
내캐릭터.R()
}
캐릭터가 추가됨에 따라서 위의 게임하기 메소드의 수정이 필요할 것으로 보인다.
티모로는 게임을 할 수가 없으니까 .
이것은 확장에 대해 개방적이어야 하는 원칙에 어긋난 것이다 .
너무 구체적인 타입에 의존해서 생긴 문제가 되겠다.
func 게임하기(내캐릭터: 롤캐릭터){
내캐릭터.Q()
내캐릭터.R()
}
위처럼 바꿈으로서 , 조금더 유연한 확장이 가능해졌다.
이건 무조건 적인 확장이 아니다. 약속을 지키고 있는 누구나 들어와도 된다는 것이다.
그렇다면 확장은 약속을 지키는 확장에 대해서는 개방적이게 된 것이다 .
처음보다 훨씬 더 유연해지고 확장에 개방적이고 내부구체적인것의 수정에 폐쇄적으로 보인다.
'OOP' 카테고리의 다른 글
OOP ) DIP - 의존관계 역전법칙(Dependency inversion Principle) (1) | 2019.05.18 |
---|---|
OOP ) LSP - 리스코프 치환 원칙 (Liskov substitution principle) (1) | 2019.05.18 |
OOP ) SRP - 단일 책임 원칙 (Single responsibility principle) (0) | 2019.05.18 |
OOP ) SOLID - 객체지향 5원칙 (0) | 2019.05.18 |
OOP ) 타입, 추상화 (0) | 2019.04.28 |