본문 바로가기

개발공부

2024 - 12 -24 객체지향적 설계

객체 지향 프로그래밍의 중요성

 

C++에서 객체 지향적으로 코드를 구현하는 것은 매우 중요합니다. 그 이유는 다음과 같습니다:

  1. 구현 시간 단축: 잘 설계된 코드는 재사용이 가능하여 개발 시간을 줄여줍니다.
  2. 유연성: 기능 변경이나 추가가 용이합니다. 코드의 구조가 잘 되어 있으면 수정이 쉽습니다.
  3. 오픈소스 및 라이브러리: 대부분의 라이브러리와 오픈소스 프로젝트가 객체 지향적으로 설계되어 있습니다.

 

 

설계 원리 이해


객체 지향 프로그래밍을 깊이 이해하기 위해서는 설계 원리를 아는 것이 중요합니다. 그 중 하나가 응집도입니다.


응집도


응집도는 클래스 내의 모듈들이 얼마나 잘 어우러져 있는지를 나타내는 개념입니다. 응집도가 높을수록 클래스의 품질이 좋아지는데, 이는 관련된 기능들이 함께 모여 있기 때문입니다.

하나의 클래스 안에는 서로 연결된 코드들이 모여 있는 것이 좋습니다. 이렇게 하면 코드의 구조가 더 명확해지고, 유지보수도 쉬워집니다. 

 

예를 들어, 같은 기능을 수행하는 코드들이 한 곳에 모여 있다면, 나중에 수정이나 업데이트를 할 때도 훨씬 수월하겠죠.

 

예시:

응집도가 낮은 클래스: StudyClass라는 클래스가 국어, 수학, 영어를 모두 다루는 경우. 이 클래스는 각 과목의 기능이 분산되어 있어 관리하기 어렵습니다.

응집도가 높은 클래스: 각 과목별로 나누어진 클래스들 (예: 국어, 수학, 영어). 각 클래스는 특정 과목의 기능만 다루므로 관련된 코드가 모여 있어 관리가 용이합니다. 예를 들어, 수학에는 수학 문제 풀이 기능만 포함되어 있습니다.

 

이렇게 각 클래스가 특정 기능에 집중하면, 나중에 코드를 추가할 때도 관련 있는 클래스에 기능을 추가하기가 쉽습니다. 

나중에 필요시 과학이라는 클래스를 만들어도 되고 수학이라는 클래스에 수학과 관련된 함수를 추가해도 알아 보기 쉽습니다.

 

요약

응집도가 높은 클래스는 관련도니 기능이 모여있어 관리가 쉬움.

기능 추가 및 변경이 용이합니다. 유연한코드

명확한 구조로 코드의 가독성이 높아집니다.

 

 

 

결합도

각 모듈들이 서로 얼마나 의존 하는지 나타냅니다.

결합도가 낮을 수록 각 모듈이 변경될때 다른 모듈에 끼치는 영향이 줄어들게 됩니다.

 

예시

 

결합도가 높은 클래스 : Car이라는 클래스는 Engine이라는 클래스를 사용하는 경우 엔진에서 나중에 디젤엔진, 가솔린엔진등이 추가되면 Car라는 클래스에서 코드 변경이 일어나게 됩니다.

 

 

결합도가 낮은 클래스 : 디젤엔진 , 가솔린 엔진등은 Engine이라는 추상 클래스를 상속 받고 그걸 Car 클래스에서 사용합니다. Car클래스는 추상클래스 Engine을 상속 받는 다양한 엔진을 사용할 수 있습니다.

 

 

이렇게 각 다른 클래스와 결합이 낮을 수록 코드 변경하기가 쉽습니다.

 

 

요약 

결합도가 높은 클래스는 변경해야할 코드가 많아짐

결합도가 낮은 클래스는 변경해야할 코드가 적어짐

 

 

 

SOLID 원칙

 

개발자들이 만든 설계원칙인 SOLID원칙을 사용 하면 객체지향프로그래밍을 할 수 있습니다.

SOLID원칙은 5가지의 원칙이 있습니다 목적은 "유지보수성 및 확장성 향상" , "변경을 유연하게 함"

입니다.

 

 

 

단일 책임 원칙(SRP)

각 클래스는 하나의 책임만 가져야 하며, 

그 책임을 변경이 꼭 필요한 경우에만 변경한다는 원칙입니다.

 

예시는 응집성이랑 비슷합니다.

국어 영역은 국어 클래스에서 수학 영역은 수학 클래스로 클래스의 책임을 나누어서

각 클래스가 특정한 책임만 가지므로, 변경이 필요할 때 해당 클래스만 수정하면 되어 유지보수와 확장이 용이해집니다.

 

 

 

개방 폐쇄 원칙(OCP)

수정은 피하고 확장은 가능하게 하자는 원칙입니다.

 

기존 코드를 수정하면  버그를 유발할 수 있기에 기존의 기능에 영향을 줄 수 있으므로

이미 작성된 코드는 수정하지 않고, 새로운 기능이 필요할 때는 기존 코드를 확장하는 방식으로 구현해야 합니다.

 

추상화의 사용이 대표적으로

공통된 추상클래스를 정의하고 

구체적인 구현 클래스에서 구현하는 방식으로 확장하는 것입니다.

 

예시는 결합도와 같이

엔진의 기능을 추가하는 것보다 디젤 , 가솔린 엔진클래스를추가하여 자동차 클래스가 사용하는 

방식으로 사용할 수 있습니다.

리스 코프 치환 원칙(LSP)

부모 클래스를 자식클래스로 대체 했을때 프로그램의 기능이 정상적으로 작동 해야한다 입니다.

 

부모 클래스에서 정의한 기능은 자식 클래스에서도 동일하게 동작해야 하며, 기능이 변경되서는 안됩니다.

기능이 맞지 않는 부모 클래스를 상속받으면, 자식 클래스에서 불필요한 코드를 작성해야 하거나, 부모 클래스의 기능을 적절히 구현하지 못해 버그가 발생할 수 있습니다.

 

따라서, 자식 클래스는 부모 클래스로부터 상속받은 기능을 그대로 사용할 수 있어야 하며, 추가적인 기능을 제공할 수 있어야 합니다.

 

예를 들면 

class Bird {
public:
    virtual void fly() {
        // 날아가는 로직
    }
};

class 독수리 : public Bird {
public:
    void fly() override {
        독수리 날아가는 로직
        
    }
};

class 닭 : public Bird {
public:
    void fly() override {
        닭이 날 수 없으니 기능 수행 불가
        
    }
};

새중에서 닭은 못날아가니 자식클래스는 기능을그대로 사용하지 못하고 불필요하게 못나는 코드를 작성해야합니다.

그러면 새중에서 공통점을 찾아서

class Bird {
public:
    virtual void makeSound() {
        // 소리 내는 로직
    }
};

class 독수리 : public Bird {
public:
    void makeSound() override {
        독수리 소리쳤다
        
    }
};

class 닭 : public Bird {
public:
    void makeSound() override {
        닭은 울부짖었다.
        
    }
};

 

소리 내는 로직이라면 닭도 소리낼 수 있으니 프로그램의 기능도 정상적으로 유지됩니다.

 

여기서 궁금증이 생기긴 합니다 새중에서 못나는 새보다 날 수 있는 새들도 많은데 이럴 경우는 어떻할까 
생각하고 알아보니 

 

다중상속 예 ) class 독수리 : public 새 , public 비행가능{}
혹은 

독수리 안에 조류와 비행 가능 클래스를넣어서
해당 클래스를 사용하는 방식

그리고 전략 패턴 이라는 걸 사용해서  해당 상태에 맞게 새의 행동을 변경하는 등의 방식이 있었습니다.

 

이건 사용자가 원하는 방식으로 할 수 있을 것 같습니다.

인터페이스 분리 원칙(ISP)

인터페이스 와 추상화의 차이점 :

메서드의 시그니처만 포함하며, 상태를 가지지 않습니다.

다중 상속을 지원하여 여러 인터페이스를 구현할 수 있습니다.

 

요약

추상화는 단일 상속에다가 변수를 가지고 있고

인터페이스는 변수는 가져선 안되고 함수만을 전달 하는 역할 을 한다.

 

 

 클라이언트가  사용하지 않는 함수에 의존하지 않아야 한다는 원칙

 

클래스에 불필요한  메서드를 구현하면 안된다 라는의미

 

 

하나의 큰 인터페이스 대신 여러 개의 작은 인터페이스로 나누어야 합니다. 

클라이언트는 자신이 필요한 메서드만 포함된 인터페이스에 의존하게 됩니다.

클라이언트가 필요로 하는 기능만을 가진 인터페이스를 사용해서

새로운 기능이 추가되거나 기존 기능이 변경될 때, 다른 클라이언트에 미치는 영향을 최소화할 수 있습니다.

 

다중상속 위의 리스코프 치환 원칙 설명할때 공부했는데 그게 인터페이스 분리 원칙

원리였습니다.

 

예를 들어, Printer와 Scanner 기능을 가진 인터페이스가 있다고 가정할 때, 프린터만 필요한 클래스가 이 인터페이스를 구현하면 스캐너 관련 메서드에 의존하게 되어 불필요한 코드를 작성해야 합니다.

 

하지만 Printer, Scanner 라는 인터페이스로 나누어서 프린터만 필요하면 프린터만 둘다필요할경우 

둘다 상속받게 함으로 불필요한 코드를 줄일 수 있습니다.

 

의존 역전 원칙(DIP)

다른 클래스나 모듈(함수)을 사용하는 사용하는 클래스는 여러 작업을 처리하는 세부사항을 담은 

클래스에 의존 하면 안되고 둘다 추상화에 의존해야한다.

 

예 시) 자동차 클래스에서 엔진과 클래스가 있을 경우

 

의존이 심할 경우: 자동차에있는 엔진이 가솔린이나 디젤 등으로바뀌거나 , 바퀴가 A바퀴에서 B바퀴로 바뀌어야 될경우

자동차의 클래스 내부 코드를 바꿔야됩니다.

 

 

의존이 적을경우 : 자동차내부에 엔진의 기능을 받은 자식클래스 가솔린으로바꾸면 해결,

바퀴를 상속받은 A바퀴로 바꾸면 해결,

a에서 b로 바꾸어도 결국 엔진이나 바퀴를 상속 받고 있어서 자동차 코드를 바꿀 필요가 없습니다. 

 

 

 

이렇게 SOILD  원칙(5개)을 배웠습니다.

내용은 간단하게 불필요한 코드작성을 줄이고 코드를 확장 변경 할 수 있게끔 만드는 원칙으로 이해 하면 편합니다.