기록이 힘이다.

[면접을 위한 CS 전공지식 노트] 객체지향 프로그래밍 설계원칙 본문

IT서적

[면접을 위한 CS 전공지식 노트] 객체지향 프로그래밍 설계원칙

dev22 2023. 2. 19. 13:52
728x90

사수가 없는 회사에서 일을 하는 요즘, CS에 대해 공부할 필요성이 많이 생겼다. 기본 지식이 부족하여 어려움을 겪은 적이 한두번이 아니기 때문이다. 

 

#면접을 위한 CS 전공지식 노트를 통해 부족한 CS 지식을 채울 겸 개발자로 기본 소양을 다듬을 겸 읽게 되었는데 저번에 읽었던 #개발자가 되기 위해 꼭 알아야 하는 IT 용어 보다 내용이 더 깊이 있어 좋은 것 같다. 

 

SOLID 원칙,

S는 단일 책임 원칙 O는 개방-폐쇄 원칙, L은 리스코프 치환 원칙, I는 인터페이스 분리 원칙, D는 의존 역전 원칙

 

단일 책임 원칙(SRP, Single Responsigbility Principle)모든 클래스는 각각 하나의 책임만 가져야 하는 원칙입니다. 예를 들어 A라는 로직이 존재한다면 어떠한 클래스는 A에 관한 클래스여야 하고 이를 수정한다고 했을 때도 A와 관련된 수정이어야 합니다.

 

글로만 읽으니 이해가 좀 안되서 관련 코드로 설명하는 자료를 찾게 되었습니다. 

아래 글을 통해 SRP를 더 잘 이해하게 되었습니다.

 

[OOP] 💠 완벽하게 이해하는 SRP (단일 책임 원칙)

단일 책임 원칙 - SRP (Single Responsibility Principle) 단일 책임 원칙(SRP)는 객체는 단 하나의 책임만 가져야 한다는 원칙을 말한다. 여기서 '책임' 이라는 의미는 하나의 '기능 담당'으로 보면 된다. 즉,

inpa.tistory.com

하나의 클래스에 여러 기능(책임)을 넣느냐, 따로따로 클래스를 분리하여 기능(책임)을 분산시키느냐 설계는 프로그램의 유지보수와 밀접한 관련이 있다.

 

책임이 이것저것 포함된 클래스는 한 책임의 변경에서 다른 책임의 변경으로의 연쇄작용이 일어 나게 된다.

 

 책임을 적절히 분배함으로써 코드의 가독성 향상, 유지보수 용이라는 이점까지 누릴 수 있으며, 뒤에서 배울 다른 설계 원리들을 적용하는 기초가 되기도 한다.

 

단일 책임의 적용은 어렵지 않다. 각 책임(기능 담당)에 맞게 클래스를 분리하여 구성하면 끝이다.-->코드 설명 

 

실제로 프로그램을 설계 할때 객체에 얼만큼의 책임을 넣어야 단일 책임 원칙을 위배하지 않는지 범위 기준은 개발자 스스로 정해야 한다. 그리고 실무의 프로세스는 매우 복잡 다양하고 변경 또한 빈번하기 때문에 경험이 많지 않거나 업무 이해가 부족하면 자기도 모르게 SRP원리에서 멀어져 버리게 될 수도 있다.

따라서 평소에 많은 연습과 경험이 필요한 원칙이다.

 

어떤 프로그램을 개발하느냐에 따라 개발자의 생각이 제각기 다르기 때문에 따라서 단일 책임 원칙에 100% 답은 없다.
하지만 중요한 것은 클래스를 작성할 때 단일 책임 원칙을 지켰는지 끊임 없이 생각해보는 것이다. 하나의 클래스가 너무 많은 책임을 가지진 않았는지, 분리할 수 있는 변수와 메소드가 많은 것은 아닌지를 항상 고민해 봐야 한다.
추후 유지보수를 위해서라도 꼭 단일 책임 원칙을 기억하며 코드를 작성하도록 노력해야 한다.

SRP 원칙 적용 주의점

1. 클래스명은 책임의 소재를 알수있게 작명

2. 책임을 분리할때 항상 결합도, 응집도 따져가며

 

개방-폐쇄 원칙(OCP, Open Closed Principle)유지 보수 사항이 생긴다면 코드를 쉽게 확장할 수 있도록 하고 수정할 때는 닫혀 있어야 하는 원칙입니다. 즉, 기존의 코드는 잘 변경하지 않으면서도 확장은 쉽게 할 수 있어야 합니다.

글로만 읽으니 이해가 좀 안되서 관련 코드로 설명하는 자료를 찾게 되었습니다. 

 

[OOP] 💠 완벽하게 이해하는 OCP (개방 폐쇄 원칙)

개방 폐쇄 원칙 - OCP (Open Closed Principle) 개방 폐쇄의 원칙(OCP)이란 기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되어야 한다는 원칙을 말한다. 보통 OCP를 확장에 대해서는

inpa.tistory.com

보통 OCP를 확장에 대해서는 개방적(open)이고, 수정에 대해서는 폐쇄적(closed)이어야 한다는 의미로 정의한다.

여기서 확장이란 새로운 기능이 추가됨을 의미한다.

 

어렵게 생각할 필요없이, OCP 원칙은 우리가 객체 지향 프로그래밍을 하면서 질리도록 배웠던 추상화를 의미하는 것으로 보면 된다.

 

OCP 원칙을 따른 JDBC

자바 애플리케이션은 데이터베이스라고 하는 주변의 변화에 닫혀(closed) 되어 있는 것이다. 반대로 데이터베이스를 손쉽게 교체한다는 것은 데이터베이스가 자신의 확장에는 열려 있다는 말이 된다.

 

OCP 원칙 적용 주의점

확장에는 열려있고 변경에는 닫히게 하기 위해서는 추상화를 잘 설계할 필요성이 있는데, 추상화(추상 클래스 or 인터페이스)를 정의할 때 여러 경우의 수에 대한 고려와 예측이 필요하다.

 

만일 이러한 추상화에 따른 상속 구조를 처음부터 요상하게 구성하게 되면, 다음에 배울 LSP(리스코프 치환 원칙) r과 ISP(인터페이스 분리 원칙) 위반으로 이어지게 된다.

또한 OCP는 DIP(의존 역전 원칙)의 설계 기반이 되기도 한다.

따라서 이부분은 오로지 개발자의 역량에 달려 있다고 해도 과언이 아니다. 많은 경험과 경력만이 역량을 키울 수 있다.

 

리스코프 치환 원칙(LSP, Liskov Substitution Principle)은 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 하는 것을 의미합니다. 

 

 

[OOP] 💠 완벽하게 이해하는 LSP (리스코프 치환 원칙)

리스코프 치환 원칙 - LSP (Liskov Substitution Principle) 리스코프 치환 원칙은 1988년 바바라 리스코프(Barbara Liskov)가 올바른 상속 관계의 특징을 정의하기 위해 발표한 것으로, 서브 타입은 언제나 기반

inpa.tistory.com

교체할 수 있다는 말은, 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행이 보장되어야 한다는 의미이다.

우리가 지금까지 자바 프로그래밍을 하면서 질리도록 사용한 다형성 원리를 얘기하는 것이다. 

 

이러한 LSP 원칙을 잘 적용한 얘제가 자바의 컬렉션 프레임워크(Collection Framework) 이다.

만일 변수에 LinkedList 자료형을 담아 사용하다, 중간에 전혀 다른 HashSet 자료형으로 바꿔도 add() 메서드 동작을 보장받기 위해서는 Collection 이라는 인터페이스 타입으로 변수를 선언하여 할당하면 된다.

왜냐하면 인터페이스 Collection의 추상 메서드를 각기 하위 자료형 클래스에서 implements하여 인터페이스 구현 규약을 잘 지키도록 미리 잘 설계되어 있기 때문이다.

너무나도 당연하게 자바를 코딩하면서 사용해온 다형성을 규칙으로서 문서화한 것이 LSP 원칙이라고 보면 된다.

행동 규약 위반한다는 것은 자식 클래스가 오버라이딩을 할 때, 잘못되게 재정의하면 리스코프 치환 원칙을 위배할 수 있다는 의미이다.

 

자식 클래스가 오버라이딩을 잘못하는 경우는 크게 두 가지로 나뉜다. 

첫번째는 자식 클래스가 부모 클래스의 메소드 시그니처를 자기 멋대로 변경하거나, 두번째는 자식 클래스가 부모 클래스의 의도와 다르게 메소드를 오버라이딩 하는 경우가 있다.

 

 리스코프 치환 원칙은 협업하는 개발자 사이의 신뢰를 위한 원칙이기도 하다 -->관련 코드 참조

 

LSP 원칙 적용 주의점

 LSP 원칙의 핵심은 상속(Inheritance)이다.

그런데 주의할 점은, 객체 지향 프로그래밍에서 상속은 기반 클래스와 서브 클래스 사이에 IS-A 관계가 있을 경우로만 제한 되어야 한다.

그 외의 경우에는 합성(composition)을 이용하도록 권고되어 있다.

따라서 다형성을 이용하고 싶다면 extends 대신 인터페이스로 implements 하여 인터페이스 타입으로 사용하기를 권하며, 상위 클래스의 기능을 이용하거나 재사용을 하고 싶다면 상속(inheritnace) 보단 합성(composition)으로 구성하기를 권장한다.

 

인터페이스 분리 원칙(ISP, Interface Segregation Principle)은 하나의 일반적인 인터페이스보다 구체적인 여러 개의 인터페이스를 만들어야 하는 원칙을 말합니다.

 

 

[OOP] 💠 완벽하게 이해하는 ISP (인터페이스 분리 원칙)

인터페이스 분리 원칙 - ISP (Interface Segregation Principle) ISP 원칙이란 범용적인 인터페이스 보다는 클라이언트(사용자)가 실제로 사용하는 Interface를 만들어야 한다는 의미로, 인터페이스를 사용에

inpa.tistory.com

만약 인터페이스의 추상 메서드들을 범용적으로 이것저것 구현한다면, 그 인터페이스를 상속받은 클래스는 자신이 사용하지 않는 인터페이스마저 억지로 구현 해야 하는 상황이 올 수도 있다.

즉, 인터페이스 분리 원칙이란 인터페이스를 잘게 분리함으로써, 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공하는 것이다. 

인터페이스 분리 원칙은 마치 단일 책임 원칙과 비슷하게 보이는데, SRP 원칙이 클래스의 단일 책임을 강조한다면, ISP는 인터페이스의 단일 책임을 강조한다고 말할 수 있다.

다만 유의할 점은 인터페이스는 클래스와 다르게 추상화이기 때문에 여러개의 역할을 가지는데 있어 제약이 없긴 하다.

 

핵심은 관련 있는 기능끼리 하나의 인터페이스에 모으되 지나치게 커지지 않도록 크기를 제한하라는 점이다.

 

ISP 원칙 적용 주의점

ISP 원칙의 주의해야 할점은 한번 인터페이스를 분리하여 구성해놓고 나중에 무언가 수정사항이 생겨서 또 인터페이스들을 분리하는 행위를 가하지 말라는 점이다.

이미 구현되어 있는 프로젝트에 또 인터페이스들을 분리한다면, 이미 해당 인터페이스를 구현하고 있는 온갖 클래스들과 이를 사용하고 있는 클라이언트(사용자)에서 문제가 일어날 수 있기 때문이다.

 

본래 인터페이스라는 건 한번 구성하였으면 왠만해선 변하면 안되는 정책같은 개념이다.

따라서 처음 설계부터 기능의 변화를 생각해두고 인터페이스를 설계해야 하는데, 이는 현실적으로 참 힘든 부분이며 역시 개발자의 역량에 달렸다.

 

의존 역전 원칙(DIP, Dependency Inversion Principle)은 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 변화에 영향받지 않게 하는 원칙을 말합니다.

 

 

[OOP] 💠 완벽하게 이해하는 DIP (의존 역전 원칙)

의존 역전 원칙 - DIP (Dependency Inversion Principle) DIP 원칙이란 객체에서 어떤 Class를 참조해서 사용해야하는 상황이 생긴다면, 그 Class를 직접 참조하는 것이 아니라 그 대상의 상위 요소(추상 클래스

inpa.tistory.com

 추상성이 낮은 클래스보다 추상성이 높은 클래스와 통신을 한다는 것을 의미하는데 이것이 DIP 원칙이다.

상위의 인터페이스 타입의 객체로 통신하라는 원칙이다.

// 변수 타입을 고수준의 모듈인 인터페이스 타입으로 선언하여 저수준의 모듈을 할당
List<String> myList = new ArrayList()<>;
Set<String> mySet = new HashSet()<>;
Map<int, String> myMap = new HashMap()<>;

 

이처럼 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 변화에 영향받지 않게 하는 것이 의존 역전 원칙이다.
상위 클래스일수록, 인터페이스일수록, 추상 클래스일수록 변하지 않을 가능성이 높기에 하위 클래스나 구체 클래스가 아닌 상위 클래스, 인터페이스, 추상 클래스를 통해 의존하라는 것이다.