객체지향 프로그래밍이란?

 

객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 컴퓨터 프로그래밍의 패러다임 중 하나로, 프로그램을 '객체'의 모임으로 파악하고자 하는 것이다. 객체 지향 프로그래밍은 유연하고 변경이 용이할 뿐만 아니라 유지 보수가 쉽기 때문에 대규모 SW개발에서 자주 사용된다.

 

 

 

 

 

 

 

 

 

 

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

 

 * 클래스(Class)

- 같은 종류의 집단에 속하는 속성(attribute)과 행위(behavior)를 변수와 메서드로 정의한 것

- 객체를 만들기 위한 일종의 메타정보

- 클래스는 다른 클래스 또는 외부 요소와 독립적으로 디자인

- 쉽게 말해 클래스는 비슷한 구조를 계속 만들어 내기 위한 일종의 틀

 

 

* 객체(Object)

- 클래스에서 정의한 것을 토대로 실제 메모리에 할당된 것

- 구현된 모든 대상을 의미

- 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성을 가지고 있고 다른 것과 식별 가능한 것

- 추상적이고 포괄적인 개념

 

 

* 인스턴스(Instance)

- 객체가 메모리에 할당되어 실제 사용되고 있음을 의미

 

클래스가 붕어빵 틀이라면 그 틀을 통해 생성된 객체(붕어빵) 하나하나를 해당 클래스의 인스턴스(Instance)라고 부른다. 즉, 인스턴스란 현실의 객체를 소프트웨어 내에서 구현한 실체라고 볼 수 있고, 이렇게 생성된 인스턴스들은 각자 고유의 특성을 가지고 독립적으로 존재한다.

 

 

* 메서드(Method)

- 클래스로부터 생성된 객체를 사용하는 방법

- 객체에 명령을 내리는 메시지

 

 

 

 

 

 

 

 

 

 

 

 

객체지향 프로그래밍의 등장 배경

 

1. 순차적 프로그래밍 (=비구조적 프로그래밍)

 

정의한 기능의 흐름에 따라 순서대로 동작을 추가하며 프로그램을 완성하는 방식이다.

 

간단한 프로그램의 경우, 이렇게 코드를 짜게 되면 흐름이 눈으로 보이기 때문에 매우 직관적일 것이다. 그러나, 조금이라도 프로그램의 규모가 커지게 되면 곤란해진다. 만일 A → B → C 라는 동작을 구현하다가, C 에서 A 로 돌아가야할 상황이라면 goto 를 활용해야 한다.

 

그러나 goto 문을 무분별하게 활용하게 되면, 그야말로 스파게티 그 자체가 완성된다. 쭉 나열된 코드 속에서 위로 갔다가 아래로 갔다가 난리도 아니게 된다.

 

 

 

2. 절차적 프로그래밍 (=구조적 프로그래밍)

 

순차적 프로그래밍을 보완하기 위해 탄생한 것이 절차적 프로그래밍이다. 절차적 프로그래밍에서 '절차'는 함수를 의미한다. 따라서 절차적 프로그래밍이란, 반복되는 동작을 함수 및 프로시저 형태로 모듈화하여 사용하는 방식이다.

 

반복 동작을 모듈화하여 코드를 많이 줄일 수 있다는 장점이 있지만, 프로시저라는 것 자체가 너무 추상적이라는 단점이 있어 이를 보완하기 위해 탄생한 것이 객체지향 프로그래밍이다.

 

 

 

 


※ 참고 ※

 

최근의 프로그래밍 패러다임

  • 명령형 프로그래밍 : 무엇(What)을 할 건지를 나타내기 보다, 어떻게(How) 할 건지 설명하는 방식
    • 절차지향 프로그래밍 : 수행되어야 할 처리 과정을 포함하는 방식
    • 객체지향 프로그래밍 : 객체들의 집합으로 프로그램의 상호작용 표현
  • 선언형 프로그래밍 : 어떻게(How) 할 건지를 나타내기 보다, 무엇(What)을 할 건지 설명하는 방식
    • 함수형 프로그래밍 : 순수 함수를 조합하고 소프트웨어를 만드는 방식

 

 

 

 

 

 

 

객체지향 프로그래밍의 특징

 

1. 추상화

필요로 하는 속성이나 행동을 추출하는 작업

 

추상적인 개념에 의존하여 설계해야 유연함을 갖출 수 있다.

즉, 세부적인 사물들의 공통적인 특징을 파악한 후 하나의 집합으로 만들어내는 것이 추상화

 

 

 

 

2. 캡슐화

낮은 결합도를 유지할 수 있도록 설계하는 것

* 결합도(coupling)란 어떤 기능을 실행할 때 다른 클래스나 모듈에 얼마나 의존적인가를 나타내는 말로, 독립적으로 만들어진 객체들 간의 의존도는 최대한 낮게 만드는 것이 중요하다.

 

즉, 한 곳에서 변화가 일어나도 다른 곳에 미치는 영향을 최소화 시키는 것이 캡슐화

객체가 내부적으로 기능을 어떻게 구현하는지 감추는 것이라고도 볼 수 있다.

 

캡슐화에서는 정보 은닉을 활용하여 외부에서 접근할 필요가 없는 것들은 private으로 접근하지 못하도록 제한을 둔다.

(객체안의 필드를 선언할 때 private으로 선언하는 이유)

 

 

 

 

3. 상속

여러 개체들이 지닌 공통된 특성을 부각시켜 하나의 개념이나 법칙으로 성립하는 과정

 

기존의 클래스에 기능을 추가하거나 재정의하여 새로운 클래스를 정의하는 것으로, 상속을 이용하면 기존에 정의되어 있는 클래스의 모든 필드와 메소드를 물려받아, 새로운 클래스를 생성할 수 있다.

 

상속을 활용하면 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있고, 코드를 공통적으로 관리할 수 있기 때문에 코드의 추가 및 변경이 매우 용이하다.

 

 

 

 

4. 다형성

서로 다른 클래스의 객체가 같은 메시지를 받았을 때 각자의 방식으로 동작하는 능력

 

다형성은, 상속과 함께 활용할 때 큰 힘을 발휘한다. 이와 같은 구현은 코드를 간결하게 해주고, 유연함을 갖추게 해준다.
즉, 부모 클래스의 메소드를 자식 클래스가 오버라이딩해서 자신의 역할에 맞게 활용하는 것이 다형성

 

 

 

 

 

 

 

 

 

 

 

 

 

객체지향 설계 원칙

SOLID라고 부르는 5가지 설계 원칙이 존재

 

 

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

- 클래스는 단 한 개의 책임을 가져야 한다.

- 클래스를 변경하는 이유는 단 한 개이어야 한다.

- 단일 책임 원칙을 제대로 지키면 변경이 필요할 때 수정할 대상이 명확하다.

 

 

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

- 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.

- 기능을 변경하거나 확장할 수 있으면서, 그 기능을 사용하는 코드는 수정하지 않는다.

- 개방 폐쇄 원칙을 지키지 않으면, instanceof와 같은 연산자를 사용하거나 다운 캐스팅이 일어난다.

 

 

3. LSP(Liskov Substitution Principle) - 리스코프 치환 원칙

- 하위 타입은 상위 타입을 대체할 수 있어야 한다.

- 상위 타입의 객체를 하위 타입의 객체로 치환해도, 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.

- 상속 관계가 아닌 클래스들을 상속 관계로 설정하면, 이 원칙이 위배된다.

 

 

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

- 인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다.

- 클라이언트의 목적과 용도에 적합한 인터페이스만을 제공해야 한다.

- 각 클라이언트가 필요로 하는 인터페이스들을 분리함으로써, 해당 클라이언트가 사용하지 않는 인터페이스에서 변경이 발생하더라도 영향을 받지 않아야 한다.

 

 

5. DIP(Dependency Inversion Principle) - 의존 역전 원칙

- 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안된다.

- 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.

- 즉, 추상화에 의존하며 구체화에는 의존하지 않는 설계 원칙을 의미한다.

 

* 고수준 모듈 : 변경이 없는 추상화된 클래스 (또는 인터페이스)

* 저수준 모듈 : 변하기 쉬운 구체적인 클래스

 

 

 

 

 

 

 

 

 

 

 

 

 

객체지향 프로그래밍의 장단점

 

장점

 - 코드의 재사용성이 증가한다.

 - 유지 보수가 용이하다.

 

단점

 - 설계에 시간과 비용이 많이 들 수 있다.

 - 객체가 많아지면 용량이 커지고, 처리속도가 상대적으로 느리다.

 

 

'CS > SW공학' 카테고리의 다른 글

테스트 주도 개발 (TDD: Test Driven Development)  (0) 2022.09.02

TDD란?

  • Test Driven Development의 약자로 테스트 주도 개발 이라고 함
  • 소프트웨어를 개발하는 여러 방법론 중 하나
  • 반복 테스트를 이용한 소프트웨어 방법론으로, 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 추가하는 단계를 반복하여 구현

 

 

 

 

 

 

TDD 개발주기

  1. RED : 문제를 정의하는 것에 집중
  2. GREEN : 그 문제를 해결하는데 집중
  3. Refactor : 작성한 코드를 개선하는데 집중

 

 

 

 

 

 

 

 

 

 

TDD는 어떤 상황에서 해야할까

  1. 처음 해보는 프로그램 주제 (내부적인 불확실성이 높은 경우)
  2. 고객의 요구조건이 바뀔 수 있는 프로젝트 (외부적인 불확실성이 높은 경우)
  3. 개발하는 도중 코드를 많이 바꿔야 된다고 생각하는 경우
  4. 개발이 완료된 후, 해당 코드를 누가 유지보수 할지 모르는 경우

 

즉, 불확실성이 높을 때 TDD를 하면 된다.

 

 

TDD의 궁극적인 목표는 작동하는 깔끔한 코드를 작성하는 것이다.

TDD의 개발 단계에는 리팩토링이 있는데, 이 리팩토링 과정을 거치면서 중복된 코드들은 제거되고, 복잡한 코드들은 깔끔하게 정리하게 된다. 또한 테스트를 처음 작성할 때에는 귀찮고 개발을 느리게 한다는 느낌을 받을 수 있지만, 장기적으로 보면 개발 비용을 아껴줄 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

TDD 개발 방법론의 프로그래밍 순서

 

  1. 실패하는 작은 단위 테스트를 작성한다.
  2. 테스트를 통과하기 위해 프로덕션 코드(실제 동작하는 코드)를 작성한다.
  3. 그 다음의 테스트 코드를 작성한다. 실패 테스트가 없을 경우에만 성공 테스트를 작성한다.
  4. 새로운 테스트를 통과하기 위해 프로덕션 코드를 추가 또는 수정한다.
  5. 1~4단계를 반복하여 실패/성공의 모든 테스트 케이스를 작성한다.
  6. 개발된 코드들에 대해 모든 중복을 제거하며 리팩토링한다.

 

즉, 단위 테스트 작성 → 단위 테스트 실행 → 운영 코드 작성 → 단위 테스트 실행 → 설계 개선(리팩토링) → 단위 테스트 → …  의 실행을 반복

 

 

 

 

 

 

 

 

 

 

 

 

일반적인 개발 vs TDD 개발

 

 

1) 일반적인 개발

보통의 개발 방식은 '요구사항 분석 -> 설계 -> 개발 -> 테스트 -> 배포'의 형태의 개발 주기를 갖는데, 이러한 방식은 소프트웨어 개발을 느리게 하는 잠재적 위험이 존재한다.

 

그 이유로는, 

  1. 소비자의 요구사항이 처음부터 명확하지 않을 수 있음
  2. 처음부터 완벽한 설계는 어려움
  3. 자체 버그 검출 능력 저하 또는 소스코드의 품질 저하
  4. 자체 테스트 비용이 증가할 수도 있음

 

 

어느 프로젝트든 초기 설계가 완벽하다고 할 수 없기 때문에, 외부 또는 내부 조건에 의해 코드 재설계가 필요할 것이다.

하지만 재설계로 인해 코드를 삽입, 수정, 삭제 하는 과정에서 불필요한 코드가 남거나 중복 처리 될 가능성이 크다.

 

이러한 코드들은 재사용이 어렵고 관리가 어려워져 유지보수를 어렵게 만든다.

 

또한, 작은 부분의 기능 수정에도 모든 부분을 테스트해야 하므로 전체적인 버그를 검출하기 어려워진다.

따라서 자체 버그 검출 능력이 저하되고, 그 결과 어디서 버그가 발생할지 모르기 때문에 잘못된 코드도 고치지 않으려 하는 현상이 나타나고 이는 소스코드의 품질 저하과 직결된다.

 

 

 

 

 

2) TDD 개발

 

TDD와 일반적인 개발 방식의 가장 큰 차이점은 테스트 코드를 작성한 뒤에 실제 코드를 작성한다는 점이다.

디자인(설계) 단계에서 프로그래밍 목적을 반드시 미리 정의하고, 무엇을 테스트할지 테스트 케이스를 작성해야 한다. 

 

  1. 테스트 코드를 작성하는 도중 발생하는 예외 사항(버그, 수정사항)들은 테스트 케이스에 추가하고 설계를 개선
  2. 이후 테스트가 통과된 코드만을 코드 개발 단계에서 실제 코드로 작성

 

이러한 반복적인 단계가 진행되면서 자연스럽게 코드의 버그가 줄어들고, 소스코드는 간결해진다.

또한, 테스트 케이스 작성으로 인해 자연스럽게 설계가 개선됨으로 재설계 시간이 절감된다.

 

 

 

 

 

 

 

 

 

 

 

TDD 개발 방식의 장점

 

1) 튼튼한 객체 지향적인 코드 생산

 

TDD는 코드의 재사용 보장을 명시하므로, 소프트웨어 개발 시 기능별 철저한 모듈화가 이뤄진다. 이는 종속성과 의존성이 낮은 모듈로 조합된 소프트웨어 개발을 가능하게 하며, 필요에 따라 모듈을 추가하거나 제거해도 소프트웨어 전체 구조에 영향을 미치지 않게 된다.

 

 

2) 재설계 시간의 단축

 

테스트 코드를 먼저 작성하기 때문에 개발자가 지금 무엇을 해야하는지 분명히 정의하고 개발을 시작하게 된다. 또한 테스트 시나리오를 작성하면서 다양한 예외사항에 대해 생각해볼 수 있다. 이는 개발 진행 중 소프트웨어의 전반적인 설계가 변경되는 일을 방지할 수 있다.

 

 

3) 디버깅 시간의 단축

 

TDD 개발 방식 프로세스 중 단위 테스트가 이루어지는 것에 대한 이점이기도 하다. 예를 들면 사용자의 데이터가 잘못 나온다면 DB의 문제인지, 비즈니스 레이어의 문제인지 UI의 문제인지 실제 모든 레이러들을 전부 디버깅 해야하지만, TDD의 경우 단위테스트가 이루어지기 때문에 특정 버그를 손쉽게 찾아낼 수 있다.

 

 

4) 테스트 문서 대체 가능


주로 SI 프로젝트 진행 과정에서는 어떤 요소들이 테스트 되었는지 테스트 정의서를 만든다. 이것은 단순 통합 테스트 문서에 지나지 않는다. 하지만 TDD를 하게 되면 테스팅을 자동화시킴과 동시에 보다 정확한 테스트 근거를 산출할 수 있다.

 

 

5) 추가 구현의 용이함


개발이 완료된 소프트웨어에 어떤 기능을 추가할 때 가장 우려되는 점은 해당 기능이 기존 코드에 어떤 영향을 미칠지 알지 못한다는 것이다. 하지만 TDD의 경우 자동화된 단위 테스트를 전제하므로 테스트 기간을 획기적으로 단축시킬 수 있고, 이는 곧 추가 구현의 용이함으로 이어진다.

 

 

 

TDD 개발 방식의 단점

 

가장 큰 단점은, 생산성의 저하이다.

 

처음부터 2개의 코드를 짜야하고, 중간에 테스트를 하면서 고쳐나가야 하기 때문에 생산성이 저하될 수 있다. TDD 방식의 개발 시간은 일반적인 개발 방식에 비해 대략 10~30% 정도로 늘어난다고 한다.

그리고 SI 프로젝트에서는 소프트웨어의 품질보다 납기일 준수가 훨씬 중요하기 때문에 TDD 방식을 잘 사용하지 않는다.

 

 

'CS > SW공학' 카테고리의 다른 글

객체지향 프로그래밍  (0) 2022.09.09

+ Recent posts