CS

객체지향 / 절차지향 프로그래밍

김디니 2022. 12. 27. 10:01

절차지향 프로그래밍, Procedural Programming

절차지향 프로그래밍은 말 그대로 절차대로 프로그래밍 하는 방법이다. 

프로그램 전체가 유기적으로 연결되어있다. 

 

 

장점

  • 코드의 가독성이 높음
  • 컴퓨터의 처리 구조와 비슷하여 실행 속도가 빠름

단점

  • 코드 순서가 민감하게 연결되어 있어 유지보수와 분석이 어려움

 

절차지향 프로그래밍이 나오기 전에 순차적, 비구조적 프로그래밍이 존재하였다. 

순차적, 비구조적 프로그래밍은 말 그대로 순차적으로 프로그래밍 하는 것이다.

추후에 필요한 코드가 있다면 순서대로 추가하는 방식이다. 

이는 규모가 커질 때 문제가 생긴다. 

 

비구조적 프로그래밍은 goto문을 사용했다고 한다. goto문은 위에서 사용했던 코드를 불러올 때 (이동하기 위해) 사용된다. 

규모가 점점 커지면 goto문을 무분별하게 사용하게 되고 코드가 베베 꼬이게 된다고 한다. 

이렇게 된다면 디버깅이 매우 힘들어진다. 

 

이러한 문제점을 해결하기 위해 절차적, 구조적 프로그래밍(Procedural Programming)이 탄생하게 된다.

반복 가능성이 있는 코드들을 재사용하기 위해 함수(프로시저)로 만들어 사용한다.

여기서 절차는 함수(프로시저, Procedure)를 뜻하고, 구조는 모듈을 뜻한다. 

 

프로시저란? 

Return이 따로 존재하지 않는 함수이다. 

테이블에서 데이터를 추출해 조작하고 그 결과를 다른 테이블에 다시 저장하거나 갱신할 때 주로 프로시저를 사용한다.

 

하지만 많은 데이터를 다룰 때 구분하기 힘들고 비효율적으로 코딩할 확률이 높다. 

이러한 단점을 보완하기 위해 객체지향 프로그래밍이 탄생하게 된다.

 

 

객체지향 프로그래밍, Object Oriented Programming (OOP)

전에 사용했던 프로그래밍의 단점을 보완하고자

특정한 개념의 함수와 자료형을 함께 묶어서 관리가 가능해지도록 만든 것이다. 

 

객체 내부에 자료형(필드)와 함수(메소드)가 같이 존재한다.

물리적, 논리적 요소를 객체로 만들어 객체 간의 독립성을 유지하도록 하고 유지보수에 도움을 준다.

또한, 중복코드의 양이 줄어들게 도니다. 

 

특징

1. 추상화 (Abstraction)

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

 

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

ex) 아우디, BMW, 벤츠는 모두 '자동차'라는 공통점이 있다.
자동차라는 추상화 집합을 만들어두고, 자동차들이 가진 공통적인 특징들을 만들어 활용한다.

 

2. 캡슐화(Encapsulation)

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

 

한 곳에서 변화가 일어나도 다른 곳에 미치는 영향을 최소화 시키기 위함이다.

결합도가 낮도록 만들어야 하는 이유는 객체들 간의 의존도가 최대한 낮게 만들어야 하기 때문이다.

(객체들 간의 의존도가 높아지면 굳이 객체 지향으로 설계하는 의미가 없어진다.

객체지향은 특정 기능의 코드가 담긴 캡슐을 하나씩 꺼내서 이곳 저곳에 쓰기 위한 것인데 그 캡슐들을 꺼내봤더니 다른 캡슐들이 줄줄이 따라서 끌려오면 필요한 캡슐만 쓸 수 없으니까!)

 

응집도를 높이고 결합도를 줄여야 요구사항 변경에 대처하는 좋은 설계 방법이라고 한다.

높은 응집도와 낮은 결합도를 갖기 위해서는 정보 은닉을 활용한다.

외부에서 접근할 필요가 없는 것들은 접근하지 못하도록 제한을 두는 것이다.

 

3. 상속(Inheritance)

▶️ 일반화 관계(Generalization)라고도 하며, 여러 개체들이 지닌 공통된 특성을 묶어 하나의 개념이나 법칙으로 성립하는 과정

 

일반화(상속)은 또 다른 캡슐화로 볼 수 있다. 

자식 클래스를 외부로부터 은닉하는 캡슐화의 일종이다. 

 

단점

  • 상위 클래스(부모 클래스)의 변경이 어려워진다.
    • 의존하는 자식 클래스가 많을 때, 부모 클래스의 변경이 필요하다면 의존하는 자식 클래스들이 영향을 받게 된다.
  • 불필요한 클래스가 증가할 수 있다.
    • 유사기능 확장시 필요 이상의 불필요한 클래스를 만들어야 하는 상황이 발생할 수 있다.
  • 상속이 잘못 사용될 수 있다.
    • 같은 종류가 아닌 클래스의 구현을 재사용하기 위해 상속을 받게 되면 문제가 발생할 수 있다. 상속 받는 클래스가 부모 클래스와 IS-A 관계가 아닐 때 이에 해당한다.

단점을 보완하기 위해서 객체 조립(Composition)을 사용한다.

객체조립은 필드에서 다른 객체를 참조하는 방식으로 구현된다.

상속에 비해 비교적 런타임 구조가 복잡해지고 구현이 어렵다.

하지만 변경 시 유연함을 확보하는데 장점이 매우 크다.

 

4. 다형성(Polymorphism)

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

 

다형성은 객체지향의 핵심이다.

상속과 함께 활용할 때 큰 힘을 발휘한다. 이와 같은 구현은 코드를 간결하게 해주고, 유연함을 갖추게 해준다.

 

즉, 부모 클래스의 메소드를 자식 클래스가 오버라이딩해서 자신의 역할에 맞게 활용하는 것이 다형성이다.

다형성을 사용하면 구체적으로 현재 어떤 클래스 객체가 참조되는 지는 무관하게 프로그래밍할 수 있다.

 

 

객체 지향 설계 과정

  • 제공해야 할 기능을 찾고 세분화한다.
    • 기능을 알맞은 객체에 할당한다.
  • 필요한 데이터를 객체에 추가한다.
  • 그 데이터를 이용하는 기능을 넣는다.
  • 기능은 최대한 캡슐화하여 구현한다.
  • 객체 간에 어떻게 메소드 요청을 주고받을 지 결정한다.

 

객체 지향 설계 원칙

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

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

 

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

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

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

 

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

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

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

 

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

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

 각 클라이언트가 사용하지 않는 인터페이스에 변경이 발생하더라도 영향을 받지 않도록 만들어야 한다.

 

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

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

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

즉, 저수준 모듈이 변경돼도 고수준 모듈은 변경할 필요가 없는 것이다.