C ++/C++ 정리

객체지향 프로그래밍의 5대 원칙과 클래스의 상속

무면허 개발자 2023. 1. 10. 16:30

( 본 게시글은 작성자가 메모용으로 사용하는 용도임을 밝힙니다. 정보가 불확실할 수 있으니 참고 부탁드립니다 )

 

객체 지향 프로그래밍, OOP 5대 원칙

C++에서는 객체 지향 프로그래밍을 하게 되는데, 이를 OOP ( Object oriented Programming ) 이라고 하고 이 OOP를 하는데 있어서 5대 원칙이 존재한다. 이 5대 원칙은 통칭 SOLID라고 하며, 5대 원칙의 한글자씩 따와서 칭하게 된다.

 

1. 단일 책임의 원칙, SRP ( Single Responsibility Principle )

단일 책임의 원칙은 모든 클래스는 단 하나의 책임을 가져야한다는 것으로, 클래스가 수정되어야할 이유는 한가지만 존재해야한다는 뜻이다. 여기서 한가지만 존재해야한다는 뜻은 클래스가 부담해야 할 책임이 다른 여러곳에서 책임을 받아서는 안된다는 뜻이다. 만약 여러 책임을 가지고 있다면, 해당 클래스를 수정해야할 필요가 더 많이 생기므로, 수정해야할 이유가 많아진다. 하나의 책임만을 가지고 있다면 해당 문제의 책임을 가지고 있는 클래스를 특정지을 수 있기 때문에 수정해야할 이유와 대상이 명확해진다.

단일 책임의 원칙을 준수함으로써 얻을 수 있는 장점은 하나의 책임, 기능만을 가지기 때문에, 코드의 가독성이 향상되고, 책임이 하나이기에 유지보수가 쉽다. 또한 클래스의 기능 변경 또한 자유로워진다.

후에 코딩이 길어지면 클래스 및 다른 곳에서 의존성이 커지는데, 그럴때 수정해야할 상황이 오면 딱 한곳만 수정하면 되기 때문에, 유지보수가 쉽다.

 

2. 개방 폐쇄의 원칙, OCP ( Open Closed Principle )

개방 폐쇄의 원칙은 확장에는 개방적이고, 수정에는 폐쇄적이여야 한다는 원칙으로 확장에 개방적이다는 요구사항이 변경될때 새로운것을 추가하여 클래스의 기능을 확장시킨다는 뜻이고, 수정에 폐쇄적이다는 기존의 코드를 수정하지 않고도, 클래스의 기능을 변경할 수 있어야 한다는 뜻이다. 이 개방 폐쇄의 원칙은 추상화랑 연관이 있는데 추상화는 중요한 부분만을 묶어 간단명료하게 만드는 것으로 중요한 부분은 결국 일반적으로 변하지 않는 개념으로 추상화를 거친 변하지 않을 부분을 고정하고 변해야할 부분이 필요하다면 그 부분만을 수정한다면 이 개방 폐쇄의 원칙을 지킬 수 있는 것이다.

이 원칙의 장점으로는 추상화를 거치기 때문에, 변경횟수가 적어 개발시간이 감소되고, 재사용이 용이해진다.

 

3. 리스코브 치환의 원칙, LSP ( Liskov Substitution Principle )

리스코브 치환 원칙은 자식 클래스는 부모 클래스를, 즉 하위 클래스는 상위 클래스를 대체할 수 있어야 한다는 원칙이다. 즉 어떠한 객체를 사용하는 프로그램이 부모 클래스가 자식 클래스로 교체가 되어도, 차이점이 인식되지 않고, 부모에게서 상속 받은 내용을 통해 자식 클래스를 사용할 수 있어야 한다.

 

4. 인터페이스 분리 원칙, ISP ( Interface Segregation Principle )

인터페이스 분리 원칙은 아무리 효율좋게 객체가 설계되었더라도 해당 객체를 사용하는 각기 다른 프로그램이 목표와 요구사항이 다르다면, 인터페이스를 통해 분리시켜줘야 한다는 원칙이다. 이 원칙을 준수한다면 프로그램이 사용하고자 하는 퍼블릭 인터페이스만을 접근하여 외부에서의 불필요한 접근을 줄일 수 있다. 또한 인터페이스는 객체와는 영향을 서로 받지 않으며, 여러개의 구체적인 인터페이스가 범용적인 하나의 인터페이스보다 낫다는 것을 알아야 한다. 인터페이스를 통해 필요하고자 하는 부분만을 분리하여 사용하면 객체의 비중을 좀더 가볍게 할 수 있고, 세밀하게 컨트롤할 수 있다.

 

5. 의존성 역전의 법칙, DIP ( Dependency Inversion Principle )

의존성 역전의 법칙은 상위 클래스가 하위 클래스에 의존하면 안되는 원칙으로, 하위 클래스의 변경이 상위 클래스의 변경에 영향을 주어서는 안된다. 상위, 하위 클래스를 만들어 줄때 대분류, 소분류 처럼 카테고리에 맞게 설정 해줘야하는데, 결국 이는 추상화에 의존하게 되는 부분이다. 상위 클래스는 변하지 않는 추상화된 클래스여야 하고, 하위 클래스는 변하기 쉬운 구체적인 클래스여야 한다.

 

=======================================================================

 

클래스의 상속

 

5대 원칙을 설명하면서 부모에서 자식으로의 상속 개념을 많이 설명했는데, 상속에 대해서 예제를 들어보겠다.

우선 부모가 될 상위 클래스를 만들어 준다.

클래스 Base를 선언하고 public에 void형 함수 Inc_Collection과 Show_Collection을 선언해주는데, 이때 Inc_Collection는 int형 매개변수를 넣어주고 리턴값을 Collection += c로, Show_Collection는 리턴값을 Collection : 뒤에 콜렉션 값을 출력하게 해준다. Show_Collection는 콜렉션 값을 출력하는 함수가 될 것이다. 그리고 기존의 private와 다르게 새로운 접근 지정자를 넣어줬는데 protected 접근 지정자는 private와 같이 외부에서의 접근을 막지만 상속관계에 있어서 자신의 자식 클래스에게는 접근을 허용하게 한다. 그러므로 Base의 자식 클래스는 이 Collection에 접근할 수 있다.

 

다음은 자식 클래스를 만들어 준다.

Child라는 자식 클래스를 선언해준다. 그리고 void형 함수 Inc_Diamond, Diamond_to_Collection를 선언해주고, private에는 변수 Diamond를 선언해준다. 여기까지는 Base와 Child는 상속관계가 아닌 개별적인 클래스지만, 상속을 해주려면 상속을 시켜줄 클래스 class Child : 옆에 상속시켜줄 접근 지정자와 클래스명을 적어준다. 또한 중요한 것은 위에서도 상속받는 클래스의 헤더파일을 추가해줘야하는데, 이때 헤더파일의 순서는 부모가 자식보다 위로 와야한다는 점이다. Diamond_to_Collection 함수의 정의는 해당 함수내에서 Inc_Collection을 호출하여 매개변수에 Diamond 값을 넣어준다. 여기까지만 봐도 알 수 있는 점은 Child 내에서 선언해주지 않은 함수 Inc_Collection을 호출할 수 있다는 점과 protected 공간의 Collection 변수를 사용할 수 있는 점으로 이미 상속이 되었다는 것을 조금 알 수 있다.

이제 main 함수에서 실행을 시켜본다. 부모 클래스 없이 자식 클래스 Child를 c로 선언해준다. 여기까지만 하고 출력을 먼저 해주면 부모 생성자 호출 -> 자식 생성자 호출 -> 자식 소멸자 호출 -> 부모 소멸자 호출 순으로 출력 되는것을 알 수 있는데, 우선 Child c가 상속 받은 Base 클래스의 기본 생성자 호출이 먼저오고 그다음 자식이 되는 Child 기본 생성자가 호출 된다. 이 다음 순서는 부모가 아닌 자식 소멸자가 호출되는데, 부모 소멸자가 먼저 호출되면 자식은 부모로부터 상속받은 것들이 NULL값이 되기 때문에, 상속을 받아 값을 임의로 다룬 자식을 먼저 소멸자 호출을 해주고 그 다음 부모 소멸자를 호출해준다. 이때 자식을 먼저 소멸시킨다고 해도, 기존 상속해주는 부모 클래스에게는 아무런 영향이 없다.

 

다음 위처럼 Inc_Collection에 10을 넣고 Show_Collection 함수를 호출해보면 콘솔창에는 10이 출력되는 것을 볼 수 있다. 다음 Inc_Diamond에 20일 넣고 Diamond_to_Collection을 호출한다. 이렇게 되면 Inc_Diamond에 인자 20이 넘어가 Diamond에 20이 초기화 될것이고, Diamond_to_Collection으로 넘어가 Inc_Collection가 Diamond의 값을 인자로 넘길텐데 Diamond의 값은 20이므로 20의 값이 기존 Collection값 10에 더해져 30이 Collection에 초기화될 것이다. f5를 눌러 콘솔창을 확인해보면 30이 제대로 나오고 있다.

다시 300의 값을 넣어도 300이 더해져 330이 출력 되고 있는 것을 확인할 수 있다.

이처럼 상속을 통해 자식 클래스가 선언하지 않은 함수, protected를 통해 변수도 접근하여 값을 가져올 수 있다.