본 포스트는 카카오 테크 캠퍼스 1기에서 제공하는 패스트캠퍼스 강의에서 배운 내용을 정리하였습니다.

카카오 테크 캠퍼스

객체


의미

  • 의사나 행위가 미치는 대상 ( 사전적 의미 )
  • 구체적, 추상적 데이터의 단위 ( 학생, 회원, 생산, 주문, 배송 )

객체 지향 / 절차 지향

ex) 아침에 일어나 학교를 가는 과정을 예를 들자.


절차 지향 프로그래밍

  • 시간이나 사건의 흐름에 따른 프로그래밍

    일어난다 -> 씻는다 -> 밥을 먹는다 -> 버스를 탄다-> 요금을 지불한다 -> 학교에 도착

객체 지향 프로그래밍

  • 프로그램에 사용되는 '객체'를 기준으로 하는 프로그래밍

  • 객체 지향 프로그램은 어떻게 구현하는가?

    1. 객체를 정의 하고

    2. 각 객체의 속성을 멤버 변수로, 역할을 메서드로 구현하고

    3. 각 객체가 제공하는 기능들 간의 소통(메세지 전달)을 통하여 객체간의 협력을 구현

클래스 코딩하기

  • 클래스는 대문자로 시작하는것이 좋음
  • java 파일 하나에 클래스는 여러 개가 있을 수 있지만, public 클래스는 하나이고, public 클래스와 .java 파일의 이름은 동일함
  • camel notation 방식으로 명명

참조 자료형

  • 기본 자료형은 사용하는 메모리의 크기가 정해져 있지만, 참조 자료형은 클래스에 따라 다름

  • 참조 자료형을 사용 할때는 해당 변수에 대해 생성하여야 함
    (String 클래스는 예외적으로 생성하지 않고 사용할 수 있음)


인스턴스 생성과 힙 메모리


인스턴스 (instance)

클래스는 객체의 속성을 정의 하고, 기능을 구현하여 만들어 놓은 코드 상태

실제 클래스 기반으로 생성된 객체(인스턴스)는 각각 다른 멤버 변수 값을 가지게 됨

가령, 학생의 클래스에서 생성된 각각의 인스턴스는 각각 다른 이름, 학번, 학년등의 값을 가지게 됨

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;
	}
  • 객체를 생성할 때 new 키워드와 함께 사용 - new Student();
  • 생성자는 일반 함수처럼 기능을 호출하는 것이 아니고 객체를 생성하기 위해 new 와 함께 호출 됨
  • 객체가 생성될 때 변수나 상수를 초기화 하거나 다른 초기화 기능을 수행하는 메서드를 호출 함
  • 생성자는 반환 값이 없고, 클래스의 이름과 동일
  • 대부분의 생성자는 외부에서 접근 가능하지만, 필요에 의해 private 으로 선언되는 경우도 있음

  • 멤버변수는 생성자호출되면서 값을 초기화 하지 않아도 자동으로 초기화됨(0, false, null 등등)

기본 생성자 (default constructor)

  • 클래스에는 반드시 적어도 하나 이상의 생성자가 존재
  • 클래스에 생성자를 구현하지 않아도 new 키워드와 함께 생성자를 호출할 수 있음

  • 디폴트 생성자는 생성자선언을 안햇을때 자동 생성됨

  • 클래스에 생성자를 따로 구현하면 기본 생성자는 제공되지 않음

    => 기본 생성자가 필요하다면 마찬가지로 선언해주면됨

  • 매개 변수가 없음, 구현부가 없음

캡슐화


설명

캡슐화란 클래스 안에 서로 연관있는 속성과 기능들을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것을 말한다. => 이때 캡슐은 클래스를 의미

목적

  • 데이터 보호(data protection) – 외부로부터 클래스에 정의된 속성과 기능들을 보호

  • 데이터 은닉(data hiding) – 내부의 동작을 감추고 외부에는 필요한 부분만 노출

    => 참조연산자(.)로 직접 접근 지양.

효과

캡슐화를 통해 객체 동작의 구현 세부 정보를 숨기고 객체와 상호 작용하기 위한 공용 인터페이스(메서드)만 클라에 제공한다.

객체를 이용하는 클라이언트는 객체가 어떤 데이터와 코드를 가지고 있는지 몰라도 인터페이스를 통해 객체의 기능을 사용할 수 있다.

즉, 객체 내부의 데이터와 코드를 외부에 노출하지 않고도 객체의 기능을 사용할 수 있다.

이는 객체의 내부 구현을 변경하더라도 외부 코드에 영향을 미치지 않게 만들어준다.

또한, 객체 내부에 캡슐화된 데이터를 보호하고, 잘못된 접근으로 인한 오류를 방지하는 효과도 있다.

(실제로 우리가쓰는 대부분 언어의 내장함수는 캡슐화가 잘 되어있다.)

구현 방법

1. 접근 제어 지시자

클래스 외부에서 클래스의 멤버 변수, 메서드, 생성자를 사용할 수 있는지 여부를 지정하는 키워드

  • private : 같은 클래스 내부에서만 접근 가능 ( 외부 클래스, 상속 관계의 클래스에서도 접근 불가)
  • 아무것도 없음 (default) : 같은 패키지 내부에서만 접근 가능 ( 상속 관계라도 패키지가 다르면 접근 불가)
  • protected : 같은 패키지나 상속관계의 클래스에서 접근 가능하고 그 외 외부에서는 접근 할 수 없음
  • public : 클래스의 외부 어디서나 접근 할 수 있음

2. get/set 메서드

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;
	}

}

3. 은닉화 구현 방법

클래스에 멤버변수들을 private하게 선언하고,

get, set 메서드를 이용헤서 멤버변수의 변화를 주거나 가져오는게 은닉화에 전형적인 방법이다.

이렇게하면 멤버변수의 직접 접근을 막음으로서 객체를사용할떄 예상치 못한 오류를 막을수있고,

나중에 디버깅할떄도 어디서 잘못되었는지를 볼떄 메서드의 로직을 보면되기 떄문에 편리하다.

즉 객체를 사용하는 쪽에서 잘못 사용할 확률을 줄이는것이다.

그렇다고 안전을 위해 '모든 변수들을 private해야한다'는 아니다.

상황과 주어진 자원에 따라 멤버의 접근제어정도를 조절해야한다.

정책상으로 오픈해야되거나 오픈함으로써 더 유용한 멤버들은 public이나 다른 접근제어자로 접근허용 해주는게 좋다.

4. 캡슐화 고려하지 않은 코드

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클래스가 사용하게 하면 어떨까?

5. 캡슐화된 코드

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);
	}
}

이렇게 사용 가능하다.

여기서 핵심은

  1. 노출될 필요 없는 멤버 변수들을 모두 private하게 은닉화 시켰고

  2. report를 만드는데 사용했던 3가지 로직을 은닉화 시킨 후, getReport라는 메서드만을 통해
    외부에서 결과물을 받도록 하고 있다.

이렇게 하면 report를 만드는 로직, 설정값등이 바껴도, 그 바뀐내용은 MakeReport클래스라는 캡슐안에서 내부적으로 수정될 것이고, 외부클래스인 TestMakeReport는 MakeReport내부가 바뀌든 말든 신경안쓰고 여전히 getReport 메서드를 통해서 원하는 결과물을 받을 수 있다!

6. 결론

이렇게 캡슐화를 활용하면, 객체 내부의 속성과 동작을 외부로의 노출을 최소화하여 각 객체의 자율성을 높이고, 이를 통해 객체 간 결합도를 낮추어 앞서 설명한 객체 지향의 핵심적인 이점을 잘 살리는 방법으로 프로그램을 설계하는 일이 가능하다.

참고 자료

profile
새로운 여정은 언제나 두렵고 동시에 흥미로 가득 차 있다.

0개의 댓글