자바와 객체지향
포스트
취소

자바와 객체지향

객제지향 프로그래밍의 탄생 배경

객체지향 이전에는 프로그래밍을 개발 시 다음과 같은 문제점들이 있었다

  1. 데이터와 함수의 분리: 데이터와 함수가 각각 분리되어 있었기 때문에 체계적으로 조작하기 어려웠다
  2. 사람의 사고방식과 다른 코드: 사람이 이해하는 방식으로 코드를 작성하는 것이 아닌, 컴퓨터가 돌아가는 방식대로 코드를 작성하다보니 코드를 직관적으로 이해하기 어려웠다
  3. 코드의 복잡성 : 프로그램이 커지면 커질수록 복잡도가 크게 증가했다
  4. 유지보수와 확장성의 어려움: 코드가 복잡해지고 이해하기 어려워지다 보니 개발/기능 수정/유지보수 등 다양한 방면에서 비용과 시간이 많이 소요되었다

객체지향 프로그래밍(Object-Oriented Programming, OOP)이 발전하게 된 배경은 위와 같은 문제점들을 해결하기 위한 노력의 일환이다

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

OOP

  • 객체지향 프로그래밍 = Objected Oriented Programming = OOP
  • 여러가지 프로그래밍 패러다임 중 하나
  • 데이터와 이 데이터를 처리하는 함수를 하나의 ‘객체’로 묶는 방식을 중심으로 프로그램을 구성하도록 하는 패러다임
  • 절차지향 프로그래밍이 너무 어렵기 때문에 사람이 생각하는 방식과 유사하게 프로그래밍 할 수 있게 하고자 하는 생각에서 나온 개념
    • 단, 모든 프로그램은 최종적으로는 절차지향적으로 수행된다. 따라서 절차지향 프로그래밍이 아니라고 생각하면 안된다
  • 프로그램을 구성하는 기본 요소를 객체로 보고 하는 객체들이 서로 상호작용을 하여 프로그램이 돌아가게 만들려는 노력
  • 주요 특징으로 캡슐화, 상속, 다형성, 추상화가 있다.

절차지향_vs_객체지향

캡슐화(Encapsulation)

알려줄 필요가 없는 데이터는 숨겨라

encapsulation

  • 데이터 구조와 데이터를 다루는 방법들을 결합 시켜 묶는 것
    • 변수와 함수를 하나로 묶는 것을 뜻함
  • 내부의 데이터를 외부로부터 보호
    • 접근제어자 (private, default, protected, public)
  • 접근제어자를 통해 정보은닉을 활용 할 수도 있다
    • 정보은닉 : 객체 안에 있는 데이터를 외부로부터 보호
    • 보호된 정보에 대해서 getter, setter 메서드를 만들어 접근 가능하게 하곤 한다
  • 사용자가 클래스 속을 알 필요가 없음
    • 사용자가 함수 속을 알 필요가 없는 것과 마찬가지
    • 이 개념은 추강화로 이어진다
  • 함수를 분리할 때 적용했던 원칙을 클래스에서도 적용할 것
    • 중복된 코드가 있다면 private 메서드로 분리
  • 실용적인 용도 : 사용자측의 변경 없이 구현부를 변경할 수 있게 해준다

접근제어자 |접근 제어자|같은 클래스의 멤버|같은 패키지의 멤버|자식 클래스의 멤버| 그 외의 영역 | |—|—|—|—|—| |public|○|○|○| ○ | |protected|○|○|○| X | |default|○|○|X| X | |private|○|X|X| X | — getter/setter 함수를 통한 데이터 접근의 객관적인 장범

  1. 멤버 변수를 저장하지 않고 필요할 때마다 getter에서 계산 가능
  2. setter에서 추가적인 로직을 실행할 수 있음
  3. 상속을 통한 다형성 구현 가능

상속(Inheritance)

기존 클래스의 기능을 확장해서 재사용하라

inheritance

  • 이미 존재하는 객체를 기반으로 확장된 객체를 만드는 방법
    • 엄밀히 말하면 객체가 아니라 클래스
  • 거의 모든 사람이 OOP 핵심이라 여기는 특성
    • 특히, 재사용성이 궁극의 목적이라 신봉하던 시대에 특ㅎ
    • 현재에도 상속을 지원하지 않으면 OO 언어라고 안 보는게 보통
  • OOP의 또 다른 매우 중요한 특성인 다형성의 기반
  • 한 번에 모든 구체적인 사항을 만들지 말고 점진적으로 기능을 확장시켜 나갈 수 있게 해준다
    • 사람에게는 점진적 학습이 가장 효율적이다
  • 확장된 객체
    • 기존의 객체에 속한 데이터와 동작을 모두 물려받음 (유전)
    • 여기에 다른 데이터나 동작을 추가할 수 있음 (진화)
    • 물론 새클래스를 상속해서 또 다른 새 클래스를 만들 수 있음
  • 실용적인 용도 : 코드 중복을 막음
    • 여러 객체에는 공통되는 데이터와 동작을 부모 객체로 만듦
    • 여러 객체는 각각 그 부모 객체를 상속 받음
    • 그 후 자기에게만 필요한 데이터나 동작을 추가

super 키워드

  • super는 현 개체의 부모 부분을 가리킴
  • super() 라고 코드를 작성하면 부모의 생성자를 호출
  • 멤버 변수나 메서드를 호출할 때도 가능

다형성(Polymorphism)

동일한 인터페이스에 대해 여러 형태의 구현을 가능하게 하라

polymorphism

  • 많은 사람들이 OOP의 핵심이라고 여기는 특징
  • 같은 지시를 내렸는데 다른 종류의 객체가 동작을 달리 하는 것
    • 같은 지시 : 동일한 함수 시그내처 호출
    • 달리 동작 : 객체의 종류에 따라 실제로 실행되는 함수 구현 코드가 다름
    • 절차적 언어에서 이런 일을 하려면 if문을 사용해야 했다
  • 어떤 함수 구현이 실행될지는 실행 중에 결정된다
    • 이를 늦은 바인딩이라고 함 (late binding)
    • 일반적인 함수 호출은 이른 바인딩 (컴파일 중에 결정된다)
  • 다형성의 혜택을 받으려면 상속 관계가 필요
    • 상속 없이는 다형성이 불가능하다
    • 부모 객체에서 함수 시그내처를 선언
    • 자식 객체에서 그 함수를 다르게 구현 (overriding)
  • 실용적인 용도
    • 다른 종류의 객체를 편하게 저장 및 처리 가능

오버라이딩(Overriding)

  • 하위 클래스가 이미 상위 클래스에 정의된 메서드의 자체 구현을 제공할 수 있도록 하는 기능
  • 오버라이딩 된 메서드를 호출 시 부모 클래스 구현 대신 자식 클래스 구현이 실행된다
  • 하위 클래스 메서드에서 @Override 어노테이션을 명시적으로 사용하여 오버라이딩 메서드임을 명시할 수 있다

다형성의 장점

  • 각 자료형의 코드가 클래스 안에 들어가서 캡슐화
  • 유지 보수성 높아짐
  • 새로운 클래스를 추가할 때 클래스 코드만 추가하면 됨
  • 클라이언트가 작성할 코드가 줄어듦 polymorphism_code 다형성을 통해 조건문의 사용 없이 코드 구현이 가능하다. 그렇기 때문에 조건문을 사용하면 OO가 아니라고 주장하는 사람이 있다. (조건문 대신 모든 것을 다형성으로 바꿔야 한다고 한다) 하지만 다형성이 조건문을 모두 대체하기는 어렵고, 설사 가능하더라고 그렇게 해야할 이유가 없다.

추상화(Abstraction)

시스템의 설계와 구현을 명확하게 구분하라

abstraction

  • OOP에서 추상화란 어떤 구체적인 것에 직접 손대지 않겠다는 것을 의미
  • 객체 속에 있는 실제 데이터나 함수 구현 방법에 종속되지 않겠다는 뜻
  • 데이터 추상화
    • 객체 사용 시 그 안에 정확히 어떤 데이터가 있는지 알 필요 없음
    • 객체 안에 있는 데이터에 접근 불가
      • 그 대신 객체의 함수를 통해 접근
    • 즉, 캡슐화는 추상화를 이루는 방법 중 하나
  • 실용적인 용도 : 설계와 구현의 분리

추상 자료형쪽 관점

  • 사용자는 클래스를 자료형으로 사용할 수 있음
  • 그 클래스 안에 들어있는 멤버 변수가 정확히 뭔지 몰라도 됨

절차적 데이터 추상화쪽 관점

  • 데이터를 직접 조작하는 대신 메서드를 호출

추상화의 단점

  • 동작 없이 데이터만 있는 클래스는 쓸데없는 코드만 늘어남
  • 어떻게 추상화를 해야할 지 뚜렷한 객관적 기준이 없음

예시로 이해하기

Person.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 추상화: 일반 개념을 나타내는 추상 클래스 정의
// 캡슐화: 개인 필드와 공용 메서드를 사용하여 데이터에 대한 액세스 제어
abstract class Person {
    // 캡슐화: `protected` 접근제어라를 사용하여 필드에 대한 액세스 제어
    protected String name;
    protected int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    // 다형성/추상화: 하위 클래스에 의해 구현되는 추상 메서드
    public abstract void speak();
}

Korean.java

1
2
3
4
5
6
7
8
9
10
11
12
// 상속: Person 클래스를 확장하여 보다 구체적인 클래스 생성
class Korean extends Person {
    public Korean(String name, int age) {
        super(name, age);
    }

    // 상속: Korean 클래스에 대한 speak() 메소드 오버라이딩
    @Override
    public void speak() {
        System.out.println("안녕하세요! 저는 한국인입니다.");
    }
}

American.java

1
2
3
4
5
6
7
8
9
10
11
12
// 상속: Person 클래스를 확장하여 보다 구체적인 클래스 생성
class American extends Person {
    public American(String name, int age) {
        super(name, age);
    }

    // 상속: American 클래스에 대한 speak() 메서드 재정의
    @Override
    public void speak() {
        System.out.println("Hello! I am an American.");
    }
}

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
    public static void main(String[] args) {
        Person koreanPerson = new Korean("홍길동", 30);
        Person americanPerson = new American("John til", 25);
        
        List<Person> people = new ArrayList<>();

        people.add(koreanPerson);
        people.add(americanPerson);

        // 같은 메서드를 호출했지만, 객체마다 다른 결과를 출력 -> 다형성
        for (Person person : people) {
            System.out.println(person.getName() + " is " + person.getAge() + " years old.");
            person.speak();
        }
    }
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

자바의 클래스와 객체

컴파일 vs 인터프리트 vs 하이브리드

Comments powered by Disqus.