본문 바로가기
개발/설계

OCP 원칙을 지키지 못할 상황일때

by 돼지얍 2023. 3. 22.
반응형

OCP: 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

ocp는 로머트마틴이 정한 객체지향 SOLID 원칙에서 O 부분에 해당하는 5가지 중 핵심원칙이다.

객체지향 언어를 처음 배울 때 쉬운 예로 어떠한 클래스 객체를 사물에 대입시켜 그 사물에 큰 주제로 피라미드 형식으로 상속받아 뻗어 나가는 형태를 취하는 식으로 배운다.

 

예를 들어 TV라는 추상적 사물이 있다면 TV에 속한 스마트 TV, 아날로그 TV 등 TV라는 구상체에 존속되는 실질적인 구현체를 사용하게 된다. 여기서 Java 기준으로 TV가 추상화 클래스이라는 것을 알 수 있다.

 

추상화라는 개념을 아는 개발자는 이제 새로운 TV 제품의 기능을 설계할 때 TV 추상화 클래스를 상속하는 하위 클래스를 만들어 해당 하위 클래스에 기능 구현 설계를 할 것이다.

 

이러한 설계 방식은 다른 클래스에 변경을 주지 않으며 새로운 TV 제품을 설계하여 확장하기 때문에

OCP 개방-폐쇄 원칙: “소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.”  를 지키고 있다.

 

모든 TV 객체의 데이터를 처리하기 위해 하위 클래스들을 캐스팅 하여 특정 처리 메서드에 인자 값으로 넣는 방식으로 구현 한다면 추상클래스의 필드 값이 필요할 시 어떤 하위 클래스이든 공통적으로 필드 변숫값을 가지고 있기 때문에 객체 별로 중복 코드를 작성할 필요가 없어져 좋은 설계라고 볼 수 있다.


하지만 이러한 설계는 OCP 원칙을 지키지 못하게 되는 위험성이 올 수도 있는데
 TV 별로 처리해야 하는 기능이 추가되어 SRP 책임을 분리를 위해 TV 별 데이터를 처리할 수 있는
인터페이스를 생성하고 TV 종류별 하위 처리 클래스를 생성했다고 생각해 보자.

 

이 경우 TV 클래스의 하위 클래스가 가지고 있는 특수한 필드값이나 메서드가 필요하게 될 상황이 있기 때문에
기능이 추가된 것일 확률이 높다.

 

가장 편한 방법은 업캐스팅된 하위 클래스를  다운캐스팅하여 하위 클래스의 필드 값이나 메서드를 사용하는 방법이다.

하지만 다운캐스팅을 한다면 OCP 원칙을 깨트리는 방법이다.

다운캐스팅을 한다는 것은 객체가 변경된다는 것을 뜻하는데 즉, 확장을 위해 기존 객체가 변경된다는 것을 뜻하는 것이다.

 

여러 가지 TV를 하나로 묶어 공통적으로 하나의 인터페이스에서 처리하는 것은 좋은 객체지향 설계이지만 이를 역으로

특정한 TV의 기능을 위해 다시 구현체로 다운 캐스팅 하는 것은 특정 하위 클래스에 의존하는 것이다. 

 

하위 클래스에서 변경이 일어 났을 시 해당 클래스를 의존하는 클래스 객체들은 영향을 받게 되는데.

이러한 직접적인 변경은 의존하는 다른 클래스의 변경으로 이어지며 버그 발생위험이 커지고 복잡성이 증가하여 코드를 수정하는 비용은 늘어 날 수 밖에 없다.

 

현실적인 관점을 추가하자면 이러한 설계로 인해 많은 변경이 일어난다면 자신뿐만 아니라 해당 객체를 사용하는 팀원들에게까지 영향이 가게 되고 앞으로 내가 설계한 코드를 유지보수 해야하는 후 개발자 에게도 피해를 끼치는 설계가가 될 수 있다.


해결방법은?

필요에 의해서 추상화나 다형성을 사용하여 기능 설계를 결정했다면 좋은 객체지향 설계 방법이다.
하지만 업캐스팅된 객체를 사용할 때 다운 캐스팅하여 직접적으로 구현체에 접근하는 방식은 잘못된 설계 방식이다.

 

OCP를 지키는 방법

 

1. 추상 메소드를 사용 (다형성을 이용)

추상 메서드를 사용하여 하위 클래스 별로 오버라이드 메서드 통해 값을 전달, 처리한다. 이러한 방식으로 설계를 한다면
해당 추상 클래스를 의존하는 어떤 클래스 객체든 간 동일한 조건으로 원하는 결과를 얻을 수 있고 의존 객체가 메서드 내용을 몰라도 되기 때문에 의존 클래스 객체의 변경을 피할 수 있다.

 

2. 포함관계 방식으로 변경

만약 각 하위 클래스 별로 리턴 타입이 다르거나 복잡한 조건이 있다면 추상화를 과감히 포기 하고 단일 클래스 객체에 

여러 클래스를 포함 하는 관계 형식으로 리팩토링 해볼 수 있다.

 하나의 클래스 객체에서 원하는 객체를 가져와 사용 할 수 있기때문에 의존 객체들이 필요로 하는 조건을 만족 시킬 수 있다.

 

쉽게 말해서 TV라는 클래스를 추상화하는 것이 아니라 TV라는 클래스 객체 박스 안에 모든 TV 제품의 클래스 객체를 다 담은 다음 해당 제품 TV 객체를 꺼내서 사용하는 방식이다.

이러한 방식은 TV 객체를 생성할 때 해당 TV와 연관된 모든 클래스 객체를 생성한다는 단점이 있지만 상속하여 계속 구현해야 하는 추상 클래스 보다 코드의 복잡성이 줄어들고 공통 필드 값과 단일 객체만 제공하면 되기 때문에 맞춤형 설계를 하기 편리하다.

 

하지만 상황에 따라 추상화의 하위 클래스가 독립적으로 사용되는 비즈니스 로직 등 밀접한 연관관계가 있다면 추상화를 사용하는 것이 훨씬 효율 적일 수도 있다.

내가 생각하는 결론은 포함관계식으로 조립 방식으로 먼저 설계를 한 뒤 한 포함 객체가 독립적으로 사용되는 상황 예를 들어 보자면 모든 TV 제품을 포함하고 기능이 업그레이드 된 TV 2와 같은 기능이 필요할 시 추상화가 효율 적 일 수 있다.


해결 방법을 말하기까지 이야기가 길었지만 추상화의 개념만을 알고 왜 원칙을 지켜야 하는지, 왜 사용하게 됐는지를 의문을 가지는 것이 중요하고 좋은 개발자가 되는 방법이라 생각한다.

반응형

'개발 > 설계' 카테고리의 다른 글

캡슐화는 무엇인가?  (0) 2023.04.28