객체지향 프로그래밍과 설계(3)

업데이트:

POCU의 개체지향 프로그래밍 및 설계 강의를 듣고 정리한 내용입니다.

public

  • 멤버 변수와 멤버 함수 선언 시 앞에 붙는 접근 제어자
  • 외부(다른 패키지)에서 클래스 내부에 담긴 상태/동작에 접근하는 것을 허용

객체 생성과 메모리

  • 자바는 기본 자료형만 stack 메모리에 생성 가능
  • 참조 자료형은 heap 메모리에 저장됨(동적 할당)
// C
human_t* adam = (human_t*)malloc(sizeof(human_t));
// java
Human adam = new Human();

포인터 vs 참조형

Human adam;

  • 자료형 : 사실상 포인터
  • 포인터는 메모리 주소를 저장하는 변수
  • adam역시 힙에 위치한 Human 객체의 주소를 담는 변수
  • 자바는 기본 자료형 제외하면 모두 포인터 형
    • 포인터 연산은 불가능(주소값 변경 불가)
  • int : 기본 자료형
  • Integer : 참조 자료형

C는 객체를 포인터 혹은 값으로 둘 다 전달 가능, 자바는 포인터로만 전달 가능

public void increaseAge(Human player, int age) {
  player.age += age;
  age = 0;
}

int age = 10;
Human adam = new Human(); // adam.age = 20
increaseAge(adam, age) // adam.age = 30, age = 10
// age는 값형, adam은 참조형

자바는 기본적으로 call by value, 값형 데이터에 대해 swap이 불가능하다.

멤버 변수의 초기값

  • 자바는 비트 패턴이 0인 값으로 초기화해 줌
    • int : 0
    • float : 0.0
    • 참조형 : null
  • 0이 아닌 값으로 초기화하고 싶으면 선언문에 대입 가능
    public class Human {
      publicc int age = 20;
    }
    

. 연산자

  • 개체의 멤버에 접근할 때 연산자를 사용
  • 포인터 역참조용 *연산자가 없음
    • 주소를 읽을 방법이 없음
    • 따라서 언제나 그 주소에 저장된 값을 읽어 옴

garbage collection

  • jvm에 내장된 가비지 컬렉터가 알아서 해 줌
  • 가비지 : 더 이상 사용되지 않는 객체(힙 메모리에서)
    • 더이상 해당 객체를 참조하는 변수가 없을 때, 더 이상 사용되지 않는다고 판단
  • 프로그래머가 직접 메모리를 해제하지 않는다.

한계

  1. 가비지 컬렉터가 메모리를 수집하는 시점을 알 수 없음
  2. 모든 객체의 사용 여부를 판단하는 게 그리 빠른 연산이 아님
  • 자원이 한정적인 시스템에는 적합하지 않음
  • 자동 메모리 관리 하에서 발생하는 메모리 누수도 존재

생성자

  • 생성 시 올바른 값으로 초기화하기 위함
  • 개체 생성 시 자동으로 호출되는 특수한 함수
  • 반환형 : 없음(void 아님!)
  • 함수명 : 클래스명과 동일
public class Human {
  public String name;
  public int age;
  public Sex sex;

  public Human(String name, int age, Sex sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
  }
}

생성자 오버로딩

코드 중복을 피하는 법

  • 매개변수 수가 적은 생성자에서 매개변수 수가 많은 생성자를 호출!
  • this()를 이용하면 다른 생성자를 호출할 수 있음
    public Human(String name, Sex sex) {
    this(name, sex == Sex.MALE ? 1 : 5, sex);
    }
    

기본 생성자

default constructor

  • 생성자를 하나도 안 만들 경우 자동으로 생기는 생성자
  • 컴파일러가 알아서 매개변수 없는 생성자를 만들어 줌
  • 기본 생성자의 함수 바디는 비어 있음

생성자의 필요성

생성자는 객체를 만들어주는 계약이다

  • 함수 시그니처와 마찬가지
  • 함수는 블랙박스
  • 호출자와 함수의 분명한 책임 분리
    • 호출자는 함수 내부의 동작을 알 필요 없음
    • 책임의 분리는 함수 시그니처, 선조건, 후조건 등으로 확실히 정의

외부에서 클래스 내부의 데이터를 알 필요가 없음. 이것이 데이터 추상화이고 캡슐화의 일부.

이 개념은 생성자뿐만 아닌 모든 메서드에 적용

접근 제어자

어떤 외부자들이 객체 속에 접근할 수 있는지 정의

  1. public : 누구나 접근 가능
  2. protected : 자식들만 접근 가능
  3. 생략 : 같은 패키지에 속한 클래스들만 접근 가능
  4. private : 외부 접근 금지

일반적인 접근 제어자

  • 멤버 변수 : private 또는 protected
  • 메서드 : public
  • 멤버 변수 접근은 메서드를 통해서만!
    • 캡슐화, 추상화

private 메서드의 용도?

  • 코드 중복을 막기 위함

private과 생성자

  • 생성자도 멤버 함수이므로 똑같은 규칙이 적용
  • new를 못하니 적어도 생성자 하나는 public인 것이 보통

주의 : 내부란 클래스 내부를 의미한다

  • 클래스 내부 != 객체 내부
  • 같은 클래스에 속한 객체끼리는 private 멤버에 접근 가능
public class Human {
  private int age;
  ...
  public void punch(Human enemy) {
    enemy.age -= 1; // OK
    this.age += 2; // OK
  }
}

패키지 접근 제어자 용도

  1. public 대신 패키지 접근 제어자를 사용할 수 있을 때
    • 특정 패키지 안에서만 사용되는 클래스
  2. public이 아닌 내포 클래스를 최상위 클래스로 바꿀 때
    • 내포 클래스는 가독성 문제가 생길 수 있음
    • 따라서 별도의 클래스로 분리시키는 것이 요즘 트렌드
    • 이때 접근 권한을 패키지 내로 제한하는 것이 public보다 나음

getter / setter

남이 읽을 수 있게 해주려면 getter

public class Human {
  private String name;
  ...
  public String getName() {
    return this.name;
  }
  ...
}

외부에서 변경 가능한 정보는 setter

public class Human {
  private String name;
  ...
  public void setName(String name) {
    this.name = name;
  }
}

장점

  1. 멤버 변수를 저장하지 않고 필요할 때마다 getter에서 계산 가능
    • ex) 질량과 중력 멤버 변수로부터 무게를 계산
  2. setter에서 추가적인 로직을 실행할 수 있음
    • ex) 음수의 나이가 인자로 들어올 경우 무시
  3. 상속을 통한 다형성 구현 가능

best practice

  1. 멤버 변수는 private
  2. 새로운 객체는 유효하도록
    • 생성자를 통해 강제할 수 있음
    • 매개변수가 틀릴 경우 / 바뀌는 경우 컴파일 에러 나게 유도
  3. getter는 자유롭게 추가
    • 주의 : 특정 객체의 레퍼런스를 반환할 때는 문제될 수 있다.
  4. setter는 고민 후 추가
    • 이상적인 객체의 상태 수정법
      • 그 객체의 사용자가 어떤 동작을 지시
      • 그 동작의 결과로 객체 안에 있는 특정 상태가 바뀜
      • 즉, 객체 스스로 상태를 변경
  public class Classroom {
    private int[] scores;
    private float mean;
    ...
    public boolean setScore(int index, int score) {
      scores[index] = score;
      updateMean();

      return True
    }

    private void updateMean() {
      this.mean = 계산 결과;
    }
  }

정리 : 캡슐화

  1. 객체의 데이터(멤버 변수)와 동작(메서드)을 하나로 묶음
  2. 내부의 데이터를 외부로부터 보호
  • 사용자가 클래스 내부를 알 필요가 없다
  • 함수를 분리할 때 적용했던 원칙을 클래스에도 적용할 것!
    • 중복된 코드가 있다면 private 메서드로

정리 : 추상화

  • 추상 자료형 측 관점
    • 사용자는 클래스를 자료형으로 사용할 수 있음
  • 절차적 데이터 추상화 측 관점
    • 데이터를 직접 조작하는 대신 메서드를 호출

어떤 구체적인 것에 직접 손대지 않겠다.

단점

  • 동작 없이 데이터만 있는 클래스는 쓸데없는 코드만 늘어남
    • 웹 프로그래밍에서의 DTO
    • 그냥 public 데이터를 쓰기도 함
  • 어떻게 추상화를 해야 하는지 객관적 기준이 없음
    • 다형성, 상속, 인터페이스에서 나오는 추상화에서 특히 문제

Reference

POCU 강의

태그: ,

카테고리:

업데이트:

댓글남기기