객체지향의 4대 특성(4) : 캡슐화 (1)

de_sj_awa·2021년 6월 5일
0

캡슐화 : 정보 은닉

자바에서 정보 은닉(information hiding)이라고 하면 접근 제어자인 private, default(package-private), protected, public와 접근자 및 설정자 메서드가 생각날 것이다.

접근 제어자가 객체 멤버(인스턴스 멤버)와 쓰일 때와 정적 멤버(클래스 멤버)와 함께 쓰일 때를 비교해보자.

1. 객체 멤버의 접근 제어자

자신의 멤버가 아닌 다른 객체의 멤버에 접근하는 경우에는 다른 객체를 생성한 후 접근해야 한다.

ClassA의 객체 멤버에는 pri, def, pro, pub 속성이 있다. UML 표기법에서 - 표시는 private 접근자를, ~ 표시는 default 접근 제어자를, # 표시는 protected 접근자를, + 표시는 public 접근 제어자를 나타낸다. 속성이나 메서드 아래에 _(밑줄)을 사용한 경우에는 정적 멤버를 나타낸다.

위 UML 다이어그램을 자바 코드로 나타내면 다음과 같다.

ClassA.java

package encapsulation01.packageOne;

public Class ClassA {
    
    private int pri;
    int def;
    protected int pro;
    public int pub;
    
    void runSomething() {
    
    }
    
    static void runStaticThing() {
    
    }
}

1. PackageOne.ClassA에서의 접근

package encapsulation01.packageOne;

public class ClassA {
    private String pri="private";
    String def = "default";
    protected String pro = "protected";
    public String pub = "public";

    void runSomething() {
        System.out.print("rung something\n");
    }

    static void runStaticThing() {
        System.out.print("rung static thing\n");
    }

    public static void main(String[] args) {
        ClassA classA = new ClassA();
        System.out.println(classA.pri);
        System.out.println(classA.def);
        System.out.println(classA.pro);
        System.out.println(classA.pub);
        classA.runSomething();
        classA.runStaticThing();
    }
}

실행 결과

private
default
protected
public
rung something
rung static thing

ClassA에서는 모든 객체 멤버 변수와 메서드에 접근 가능하다.

2. PackageOne.ClassB에서의 접근

package encapsulation01.packageOne;

public class ClassB {
    public static void main(String[] args) {
        ClassA classA = new ClassA();
        // System.out.println(classA.pri); 오류 발생
        System.out.println(classA.def);
        System.out.println(classA.pro);
        System.out.println(classA.pub);
        classA.runSomething();
        classA.runStaticThing();
    }
}

실행 결과

default
protected
public
rung something
rung static thing

ClassB에서는 ClassA의 private 접근 제어자가 붙은 객체 멤버 변수에 접근이 불가능하다.

3. PackageOne.ClassAA에서의 접근

package encapsulation01.packageOne;

public class ClassAA extends ClassA{
    public static void main(String[] args) {
        ClassA classA = new ClassA();
        // System.out.println(classA.pri); 오류 발생
        System.out.println(classA.def);
        System.out.println(classA.pro);
        System.out.println(classA.pub);
        classA.runSomething();
        classA.runStaticThing();
    }
}

실행 결과

default
protected
public
rung something
rung static thing

ClassAA에서는 ClassA의 private 접근 제어자가 붙은 객체 멤버 변수에 접근이 불가능하다.

4. PackageTwo.ClassAB에서의 접근

package encapsulation01.packageTwo;
import encapsulation01.packageOne.ClassA;

public class ClassAB extends ClassA{
    public static void main(String[] args) {
        ClassA classA = new ClassA();
        // System.out.println(classA.pri);
        // System.out.println(classA.def);
        // System.out.println(classA.pro);
        System.out.println(classA.pub);
        // classA.runSomething();
        // classA.runStaticThing();
    }
}

실행 결과

public

ClassAB는 ClassA의 상속을 받았고 ClassA의 객체를 생성했음에도 불구하고 ClassA의 public 접근 제어자가 붙은 객체 멤버 변수에만 접근이 가능하다. protected 접근 제어자를 사용했더라도 상속을 받은 자신의 클래스의 객체 멤버 변수에만 접근이 가능하다.

5. PackageTwo.ClassC에서의 접근

package encapsulation01.packageTwo;

import encapsulation01.packageOne.ClassA;

public class ClassC {
    public static void main(String[] args) {
        ClassA classA = new ClassA();
//        System.out.println(classA.pri);
//        System.out.println(classA.def);
//        System.out.println(classA.pro);
        System.out.println(classA.pub);
//        classA.runSomething();
//        classA.runStaticThing();
    }

실행 결과

public

ClassC는 ClassA의 public 접근 제어자가 붙은 객체 멤버 변수에만 접근이 가능하다.

2. 정적 멤버의 접근 제어자

ClassA가 정적 속성인 priSt, defSt, proSt, pubSt를 가지고 있다고 가정해보면 다음과 같은 UML을 작성할 수 있다.

이를 자바 코드로 표현하면 다음과 같다.

1. PackageOne.ClassA에서의 접근

package encapsulation01.packageOne;

public class ClassA {
	private int pri;
	int def;
	protected int pro;
	public int pub;

	static private int priStatic;
	static int defStatic;
	static protected int proStatic;
	static public int pubStatic;

	void runSomething() {
		pri = 1;
		this.pri = 1;
		def = 1;
		this.def = 1;
		pro = 1;
		this.pro = 1;
		pub = 1;
		this.pub = 1;

		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		ClassA.priStatic = 1;
		priStatic = 1;
		this.priStatic = 1;
		ClassA.defStatic = 1;
		defStatic = 1;
		this.defStatic = 1;
		ClassA.proStatic = 1;
		proStatic = 1;
		this.proStatic = 1;
		ClassA.pubStatic = 1;
		pubStatic = 1;
		this.pubStatic = 1;
	}

	static void runStaticThing() {
		// 객체를 생성하지 않고는 객체 멤버 접근 불가
		// pri = 1; this.pri = 1;
		// def = 1; this.def = 1;
		// pro = 1; this.pro = 1;
		// pub = 1; this.pub = 1;

		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		ClassA.priStatic = 1;
		priStatic = 1; // this.priStatic = 1;
		ClassA.defStatic = 1;
		defStatic = 1; // this.defStatic = 1;
		ClassA.proStatic = 1;
		proStatic = 1; // this.proStatic = 1;
		ClassA.pubStatic = 1;
		pubStatic = 1; // this.pubStatic = 1;

		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassA ca = new ClassA();
		ca.pri = 1;
		ca.def = 1;
		ca.pro = 1;
		ca.pub = 1;

		// 객체 참조 변수를 통해 정적 멤버도 접근 가능, 권장하지는 않음
		ca.priStatic = 1;
		ca.defStatic = 1;
		ca.proStatic = 1;
		ca.pubStatic = 1;
	}
}

2. PackageOne.ClassB에서의 접근

package encapsulation01.packageOne;

public class ClassB {
	void runSomething() {
		// 상속을 받지 않았기에 ClassA 의 객체 멤버는 객체 생성 후에 접근 가능
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		//pro = 1;	this.pro = 1;
		//pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	//priStatic = 1;	this.priStatic = 1;
		ClassA.defStatic = 1;	//defStatic = 1;	this.defStatic = 1;
		ClassA.proStatic = 1;	//proStatic = 1;	this.proStatic = 1;
		ClassA.pubStatic = 1;	//pubStatic = 1;	this.pubStatic = 1;

		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassA ca = new ClassA();
		//ca.pri = 1;
		ca.def = 1;
		ca.pro = 1;
		ca.pub = 1;
	}
	
	static void runStaticThing() {
		// 객체를 생성하지 않고는 객체 멤버 접근 불가
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		//pro = 1;	this.pro = 1;
		//pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	//priStatic = 1;	//this.priStatic = 1;
		ClassA.defStatic = 1;	//defStatic = 1;	//this.defStatic = 1;
		ClassA.proStatic = 1;	//proStatic = 1;	//this.proStatic = 1;
		ClassA.pubStatic = 1;	//pubStatic = 1;	//this.pubStatic = 1;
		
		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassA ca = new ClassA();
		//ca.pri = 1;
		ca.def = 1;
		ca.pro = 1;
		ca.pub = 1;
		
		// 객체 참조 변수를 통해 정적 멤버도 접근 가능, 권장하지는 않음
		//ca.priStatic = 1;
		ca.defStatic = 1;
		ca.proStatic = 1;
		ca.pubStatic = 1;
	}
}

3. PackageOne.ClassAA에서의 접근

package encapsulation01.packageOne;

public class ClassAA extends ClassA {	
	void runSomething() {
		//pri = 1;	this.pri = 1;
		def = 1;	this.def = 1;
		pro = 1;	this.pro = 1;
		pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	priStatic = 1;	this.priStatic = 1;
		ClassA.defStatic = 1;	defStatic = 1;	this.defStatic = 1;
		ClassA.proStatic = 1;	proStatic = 1;	this.proStatic = 1;
		ClassA.pubStatic = 1;	pubStatic = 1;	this.pubStatic = 1;
	}
	
	static void runStaticThing() {
		// 객체를 생성하지 않고는 객체 멤버 접근 불가
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		//pro = 1;	this.pro = 1;
		//pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	priStatic = 1;	//this.priStatic = 1;
		ClassA.defStatic = 1;	defStatic = 1;	//this.defStatic = 1;
		ClassA.proStatic = 1;	proStatic = 1;	//this.proStatic = 1;
		ClassA.pubStatic = 1;	pubStatic = 1;	//this.pubStatic = 1;
		
		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassAA caa = new ClassAA();
		//ca.pri = 1;
		caa.def = 1;
		caa.pro = 1;
		caa.pub = 1;
		
		// 객체 참조 변수를 통해 정적 멤버도 접근 가능, 권장하지는 않음
		//ca.priStatic = 1;
		caa.defStatic = 1;
		caa.proStatic = 1;
		caa.pubStatic = 1;
	}
}

4. PackageTwo.ClassAB에서의 접근

package encapsulation01.packageTwo;

import encapsulation01.packageOne.ClassA;

public class ClassAB  extends ClassA {	
	void runSomething() {
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		pro = 1;	this.pro = 1;
		pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	priStatic = 1;	this.priStatic = 1;
		//ClassA.defStatic = 1;	defStatic = 1;	this.defStatic = 1;
		ClassA.proStatic = 1;	proStatic = 1;	this.proStatic = 1;
		ClassA.pubStatic = 1;	pubStatic = 1;	this.pubStatic = 1;
	}
	
	static void runStaticThing() {
		// 객체를 생성하지 않고는 객체 멤버 접근 불가
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		//pro = 1;	this.pro = 1;
		//pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	priStatic = 1;	//this.priStatic = 1;
		//ClassA.defStatic = 1;	defStatic = 1;	//this.defStatic = 1;
		ClassA.proStatic = 1;	proStatic = 1;	//this.proStatic = 1;
		ClassA.pubStatic = 1;	pubStatic = 1;	//this.pubStatic = 1;
		
		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassAB cab = new ClassAB();
		//ca.pri = 1;
		//ca.def = 1;
		cab.pro = 1;
		cab.pub = 1;
		
		// 객체 참조 변수를 통해 정적 멤버도 접근 가능, 권장하지는 않음
		//ca.priStatic = 1;
		//cab.defStatic = 1;
		cab.proStatic = 1;
		cab.pubStatic = 1;
	}
}

위의 예제와는 다르게 protected 접근 제어자가 붙은 객체 멤버 변수에 접근이 가능하다. 상속을 받은 자신의 객체 멤버 변수에 접근이 가능한 것이다. 또한 정적 멤버 변수는 객체를 생성하지 않아도 접근이 가능하기 때문에 private 접근 제어자가 붙은 정적 멤버 변수 이외에는 모두 접근 가능하다.

5. PackageTwo.ClassC에서의 접근

package encapsulation01.packageTwo;

import encapsulation01.packageOne.ClassA;

public class ClassC {
	void runSomething() {
		// 상속을 받지 않았기에 ClassA 의 객체 멤버는 객체 생성 후에 접근 가능
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		//pro = 1;	this.pro = 1;
		//pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	//priStatic = 1;	this.priStatic = 1;
		//ClassA.defStatic = 1;	//defStatic = 1;	this.defStatic = 1;
		//ClassA.proStatic = 1;	//proStatic = 1;	this.proStatic = 1;
		ClassA.pubStatic = 1;	//pubStatic = 1;	this.pubStatic = 1;

		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassA ca = new ClassA();
		//ca.pri = 1;
		//ca.def = 1;
		//ca.pro = 1;
		ca.pub = 1;
	}
	
	static void runStaticThing() {
		// 객체를 생성하지 않고는 객체 멤버 접근 불가
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		//pro = 1;	this.pro = 1;
		//pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	//priStatic = 1;	//this.priStatic = 1;
		//ClassA.defStatic = 1;	//defStatic = 1;	//this.defStatic = 1;
		//ClassA.proStatic = 1;	//proStatic = 1;	//this.proStatic = 1;
		ClassA.pubStatic = 1;	//pubStatic = 1;	//this.pubStatic = 1;
		
		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassA ca = new ClassA();
		//ca.pri = 1;
		//ca.def = 1;
		//ca.pro = 1;
		ca.pub = 1;
		
		// 객체 참조 변수를 통해 정적 멤버도 접근 가능, 권장하지는 않음
		//ca.priStatic = 1;
		//ca.defStatic = 1;
		//ca.proStatic = 1;
		ca.pubStatic = 1;
	}

}

ClassC는 ClassA의 상속을 받지 않았기 때문에 public 접근 제어자가 붙은 정적 멤버 변수에만 접근이 가능하다.


정적 멤버인 경우 객체참조변수명.정적멤버 형태로도 접근할 수 있지만 클래스명.정적멤버 형식으로 접근해야 한다고 권장하는데 바로 일관된 형식으로 접근하기 위해서이다. 또한 메모리의 물리적 접근에 따른 이유도 있다.

참고

  • 스프링 입문을 위한 자바 객체지향의 원리와 이해
profile
이것저것 관심많은 개발자.

0개의 댓글