개체 지향 프로그래밍을 배우는데 있어 알아두면 좋은 내용들을 정리해본다.
접근제어자와 캡슐화
- 멤버 변수 직접 접근 못하도록 해야함
- 메서드를 통해서만 접근 가능
- 내부에 어떤 변수가 있는지는 모르겠고, 소통은 메서드로만 해!
- 일단 이게 기본
public class Human {
private String name;
private int age;
public Human(String name, int age) {
this.name = name;
this.age = Math.max(age, 0);
}
}
getter, setter
- 누군가가 내부 정보를 궁금해하거나 변경하고 싶을 때 열어주는 메서드
- 필요한 시점에 필요한 것만 열겠다는 발상
- 읽게 해주려면 getter, 쓰게 해주려면 setter
- 요즘 언어에서는 immutable property와 같은 것이 있어 방식이 바뀌고 있긴 하다.
- get, set시 다른 작업을 해야할 수도 있기 때문에 이걸 일단 기본으로 함
- 특수한 경우(성능 등)에 public으로 멤버 변수를 열어줌
public class Human {
private String name;
private int age;
public Human(String name, int age) {
this.name = name;
this.age = Math.max(age, 0);
}
}
함수를 통한 데이터 접근의 장점
- 멤버 변수를 저장하지 않고 필요할 때마다 getter에서 "계산" 가능
- setter에서 추가적인 로직을 실행할 수 있음
- 상속을 통한 다형성 구현 가능
Best Practice
- 멤버 변수는 private
- 새 개체는 생성함과 동시에 유효해야 한다.
- 개체는 살아있는 동안 언제나 유효한 상태여야 이상적임
- 실수도 방지할 수 있음
- 생성자를 통해 강제할 수 있음
- getter는 자유롭게 추가
- 알 필요 없는 정보는 안보여주는게 정석
- 하지만 보여줘도 큰 문제는 없음
- 그래서 자유롭게 열어둚
- 하지만 개체의 Reference를 넘기는 경우 주의해야 함
- C++의 경우 "읽기 전용" 레퍼런스를 반환할 수 있어 이런 문제에서 자유로움
- setter는 고민 후 추가
- 이상적인 개체의 상태 수정법
- 개체의 사용자가 동작을 지시
- 동작의 결과로 개체 안에 있는 상태를 변경
- 데이터를 직접 바꾸는 녀석이기 때문에 가능한 피하자.
- 무조건 추가하는 경우도 있긴 함.. (아무 생각 없이)
- 만약 SDK인 경우에는 미래를 대비하여 추가하는 경우도 있음
캡슐화
개체의 데이터와 동작을 하나로 묶음 -> Class
내부의 데이터를 외부로부터 보호 -> getter, setter, 접근제어자
- 사용자는 클래스 속을 알 필요 없음
- 함수를 분리할 때 적용했던 원칙을 클래스에도 적용하면 됨
추상화
구체적인 것에 손대지 않겠다.
- 추상 자료형(Abstract data type)쪽 관점
- 클래스를 "사용"하는 사람의 관점
- 캡슐화(데이터 묶음)에 좀 더 방점이 가있는 관점
- 사용자는 클래스를 "자료형"으로 사용할 수 있음
- 그 안에 들어간 멤버 변수가 정확히 뭔지 몰라도 됨
- 클래스로부터 개체 생성 가능
- 절차적 데이터 추상화(Procedural data abstraction)쪽 관점
- 클래스를 "제작"하는 사람의 관점
- 메서드에 좀 더 방점이 가있는 관점
- 데이터를 직접 조작하는 대신 "메서드"를 호출
- 동작적 개체(behavior objects) 진영이라고 하기도 함 (정식 X)
추상화의 단점
- 동작없이 데이터만 있는 클래스는 쓸데없는 코드만 늘어남
- DTO(Data Transfer Object)
- 이런 경우 그냥 public을 쓰기도 함..
- 어떻게 추상화를 해야하는지 뚜렷한 "객관적 기준"이 없음
- 인간은 뚜렷한 실체가 없는 개념을 이해하기 어려워함
- 이해하더라도 각자 다르게 이해함
- 다형성, 상속, 인터페이스로 가면서 더 문제가 됨
메서드 이름 짓는 방법
분무기의 물을 뿌리는 행위을 생각해보면..
- 동작에 초점을 맞추기
- 용도에 초점을 맞추기
좋은 표현력을 가지는 코드
- 물흐르듯 읽히는 변수명
- 단위로 한번 체크해주자(
remainingWaterInML
)
- 두가지의 일을 하는 경우 And를 써서 표현해주자(
sprayAndGetUsedAmountWater()
)
- 물론 안 하는게 가장 좋다.
- 만약 그래야 한다면 확실하게 표현하는게 낫다는 의미
- 매직 넘버보다는 상수형 변수를 사용하자.
개체 지향 프로그래밍을 하며 발생하는 실수들
- 모든 세계를 시뮬레이션 하려는 시도를 하지 말자.
- 필요한 것만 만들자.
- 사용하지 않는 것을 작성하는 것은 쓸데없는 유지보수 비용의 증가를 가져온다.
- 코드를 여러번 고치는 건 일반적이다.
- 적어도 2, 3번은 뒤엎어야 좋은 코드가 나온다.
- 당연한 것이라 받아들여라.
좋은 프로그래밍 습관
- 사용하지 않는 코드는 삭제한다.
- 고치는 일이 프로그래머에게는 업무와 다름없다.
- 그 과정에서 사용하지 않는 코드는 유지보수 비용을 증가시킨다.
- 필요한 시점에 코드를 추가하라.
- 미리 만들려고 하는 행동은 결국 불필요한 동작을 추가하는 것과 같다.
- 간단하게 만들고 살을 붙여라.
- 처음 코드를 짤 때 가정은 거의 틀릴 가능성이 높다.
- 그렇기 때문에 가장 간단하게 뼈대를 짜고, 이에 추후 확장이 가능토록 만드는 것이 좋다.
유연성의 두 얼굴
유연성 높은 설계가 최고는 아니다.
- 너무 쪼갤 경우 여러 파일을 넘나 들어야 함
- 하나의 콘텍스트로 적는 것이 보다 나은 소통을 가져올 수 있다.
- 먼 미래 때문에 지나치게 쪼개는 것은 오버 엔지니어링
- 프로그래머의 기본자세를 먼저 가질 것
- 읽기 명확한 코드 생성
- 실수를 저지르기 어려운 코드 생성
- 문제를 해결하는 코드 생성
- 문제가 생기면 디버깅
- 그 이후 필요에 따라 점점 유연성을 키우는 법을 배우기
개체 지향 프로그래밍 알고 있으면 좋은 사고방식
- 개체는 자율성을 가진다.
- "분무기로 화분에 물을 줘":
waterSpray.addWaterTo(flowerPot)
- "분무기를 줄테니 화분아 너가 물을 줘":
flowerPot.addWater(waterSpray)
- 전자가 보다 현실세계에서 자연스러우나, 코드 세계에서는 후자가 보다 나은 방식
- 개인적 생각: 영어적인 사고 방식으로 코드를 짜는 것이 보다 수월함
Reference