디자인 패턴을 찾다 보니, 객체 지향 프로그래밍 원칙을 좀 알아야 될거 같아서 찾아 보게 되었습니다.
SRP : single responsibility principle - 단일 책임의 원칙
객체는 오직 하나의 책임을 가져야 한다. (객체는 오직 하나의 변경의 이유만을 가져야 한다.)
사칙연산 함수를 가지고 있는 계산 클래스가 있다고 치자. 이 상태의 계산 클래스는 오직 사칙연산 기능만을 책임진다. 만일 프로그램이 대대적으로 공사를 들어가게 되더라도 계산 클래스가 수정될만한 사유는 누가 봐도 사칙연산 함수와 관련 된 문제 뿐이다. 이처럼 단일 책임 원칙은 클래스의 목적을 명확히 함으로써 구조가 난잡해지거나 수정 사항이 불필요하게 넓게 퍼지는 것을 예방하고 기능을 명확히 분리할 수 있게 한다.
위의 원칙이 제대로 지켜지지 않으면 어떻게 될까? 어떤 프로그래머가 위의 계산 클래스를 통해 GUI를 가지는 계산기 프로그램을 개발하고 있다. 그런데 중간에 귀찮다고 GUI 관련 코드를 계산 클래스에 넣어버렸다. 이렇게 되면 계산 클래스는 계산과 GUI라는 두 가지 책임을 지게 되는데 만일 GUI 관련 수정 사항이 발생하게 되면 별 상관도 없어보이는 계산 클래스를 고치게 된다. 이처럼 하나의 클래스가 두 가지 이상의 책임을 지니게 되면 클래스의 목적이 모호해지고 기능을 수정할 때 영향을 받는 범위도 커져서 유지보수가 힘들어지며[1] 결국 작성한 본인조차도 이게 정확히 뭐하는 클래스인지 명확히 설명할 수가 없는 스파게티 코드가 되어버린다.
이런건 실제 구현하다 어떤 기능을 추가하거나 고쳐야지 했을때, 건드려야 할 부분이 너무 많을때 이런식으로 할걸 하고 생각한 적이 종종 있는거 같다. 하지만 가끔은 기능을 명확히 나누기 힘들면 객체나 클래스의 계층 구조를 어떻게 형성해야 될까 하고 고민해 본적은 있는거 같다.
음 ios에서 뷰컨에서 서버의 데이터를 다운로드 하는 기능까지 구현한 부분은, 다른 클래스의 기능으로 빼는것이 뷰컨의 책임을 좀 덜어줄 수 있었을거 같다.
OCP : Open Closed Principle - 개방 폐쇄 원칙
객체는 확장에 대해서는 개방적이고 수정에 대해서는 폐쇄적이어야 한다는 원칙이다. 즉, 객체 기능의 확장을 허용하고 스스로의 변경은 피해야 한다.
예를 들자면, 스타크래프트의 유닛을 만든다고 치자. 당신은 이런저런 공통사항을 생각하며 메소드와 필드를 정의한다. 이 중엔 이동 메소드도 있다. 이동 메소드는 대상 위치를 인수로 받아 속도에 따라 대상 위치까지 유닛을 길찾기 인공지능을 사용해 이동한다. 하지만 잠깐 곰곰히 생각해보니 이러면 브루들링 같은 유닛의 기묘한 움직임을 구현할 때 애로사항이 꽃필 것 같다.
당신은 고민하다가 이동 메소드에서 이동 패턴을 나타내는 코드를 별도의 메소드로 분리하고, 구현을 하위 클래스에 맡긴다. 그러면 브루들링 클래스에서는 이동 패턴 메소드만 재정의하면 유닛 클래스의 변경 없이 색다른 움직임을 보여줄 수 있다! '유닛' 클래스의 '이동' 메소드는 수정할 필요조차 없다(수정에 대해선 폐쇄). 그냥 브루들링 클래스의 이동 패턴 메소드만 재정의하면 그만인 것이다(확장에 대해선 개방).
음 약간 ios나 안드로이드에서 이미 구현된 뷰컨등의 클래스들을 override해서 사용하는게 개방성을 높여주는거 같다.
LSP : Liskov substitution principle - 리스코프 치환 원칙
자식 클래스는 언제나 자신의 부모 클래스를 대체할 수 있다는 원칙이다. 즉 부모 클래스가 들어갈 자리에 자식 클래스를 넣어도 계획대로 잘 작동해야 한다는 것. 상속의 본질인데, 이를 지키지 않으면 부모 클래스 본래의 의미가 변해서 is-a 관계가 망가져 다형성을 지킬 수 없게 된다.
또다시 예를 들면, 컴퓨터용 '마우스' 클래스가 있다고 치자. 컴퓨터에 있는 PS/2 포트나 USB 포트를 통해 연결할 수 있고, 마우스를 바닥에 대고 움직이면 컴퓨터가 신호를 받아들인다는 것을 안다. 사용 면에서는 왼쪽과 오른쪽 버튼, 그리고 휠이 있어 사용자가 누르거나 굴릴 수 있을 것이다. 마우스가 볼마우스든 광마우스든, 아니면 GPS를 이용하건 간에 아무튼 사용자는 바닥에 착 붙여 움직일 것이고, 모든 마우스는 예상대로 신호를 보내 줄 것이다. 또한 만약 추가적인 특별한 버튼이 있는 마우스(상속)라도 그 버튼의 사용을 제외한 다른 부분은 보통의 마우스와 다를 바 없으므로 사용자는 그 마우스의 그 버튼이 뭔 역할을 하던간에 문제 없이 잘 사용한다. 여기까지 나온 마우스들은 LSP를 잘 지킨다고 볼 수 있다.
하지만 오른쪽/왼쪽 버튼 대신 옆쪽 버튼을 사용하는 펜마우스를 처음으로 접하게 되면 사용자는 평소 보던 버튼을 누를 수 없다며 이상을 호소할 것이다. 이런 경우 LSP를 전혀 지키지 못 하는 것이다.
음 그니까 마우스 위쪽에서 상하로 휠을 다루다 갑자기 옆에서 휠을 다루면 적응하기 힘들다는 내용인데, 자식이 부모랑 너무 다르다 즉 대체할 수 없는 경우는 안된다는것 같다.
ISP : Interface Segregation Principle - 인터페이스 분리의 원칙
클라이언트에서 사용하지 않는 메서드는 사용해선 안된다. 그러므로 인터페이스를 다시 작게 나누어 만든다. OCP와 비슷한 느낌도 들지만 엄연히 다른 원칙이다. 하지만 ISP를 잘 지키면 OCP도 잘 지키게 될 확률이 비약적으로 증가한다. 정확히 말하자면 인터페이스의 SRP라고 할 수 있다.이젠 좀 지겹겠지만 또 예를 들어보자. 게임을 만드는데 충돌 처리와 이펙트 처리를 하는 서버를 각각 두고 이 처리 결과를 (당연히) 모두 클라이언트에게 보내야 한다고 가정하자. 그러면 아마 Client라는 인터페이스를 정의하고 그 안에 충돌전달()과 이펙트전달(이펙트)를 넣어놓을 것이다. 그리고 충돌 서버와 이펙트 서버에서 이 인터페이스를 구현하는 객체들을 모아두고 있으며, 때에 따라 적절히 신호를 보낸다. 하지만 이렇게 해두면 충돌 서버에겐 쓸모없는 이펙트전달 인터페이스가 제공되며, 이펙트 서버에겐 쓸모없는 충돌전달 인터페이스가 제공된다. 이를 막기 위해선 Client 인터페이스를 쪼개 이펙트전달가능 인터페이스와 충돌전달가능 인터페이스로 나눈 뒤, 충돌에는 충돌만, 이펙트에는 이펙트만 전달하면 될 것이다. 또한 Client 인터페이스는 남겨두되 이펙트전달가능과 충돌전달가능 이 둘을 상속하면 된다. 그렇다고 인터페이스 너무 작게 쪼개서 떡칠은 하지 말자. 뭐든지 적당히. 물론 적당히가 제일 힘들다.
DIP : Dependancy Inversion Principle - 의존성 역전 원칙
추상성이 높고 안정적인 고수준의 클래스는 구체적이고 불안정한 저수준의 클래스에 의존해서는 안된다는 원칙으로서, 일반적으로 객체지향의 인터페이스를 통해서 이 원칙을 준수할 수 있게 된다. (상대적으로 고수준인) 클라이언트는 저수준의 클래스에서 추상화한 인터페이스만을 바라보기 때문에, 이 인터페이스를 구현한 클래스는 클라이언트에 어떤 변경도 없이 얼마든지 나중에 교체될 수 있다. (디자인 패턴 중 전략 패턴을 떠올리면 된다)
전략 패턴 [https://victorydntmd.tistory.com/292]
'취업,면접 대비 > cs 전공 공부' 카테고리의 다른 글
<cs 지식> AccessControl, private vs public, private vs file private (0) | 2020.10.06 |
---|---|
<용어> wrapper란? (1) | 2020.04.08 |
<자료구조>그래프 주요 용어 (0) | 2020.04.03 |
<객체지향 & 디자인 패턴> 객체지향과 디자인 패턴 1 (0) | 2020.04.02 |
<운영체제> 인터럽트 (0) | 2020.03.29 |