[OOP] 객체지향 프로그래밍 (Object-Oriented Programming)

Happy Jiwon·2023년 2월 8일
1

Study

목록 보기
1/5

시작하기 전

'객체지향 프로그래밍이 무엇인지 설명해보세요.'
가장 기본적인 질문이지만, 답변에 따라 꼬리에 꼬리를 무는 질문의 시작점이라고 할 수 있다.

객체지향 프로그래밍은 컴퓨터 프로그래밍 패러다임 중 하나로 프로그래밍에서 필요한 데이터를 추상화 시켜 상태와 행위를 가진 객체로 만들고, 객체들간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법이다.

단순히 'OOP란 ~~다.'라고 외우지 말고 객체지향 프로그래밍의 등장배경을 알아보면서 이해하는 시간을 가져보자.


프로그래밍 패러다임

순차적 프로그래밍(Sequential Programming) - 비구조적 프로그래밍

순차적 프로그래밍은 말 그대로 순차적으로 흘러가는 프로그래밍 구조이다.
정의한 기능의 흐름에 따라 순서대로 동작을 추가하며 프로그램을 완성하는 방식이다.

간단한 프로그램의 경우, 코드의 흐름이 눈으로 보이기 때문에 매우 직관적이다.

그러나 조금이라도 프로그램의 규모가 커진다고 가정해보자.
예를들어, A → B → C 라는 동작을 구현하다가, C 에서 A 로 돌아가야할 상황이라면 goto 를 활용해야 한다.

goto문을 계속 활용하여 프로그램이 방대해질수록 코딩에 집중할 수 없고 흐름만 신경쓰다 시간을 다 보낼것이다.
그렇게 되면 동작이 직관적이지 못하게 되고, 장점이 사라지는 셈이다.

이로 인해 등장한 프로그래밍이 절차적/구조적 프로그래밍이다.

절차적 프로그래밍(Procedural Programming) - 구조적 프로그래밍

절차적이라고 하면 단순히 순차적인 명령 수행일 것 같지만 그렇지 않다.
절차적 프로그래밍에서 '절차'는 순차적인 명령 수행이 아니라 루틴, 서브루틴, 메소드, 함수 등을 이용한 프로그래밍 패러다임을 뜻하는 명령형 프로그래밍의 일종이다.

Procedural Programming에서 Procedural를 '절차적'으로 번역해버려서 마치 절차적으로 실행하는 것이 중점이 되는 것처럼 보이지만 Procedural은 '프로시저'의 의미이다.

💡 루틴, 서브루틴, 메소드, 함수를 통틀어 프로시저라고 한다.

프로시저를 사용함으로써 함수를 통해 코드의 재활용성이 높아지고, 함수의 호출을 통해 프로그램의 흐름을 쉽게 볼 수 있는 장점이 있다.

그러나, 기본적으로 프로시저를 호출하는 것은 그냥 코드를 쓰는 것보다 시간이 매우 많이 소모된다는 단점이 있다.
코드가 길어지면 가독성이 떨어지고, 기능 구현에 많은 불편함을 가져온다.

이를 보완하기 위해 나온것이 객체지향 프로그래밍이다.

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

Java, C++, C#, Python, PHP, Ruby, Object-C 등 다양한 언어에서 객체지향 프로그래밍을 지원한다.

객체지향 프로그래밍은 절자치향과는 다르게 관련성 있는 객체들의 집합이라는 관점으로 접근하는 소프트웨어 디자인으로 볼 수 있다.


OOP 기본 구성 요소

  • 클래스(Class)
    같은 종류의 집단에 속하는 속성(attribute)과 행위(behavior)를 정의한 것으로 객체지향 프로그램의 기본적인 사용자 정의 데이터형(user defined data type)이라고 할 수 있다.
  • 객체(Object)
    클래스의 인스턴스(실제로 메모리상에 할당된 것)이다.
    객체는 자신 고유의 속성을 가지며 클래스에서 정의한 행위를 수행할 수 있다. 객체의 행위는 클래스에 정의된 행위에 대한 정의를 공유함으로써 메모리를 경제적으로 사용한다.
  • 메서드(Method), 메시지(Message)
    클래스로부터 생성된 객체를 사용하는 방법으로서 객체에 명령을 내리는 메시지라 할 수 있다.
    메서드는 한 객체의 서브루틴(subroutine) 형태로 객체의 속성을 조작하는 데 사용된다. 또 객체 간의 통신은 메시지를 통해 이루어진다.

OOP 4가지 특징

객체지향 프로그래밍에는 캡슐화, 추상화, 상속, 다형성의 4가지 특징이 있다.
간단히 설명하겠다.

1) 캡슐화(Encapsulation)

정보은닉이라는 목적을 달성하기 위해 접근 제한자를 이용하게 된다.

캡슐화에는 private을 이용해 변수 및 메서드들에 접근하지 못하도록 제한하고 외부에서 접근 가능한 특정 메서드들을 통해 간접적으로 접근할 수 있는 방법과 getter와 setter를 통해 데이터를 저장하거나 사용하는 방법이 있다.

캡슐화는 응집도와 독립성을 높임으로써 모듈화를 지향한다.

2) 추상화(Abstraction)

추상화의 일반적인 의미는 여러 사물/개념에서 공통적인 특성(변수, 메소드)을 묶어 표현한 것이다.

예를 들어 남자, 여자 등의 사람은 운동하기/일하기 등의 행동을 하기 때문에 사람이라는 추상 클래스를 만들 수 있습니다.

추상 메서드란, 함수 선언만 되어있고 구현부가 없는 아래와 같은 메서드를 추상 메서드라고 한다.

public abstract class 클래스명();

🖐🏻 추상 클래스의 일부 다형성 보장
추상 클래스는 다형성을 보장하기 위해 나타난 개념이다.
자식 클래스에서 반드시 재정의가 되어야 된다는 점에서 다형성이 보장된다.

부모 클래스에서 추상 메서드를 선언하면, 자식 클래스는 부모가 가진 추상 메소드들을 자식 클래스에서 반드시 재정의(오버라이딩)해야 한다.
즉, 부모가 자식에게 명령을 내렸을 때 자식 클래스가 반드시 동작되도록 재정의한다는 점에서 다형성이 보장된다.

3) 상속화(Inheritance)

부모 클래스에 정의된 변수 및 메서드 들을 자식 클래스에서 상속받아 사용할 수 있게 되는 것이다.
상속화를 사용하는 이유는 코드의 재사용성을 극대화시키기 위해서이다.

상속화를 사용한다면 부모 클래스에서 정의한 변수 및 메서드들을 여러 자식 클래스에서도 사용할 수 있기 때문에 재사용성이 증가하게 되고 더불어 자식 클래스에서는 동일한 코드들을 다시 작성할 필요가 없기 때문에 코드가 간결해지게 된다.

4) 다형성(Polymorphism)

다양한 형태로 표현이 가능한 구조를 말한다.
다른 표현으로는 자바에서 하나의 인스턴스를 생성할 때 한 클래스에 대해서만 생성하는 것이 아니라 여러 클래스에 대해 인스턴스를 생성할 수 있다는 것을 말한다.
쉽게 말해서, 상위클래스로 인스턴스를 만들고 하위클래스로 객체를 만들 수 있다.

예제를 통해 자세히 알아보겠다.

추상화 abstract을 사용해 Animal 클래스(추상 클래스)를 생성한 후
bark()라는 메서드를 선언한다.
public abstract class Animal {
     public abstract void bark();
}
Animal 클래스를 상속받는 클래스 Bird, Dog, Cat을 생성해준다.
public class Bird extends Animal {
 ...
}

public class Cat extends Animal {
 ...
}

public class Dog extends Animal {
 ...
}

이때 Animal 클래스를 abstract으로 하였기 때문에 bark() 메소드는 반드시 오버라이딩 해주어야 한다.
오버라이딩을 하지않으면 에러메시지가 나타난다.

각 클래스에 맞게 메서드를 오버라이딩해준다.
public class Bird extends Animal {
	@Override
	public void bark() {
	System.out.print("짹짹");
	}
}

public class Cat extends Animal {
	@Override
	public void bark() {
	System.out.print("야옹");
	}
}

public class Dog extends Animal {
	@Override
	public void bark() {
	System.out.print("멍멍");
	}
}
하나의 상위 클래스로 여러 클래스에 대해 인스턴스를 생성할 수 있다.
public void polymorphismTest() {
	// 1
	Animal animal = new Dog();
    
    // 2
	Animal animal = new Cat();
    
    // 3
	Animal animal = new Bird();


	// 1번으로 객체를 생성했을 때 인스턴스의 실제 타입을 알아보고싶을 경우
    animal instanceof Dog => 참이면 true, 틀리면 false로 나타난다.
    
}
instanceof 연산자
참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용한다.

위 예시와 같이
검사하고자 하는 참조변수 instanceof 타입(클래스명) => 연산결과가 맞으면 true, 틀리면 false 를 반환


🖐🏻 다형성 사용 이유

상속과 메서드 재정의를 활용하여 확장성있는 프로그램을 만들 수 있다.
그렇지 않은 경우에 프로그램 규모가 커지면 커질수록 코드 유지보수가 힘들기 때문이다.

상위 클래스에서 공통적인 부분을 제공하고 하위 클래스에서 각 클래스에 맞는 기능을 구현할 수 있다.

위 예시와 같이 abstract을 사용한 이유는 메서드 오버라이딩을 반드시 구현해주어야 했기 때문이다.
만약, abstract을 사용하지 않고 메서드를 정의하다 오버라이딩을 하지 않는 경우가 발생할 수 있는데, 이에 대한 에러를 빠르게 찾기 위함도 있다.

상속과 메서드 재정의를 활용하여 확장성 있는 프로그램을 만들 수 있음.
그렇지 않는 경우 많은 if-else if 문이 구현되고 코드 유지 보수가 힘듬.
상위 클래스에서 공통적인 부분을 제공하고 하위 클래스에서 각 클래스에 맞는 기능 구현
여러 클래스르 하나의 타입(상위 클래스)으로 핸들링 할 수 있음.
상위 클래스를 사용하여, 기능이 다른 하위 클래스를 추가하거나 및 확장이 유용.


OOP의 장점

다음과 같이 객체지향 프로그래밍의 특징에 대해서 알아보았는데, 객체지향 프로그래밍의 가장 큰 특징은 하나의 문제 해결을 위해 클래스를 이용하여 연관 있는 처리부분(함수)데이터 부분(변수)를 하나로 묶어 객체(인스턴스)를 생성해 사용한다는 점이다.

객체지향 프로그래밍에서는 프로젝트를 독립적인 객체 단위로 분리해서 작업할 수 있기 때문에 협업이 중요한 규모가 큰 프로젝트를 진행할 수 있으며 유지보수가 쉽고 확장성 측면에서도 유리하다.


그래서 OOP가 뭐야?

객체지향에 대해서 "이거야!!"라고 정확하게 정의할 수 없었다. 구글링 해보면 "객체지향 프로그래밍이란 모든 데이터를 객체로써 정의하고 객체간의 메시지를 주고받는 통신으로 프로그램을 수행하는 방식"이라는 말은 객체지향적인 프로그램을 구현하는데 전혀 도움이 되지 않았다.

'코드의 재사용을 위함이다', '모듈화를 위함이다' 등 이런 것들은 모든 프로그래밍 패러다임이 대부분 지향하는 공통 목표이다.

좋은 프로그램은 '응집도가 높고, 결합도가 낮다' 는 모든 프로그래밍 패러다임의 공통 목표라고 볼 수 있다.
OOP는 이 목표를 달성하기 위한 하나의 방법론이라고 할 수 있다. 방법론을 사용하기 위해서는 '어떻게?'가 필요한데 우리는 '어떻게?'에 대해 위에서 이미 공부하였다.

객체지향 프로그래밍이란 캡슐화, 추상화, 상속, 다형성 을 이용하여 코드 재사용을 증가시키고, 유지보수를 감소시키는 장점을 얻기 위해 객체들을 연결시켜 프로그래밍하는 것이다.

이론을 빠삭하게 알고있더라도 큰 프로그래밍을 구현하는 것은 어려울 것이다. 캡슐화, 다형성, 상속 등을 효율적으로 사용하는 원칙과 방법에 대해 기술한 SOLID 원칙을 공부해야 한다.

다음은 SOLID 원칙에 대해 포스팅 해 볼 예정이다.

profile
공부가 조은 안드로이드 개발자

0개의 댓글