본 포스트는 카카오 테크 캠퍼스 1기에서 제공하는 패스트캠퍼스 강의에서 배운 내용을 정리하였습니다.
시간이나 사건의 흐름에 따른 프로그래밍
일어난다 -> 씻는다 -> 밥을 먹는다 -> 버스를 탄다-> 요금을 지불한다 -> 학교에 도착
객체 지향 프로그램은 어떻게 구현하는가?
객체를 정의 하고
각 객체의 속성을 멤버 변수로, 역할을 메서드로 구현하고
각 객체가 제공하는 기능들 간의 소통(메세지 전달)을 통하여 객체간의 협력을 구현
기본 자료형은 사용하는 메모리의 크기가 정해져 있지만, 참조 자료형은 클래스에 따라 다름
참조 자료형을 사용 할때는 해당 변수에 대해 생성하여야 함
(String 클래스는 예외적으로 생성하지 않고 사용할 수 있음)
클래스는 객체의 속성을 정의 하고, 기능을 구현하여 만들어 놓은 코드 상태
실제 클래스 기반으로 생성된 객체(인스턴스)는 각각 다른 멤버 변수 값을 가지게 됨
가령, 학생의 클래스에서 생성된 각각의 인스턴스는 각각 다른 이름, 학번, 학년등의 값을 가지게 됨
new 키워드를 사용하여 인스턴스 생성
완성된 인스턴스는 동적 메모리(heap memory) 에 할당됨
C나 C++ 언어에서는 사용한 동적 메모리를 프로그래머가 해제 시켜야 함
( C에서는 free() / C++에서는 delete 이용)
자바에서 Gabage Collector 가 주기 적으로 사용하지 않는 메모리를 수거
하나의 클래스로 부터 여러개의 인스턴스가 생성되고 각각 다른 메모리 주소를 가지게 됨.
Student studentLee=new Student();
여기서 studentLee는 참조변수라고 하고,
생성된 인스턴스의 메모리 주소 값을 참조 값이라함.
=> studentLee가 가르키는게 heap영역에 저장된 인스턴스의 메모리 주소를 가르키기때문
<클래스 명>([매개변수])
{
원하는 멤버변수의 초기 셋팅 또는 메서드 실행
}
ex)
public Student(int studentNumber, String studentName, int grade) {
this.studentNumber = studentNumber;
this.studentName = studentName;
this.grade = grade;
}
대부분의 생성자는 외부에서 접근 가능하지만, 필요에 의해 private 으로 선언되는 경우도 있음
멤버변수는 생성자호출되면서 값을 초기화 하지 않아도 자동으로 초기화됨(0, false, null 등등)
클래스에 생성자를 구현하지 않아도 new 키워드와 함께 생성자를 호출할 수 있음
디폴트 생성자는 생성자선언을 안햇을때 자동 생성됨
클래스에 생성자를 따로 구현하면 기본 생성자는 제공되지 않음
=> 기본 생성자가 필요하다면 마찬가지로 선언해주면됨
매개 변수가 없음, 구현부가 없음
캡슐화란 클래스 안에 서로 연관있는 속성과 기능들을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것을 말한다. => 이때 캡슐은 클래스를 의미
데이터 보호(data protection) – 외부로부터 클래스에 정의된 속성과 기능들을 보호
데이터 은닉(data hiding) – 내부의 동작을 감추고 외부에는 필요한 부분만 노출
=> 참조연산자(.)로 직접 접근 지양.
캡슐화를 통해 객체 동작의 구현 세부 정보를 숨기고 객체와 상호 작용하기 위한 공용 인터페이스(메서드)만 클라에 제공한다.
객체를 이용하는 클라이언트는 객체가 어떤 데이터와 코드를 가지고 있는지 몰라도 인터페이스를 통해 객체의 기능을 사용할 수 있다.
즉, 객체 내부의 데이터와 코드를 외부에 노출하지 않고도 객체의 기능을 사용할 수 있다.
이는 객체의 내부 구현을 변경하더라도 외부 코드에 영향을 미치지 않게 만들어준다.
또한, 객체 내부에 캡슐화된 데이터를 보호하고, 잘못된 접근으로 인한 오류를 방지하는 효과도 있다.
(실제로 우리가쓰는 대부분 언어의 내장함수는 캡슐화가 잘 되어있다.)
클래스 외부에서 클래스의 멤버 변수, 메서드, 생성자를 사용할 수 있는지 여부를 지정하는 키워드
private 으로 선언된 멤버 변수 (필드)에 대해 접근, 수정할 수 있는 메서드를 public으로 제공하는 방식. 각각 getter, setter라고도함
get() 메서드만 제공 되는 경우 read-only 필드
몇몇 IDE에서는 getter,setter를 생성해주는 기능있음
=> ex(이클립스)
public class Test1Test {
private int studentNumber;
private String studentName;
public int getStudentNumber() { //Eclipse 에서 자동 generate 기능 이용
return studentNumber;
}
public void setStudentNumber(int studentNumber) { //Eclipse 에서 자동 generate 기능 이용
this.studentNumber = studentNumber;
}
public String getStudentName() { //Eclipse 에서 자동 generate 기능 이용
return studentName;
}
public void setStudentName(String studentName) { //Eclipse 에서 자동 generate 기능 이용
this.studentName = studentName;
}
}
클래스에 멤버변수들을 private하게 선언하고,
get, set 메서드를 이용헤서 멤버변수의 변화를 주거나 가져오는게 은닉화에 전형적인 방법이다.
이렇게하면 멤버변수의 직접 접근을 막음으로서 객체를사용할떄 예상치 못한 오류를 막을수있고,
나중에 디버깅할떄도 어디서 잘못되었는지를 볼떄 메서드의 로직을 보면되기 떄문에 편리하다.
즉 객체를 사용하는 쪽에서 잘못 사용할 확률을 줄이는것이다.
그렇다고 안전을 위해 '모든 변수들을 private해야한다'는 아니다.
상황과 주어진 자원에 따라 멤버의 접근제어정도를 조절해야한다.
정책상으로 오픈해야되거나 오픈함으로써 더 유용한 멤버들은 public이나 다른 접근제어자로 접근허용 해주는게 좋다.
public class MakeReport {
StringBuffer buffer = new StringBuffer();
private String line = "===========================================\n";
private String title = " 이름\t 주소 \t\t 전화번호 \n";
void makeHeader()
{
buffer.append(line);
buffer.append(title);
buffer.append(line);
}
void generateBody()
{
buffer.append("James \t");
buffer.append("Seoul Korea \t");
buffer.append("010-2222-3333\n");
buffer.append("Tomas \t");
buffer.append("NewYork US \t");
buffer.append("010-7777-0987\n");
}
void makeFooter()
{
buffer.append(line);
}
}
위 MakeReport라는 코드를
public class TestMakeReport {
public static void main(String[] args) {
report report = new report();
report.makeHeader();
report.generateBody();
report.makeFooter();
StringBuffer builder = report.buffer
System.out.println(builder);
}
}
이렇게 하면 정상적을 출력이 될것이다. 것보기엔 문제가 없다.
하지만 이것은 캡슐화가 제대로 되지 않은 코드이다.
캡슐화가 잘된 코드라면 report클래스에 쓰는 멤버변수와 메서드들은 최대한 그 클래스 내부에서 사용되서 캡슐처럼 닫혀있어야 하기 떄문이다.
멤버 변수중 buffer도 은닉화 되어 있지 않고,
MakeReport클래스안에서 report를 완성시키기 위해 만든 3가지 메서드를 TestMakeReport클래스 안에서 사용하고 있다. 이는 캡슐화가 제대로 이뤄지지 않았고, 두 클래스간 결합도가 높다고 할 수 있다.
이런 경우, report를 만드는 로직이 바뀌면 TestMakeReport에서도 많은 부분이 수정해야 될 수 있다.
예를 들어, report를 만드는 순서가 makeHeader -> makeFooter -> generateBody 로 바꼈다고 해보자. 그러면 우리는 TestMakeReport 클래스에서 로직을 다음과 같이 바꿔야 한다.
public class TestMakeReport {
public static void main(String[] args) {
report report = new report();
report.makeHeader();
report.makeFooter();
report.generateBody();
StringBuffer builder = report.buffer
System.out.println(builder);
}
}
지금 예시에서야 순서만 바뀐거지, 로직이나 함수명 등이 바뀌면 또 그걸 다 적용시켜줘야한다.
하지만 위 3가지 로직은 원래 MakeReport클래스의 기능이였으므로, 3가지 로직을 은닉화 시키고 완성된 report 결과물 만을 TestMakeReport클래스가 사용하게 하면 어떨까?
public class MakeReport {
private StringBuffer buffer = new StringBuffer();
private String line = "===========================================\n";
private String title = " 이름\t 주소 \t\t 전화번호 \n";
private void makeHeader()
{
buffer.append(line);
buffer.append(title);
buffer.append(line);
}
private void generateBody()
{
buffer.append("James \t");
buffer.append("Seoul Korea \t");
buffer.append("010-2222-3333\n");
buffer.append("Tomas \t");
buffer.append("NewYork US \t");
buffer.append("010-7777-0987\n");
}
private void makeFooter()
{
buffer.append(line);
}
public String getReport()
{
void makeHeader();
void generateBody();
void makeFooter();
return buffer.toString();
}
}
위 처럼 구현하면
public class TestMakeReport {
public static void main(String[] args) {
report report = new report();
String builder = report.getReport();
System.out.println(builder);
}
}
이렇게 사용 가능하다.
여기서 핵심은
노출될 필요 없는 멤버 변수들을 모두 private하게 은닉화 시켰고
report를 만드는데 사용했던 3가지 로직을 은닉화 시킨 후, getReport라는 메서드만을 통해
외부에서 결과물을 받도록 하고 있다.
이렇게 하면 report를 만드는 로직, 설정값등이 바껴도, 그 바뀐내용은 MakeReport클래스라는 캡슐안에서 내부적으로 수정될 것이고, 외부클래스인 TestMakeReport는 MakeReport내부가 바뀌든 말든 신경안쓰고 여전히 getReport 메서드를 통해서 원하는 결과물을 받을 수 있다!
이렇게 캡슐화를 활용하면, 객체 내부의 속성과 동작을 외부로의 노출을 최소화하여 각 객체의 자율성을 높이고, 이를 통해 객체 간 결합도를 낮추어 앞서 설명한 객체 지향의 핵심적인 이점을 잘 살리는 방법으로 프로그램을 설계하는 일이 가능하다.