본문 바로가기

CS공부

객체지향 프로그래밍 OOP (3)

드디어 객체지향 프로그래밍 3번째 글을 쓸 시간이 왔습니다 ㅜㅜ
한동안 개발과제 처리한다고 너무 바빠서 따로 공부한것을 글을 정리할 시간이 부족해 꽤 시간이 지난뒤 OOP에 대한 글을 마저 쓰게된다.

 

지금까지 공부했던것을 잠깐 정리하자면

 

절차지향 프로그래밍 -> 객체지향 프로그래밍으로 프로그래밍이 발전되어왔고

객체지향 프로그래밍에서 상당히 오랜시간 상속이 중요한 키워드로 학습이 이뤄졌지만 요즘은 상속보다는 추상화쪽에 좀 더 각광을 받는다고한다. 

그리고 객체지향프로그밍의 특징중하나가 정보의 은닉화인데 개발자가 원하는대로 접근제어 연산자를 통해 외부에서의 접근을 막을수있고 Swift에서는 open public internal fileprivate private 순으로 컨트롤 할 수 있다.

 

 OOP를 공부하게 되면 필연적으로 나오는게 SOLID에 대해 공부를 하게 되는데

 

지난번에 SRP에 대해 공부를 하였고 SRP의 대략적인 내용은 "하나의 클래스는 하나의 책임만을 가져야한다" 였다. 복습은 이 정도로 하고

이제 OLID에 대해 알아보자

 

OCP

"수정에는 닫혀있고 확장에는 열려있다"

코드의 수정은 최소한으로 진행되지만 확장에는 열려있다. 

쉽게이야기하면 해당 로직을 변경할때 최소한의 코드를 수정해야하지만 기존의 코드를 확장 시켜 사용할때는

잘 동작하는 기존 코드를 손대지 않고 새로운 코드를 추가하는것만으로 가능해야한다 

 

OCP 잘 준수하였을경우 최소한의 코드 수정으로 안정적으로 코드 수정이 가능, 사이드이팩트가 최소화가 된다

이 부분을 잘 활용할 수 있는 부분이 Swift에서는 Protocol을 통한 추상화를 생각 할 수 있다.

예시로 Uikit에 있는 많은 View에서 지원해주는 Delegate를 채택하여 사용하는 기능들을 볼수있겠다

ex ) collectionView의 다양한 기능들 , TextField의 기능들 등등

 

그리고 OCP에서 한 강좌에서 Enum에서 func을 사용할때 Switch문을 사용하지 않고 해당 func을 가지는 프로콜을 만들고 해당 프로토콜을 상속받은 여러 Class를 만드는 방식을 채택해서 실제 코드를 변경해 보았는데 불편하다고 느껴 기존의 Enum 코드를 그대로 사용하고있다. 이 부분은 계속 고민하면서 공부 , 수정을 거쳐야 할 것 같다.

 

 

LSP

자료형 S가 자료형 T의 하위형이라면 필요한 프로그램의 속성(정확성 , 수행하는 업무등)의 변경없이 자료형 T의 객체를 자료형 S 객체로 치환 할수 있어야한다

객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야한다.

이걸 어길 경우 잘못된 상속을 하는 경우가 생김

상속시 주의사항

부모의 함수를 자식이 거부

직사각형과 정사각형은 어떤 상속 구조를 가지나?

정답은?

 

 

 

 

 

 

 

 

 

이 질문을 처음 봤을때는 직사각형을 상속받은 정사각형 이라고 생각했다

근데 직사각형의 구조체에 setWidth라는 함수가 있다면?

직사각형에서 setWidth를 2배로하면 면적이 2배가되고

정사각형에서 setWidth를 2배로하면 면적이 4배가 된다

부모의 규칙을 깨버리기에 해당 아이템은 상속을 진행하면안된다

 

 

ISP

클라이언트(사용하는쪽)가 자신이 이용하지 않는 메서드에 의존하지 않아야한다

서버(사용되는 쪽)

상속을 해서 사용 , 내부에서 해당 서버의 인스턴스를 사용

인터페이스를 너무 큰단위로 만들었을경우 클라이언트가 사용하지 않을 메소드들이 강제가됨

상속 받은 메소드를 퇴화 (사용하지 않지만 선언만) 진행되기에 LSP 위반

objc -> Swift 넘어가면서 프로토콜이 더 작은단위로 나눠짐 ex Hashable , Equtable 등이있음

큰 덩어리의 인터페이스들을 구체적이고 작은 단위들로 분리시켜 클라이언트들이 꼭 필요한 메소드만 사용할 수 있게 해야한다.

인터페이스 분리의 법칙

인터페이스를 명확한 목졀별로 나누고 인터페이스를 기준으로 코딩 “POP 와 가장큰 연관이됨”

구현과 인터페이스를 분리하는게 최선은 아님

목적별로 인터페이스를 분리하면 장점

클래스의 인터페이스가 비대한경우 의존성 제거 Adpater 패턴 , 다중 프로토콜채용

하지만 항상 잘게쪼개는게 정답은 아님 항상 그 상황에 맞춰 최선의 유연함을 선택하는게 최선

 

 

DIP

코드에서 통상적으로 발견되는 특별한 문제 발견

- 상위 수준의 모듈이 하위 수준의 모듈에 의존성을 가지는 경향 

- 정책이 구체적인 것에 의존하는 경향

 

DIP의 목적은 모듈간의 의존관계를 끊는 방법 제시

"변경이 다른 코드에 영향을 최소화 시킬 방법을 제시"

코드는 어떻게든 의존관계를 가지기에 의존관계 자체를 없앨 수는 없음

그렇지만 의존관계를 잘 정리하지 않으면 코드는 상당히 난잡해짐 , 무엇이 무엇을 의존하느냐에 따라 결과가 달라짐

모든 클래스들이 최대한 의존관계가 적으면 좋지만 , 상호 의존적인 관계보다 단방향으로 흘러가는게 좋음

 

의존의 관계는 

구체적인 부분에서 추상적인 방향으로 의존관계를 가져야하고

추상적인 것이 구체적인것에 의존하지 않아야한다.

 

protocol Testprotocol {
	func Test()
}

class testClass : Testprotocl {
	func Test(){
    	print("테스트")
    }
}

 

DIP를 잘 지치기 위해 어디가 추상적인 개념이고 무엇이 구체적인 개념인지를 알아야한다