Section1-객체지향 프로그래밍심화(캡슐화)

솜씨좋은 개발자·2022년 7월 20일
0

Section1

목록 보기
13/18

📖 학습 목표

  • 캡슐화
  • 패키지와 import
  • 접근제어자
  • getter/setter

객체지향 4가지 특징(상속성, 캡슐화, 다형성, 추상화)

✍ 캡슐화

캡슐화란 특정 객체 안에 관련된 속성과 기능을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것을 말한다.
이렇게 캡슐화를 해야하는 이유로 크게 세 가지 목적이 있다. 첫 째는 데이터 보호의 목적이고, 두 번째로 내부적으로만 사용되는 데이터에 대한 불필요한 외부 노출을 방지하기 위함이다. 즉, 정보 은닉을 위한 것이다. 세 번째는 유지보수와 코드 확장 시에도 오류의 범위를 최소화할 수 있어서 효과적으로 코드를 유지보수하기에 용이하다.

✍ 패키지

패키지(package)란 특정한 목적을 공유하는 클래스와 인터페이스의 묶음을 의미
패키지는 클래스들을 그룹 단위로 묶어 효과적으로 관리하기 위한 목적을 가지고 있다.

이 디렉토리는 하나의 계층구조를 가지고 있는데, 계층 구조 간 구분은 점(.)으로 표현된다.
마지막으로, 패키지가 있는 경우 소스 코드의 첫 번째 줄에 반드시 package 패키지명이 표시되어야 하고, 만약 패키지 선언이 없으면 이름없는 패키지에 속하게 된다.

// 패키지를 생성했을 때
package practicepack.test; // 패키지 구문 포함. 패키지가 없다면 구문 필요없음

패키지로 클래스를 묶는 것의 또 하나의 장점은 클래스의 충돌을 방지해주는 기능에 있다. 예를 들면, 같은 이름의 클래스를 가지고 있더라고 각각 다른 패키지에 소속되어 있다면 이름명으로 인한 충돌이 발생하지 않는다. 규모가 큰 프로젝트에서 협업시 클래스명 중복으로 인한 충돌이 종종 발생할 수 있는데 패키지를 설정하면 이러한 클래스 간의 충돌을 효과적으로 방지할 수 있다.

Import문

import문은 다른 패키지 내의 클래스를 사용하기 위해 사용하며, 일반적으로 패키지 구문과 클래스문 사이에 작성한다.
예를 들면, import문 없이 다른 패키지의 클래스를 사용하기 위해서는 아래와 같이 매번 패키지명을 붙여 주어야 하는데, import문을 사용하면 사전에 컴파일러에게 소스파일에 사용된 클래스에 대한 정보를 제공하여 이러한 번거로움을 덜 수 있다.

import 패키지명.클래스명; 또는 import 패키지명.*;

먼저 import 키워드를 써주고 패키지명과 패키지명을 생략하고자하는 클래스명을 함께 써주면 된다.
만약 같은 패키지에서 여러 클래스가 사용될 때는 import문을 여러번 사용하기보다는 위에 작성된 것처럼 import 패키지명.* 으로 작성하면 해당 패키지의 모든 클래스를 패키지명 없이 사용가능하다.

package practicepack.test;

public class ExampleImp {
		public int a = 10;
		public void print() {
			System.out.println("Import문 테스트")
}

package practicepack.test2; // import문을 사용하는 경우

import practicepack.test.ExampleImp // import문 작성

public class PackageImp {
		public static void main(String[] args) {
			ExampleImp x = new ExampleImp(); // 이제 패키지명을 생략 가능
		}
}

✍ 접근 제어자

제어자는 클래스, 필드, 메서드, 생성자 등에 부가적인 의미를 부여하는 키워드를 의미한다.
자바에서 제어자는 크게 접근 제어자와 기타 제어자로 구분할 수 있다.

접근 제어자: public, protected, (default), private
기타 제어자: static, final, abstract, native, transient, synchronized 등

접근 제어자를 사용하면 클래스 외부로의 불필요한 데이터 노출을 방지(data hiding)할 수 있고, 외부로부터 데이터가 임의로 변경되지 않도록 막을 수 있다. 이것은 데이터 보호의 측면에서 매우 중요하다고 할 수 있다.

접근 제어자접근 제한 범위
public접근 제한 없음
protected동일 패키지 + 다른 패키지의 하위 클래스에서 접근 가능
(default)동일 패키지 내에서만 접근 가능
private동일 클래스에서만 접근 가능

public(접근 제한 없음) > protected(동일 패키지 + 하위클래스) > default(동일 패키지) > private(동일 클래스) 순으로 정리할 수 있다.
이중 default의 경우는 아무런 접근 제어자를 붙이지 않는 경우 기본적인 설정을 의미한다. 즉 변수명 앞에 아무런 접근제어자가 없는 경우에는 자동으로 해당 변수의 접근 제어자는 default가 된다.

package package1; // 패키지명 package1 

//파일명: Parent.java
class Test { // Test 클래스의 접근 제어자는 default
    public static void main(String[] args) {
        Parent p = new Parent();

//        System.out.println(p.a); // 동일 클래스가 아니기 때문에 에러발생!
        System.out.println(p.b);
        System.out.println(p.c);
        System.out.println(p.d);
    }
}

public class Parent { // Parent 클래스의 접근 제어자는 public
    private int a = 1; // a,b,c,d에 각각 private, default, protected, public 접근제어자 지정
    int b = 2;
    protected int c = 3;
    public int d = 4;

    public void printEach() { // 동일 클래스이기 때문에 에러발생하지 않음
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);
    }
}

// 출력값
2
3
4

a,b,c,d 모두 에러 없이 정상적으로 접근 가능함을 확인하실 수 있다. 동일한 패키지의 동일한 클래스 내에 있기 때문에 가장 접근 제한이 엄격한 private 변수도 접근이 가능하다.
반면 위의 Test 클래스에서 객체를 생성하여 접근을 시도했을 때는 private 접근 제어자가 있는 a에는 접근이 불가하여 에러가 발생하는 모습을 확인할 수 있다.

package package2; // package2 

//파일명 Test2.java
import package1.Parent;

class Child extends package1.Parent {  // package1으로부터 Parent 클래스를 상속
    public void printEach() {
        // System.out.println(a); // 에러 발생!
        // System.out.println(b);
        System.out.println(c); // 다른 패키지의 하위 클래스
        System.out.println(d);
    }
}

public class Test2 {
    public static void main(String[] args) {
        Parent p = new Parent();

//        System.out.println(p.a); // public을 제외한 모든 호출 에러!
//        System.out.println(p.b);
//        System.out.println(p.c);
        System.out.println(p.d);
    }
}

먼저 package1의 Parent 클래스로부터 상속을 받아 만들어진 Child 클래스를 살펴보면, 같은 클래스와 같은 패키지 안에 있는 private(a)과 default(b) 접근 제어자를 사용하는 멤버에는 접근이 불가능한 반면, 다른 패키지의 하위 클래스에 접근가능한 protected(c)와 어디서나 접근이 가능한 public(d)에는 접근이 가능하다는 사실을 확인할 수 있다.
마지막으로 Test2 클래스는 상속받은 클래스가 아니기 때문에 다시 protected(c)에는 접근이 불가능하고 public(d)에만 접근이 가능하다.
결론적으로 다시 정리하면, 우리는 접근 제어자를 통해 외부로부터 데이터를 보호하고, 불필요하게 데이터가 노출되는 것을 방지 할 수 있다.

✍ getter와 setter 메서드

그럼 캡슐화를 유지하면서 데이터를 변경해야 한다면 어떻게 해야 할까?
private 접근제어자가 포함되어 있는 객체의 변수의 데이터 값을 추가하거나 수정하고 싶을 때를 생각해볼 수 있다.이런 경우 getter와 setter 메서드를 사용할 수 있다.

public class GetterSetterTest {
    public static void main(String[] args) {
        Worker w = new Worker();
        w.setName("김코딩");
        w.setAge(30);
        w.setId(5);

        String name = w.getName();
        System.out.println("근로자의 이름은 " + name);
        int age = w.getAge();
        System.out.println("근로자의 나이는 " + age);
        int id = w.getId();
        System.out.println("근로자의 ID는 " + id);
    }
}

class Worker {
    private String name; // 변수의 은닉화. 외부로부터 접근 불가
    private int age;
    private int id;

    public String getName() { // 멤버변수의 값 
        return name;
    }

    public void setName(String name) { // 멤버변수의 값 변경
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if(age < 1) return;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

// 출력값
근로자의 이름은 김코딩
근로자의 나이는 30
근로자의 ID는 5

먼저 setter 메서드는 외부에서 메서드에 접근하여 조건에 맞을 경우 데이터 값을 변경 가능하게 해주고 일반적으로 메서드명에 set-을 붙여서 정의한다.
예시를 보면 이름 값을 변경하기 위해 setName()이라는 메서드를 사용하고 있음을 확인할 수 있다.

한편 getter 메서드는 이렇게 설정한 변수 값을 읽어오는 데 사용하는 메서드이다.
경우에 따라 객체 외부에서 필드 값을 사용하기에 부적절한 경우가 발생할 수 있는데 이런 경우에 그 값을 가공한 이후에 외부로 전달하는 역할을 하게 된다.
위의 예시에서 볼 수 있듯이 get-을 메서드명 앞에 붙여서 사용한다.

예시를 좀 더 자세히 살펴보면, 먼저 Worker 클래스를 기반으로 객체 인스턴스를 생성해주고 같은 타입을 가지고 있는 참조변수 w에 담았다.
다음으로 w의 setter 메서드를 사용하여 이름, 나이, 아이디에 대한 데이터 값을 저장하고, getter 메서드를 통해 해당 데이터 값을 불러와 변수에 담아 출력해주고 있다.

이렇게 setter와 getter 메서드를 활용하면 데이터를 효과적으로 보호하면서도 의도하는 값으로 값을 변경하여 캡슐화를 보다 효과적으로 달성할 수 있다.

profile
개발의 방으로

0개의 댓글

관련 채용 정보