캡슐화

2020. 5. 23. 18:36정리/JAVA


캡슐화란?

캡슐화(Encapsulation)이란 객체 지향 프로그래밍의 특징 중 하나로, 

어떠한 목적을 수행하기 위해 필요한 데이터의 구조(필드)와 데이터를 다루는 행위(메소드)를 한 곳에 응집시켜 놓은 뒤 이에 대한 내부 로직은 정보 은닉을 통해 감추는 객체 지향 설계 원칙이다. 

응집도를 높여주고 결합도를 낮춰주는 객체 지향 설계 원칙이다.

 

객체 지향 프로그래밍의 장점 중 하나인 유지보수 및 디버깅이 편리하다는 장점은 

응집도가 높기 때문에 하나의 목적을 수행하는 코드가 뭉쳐 있으므로 해당 구간에 대한 분석이 쉬우며 

결합도가 낮기 때문에 서로 다른 목적을 수행하는 코드가 서로에게 영향을 끼치는 부분을 최소화해 특정 기능의 수정이 필요할 경우 그 기능을 수행하는 코드만을 수정하면 되며, 디버깅 시에도 오류가 발생한 기능을 구현한 코드만을 살펴보면 되기 때문에 간편하기 때문에 등장한 장점이다. 

 

그렇기 때문에 캡슐화가 제대로 이루어지지 않은 프로그램의 경우 서로 다른 목적을 수행하는 코드가 서로에게 영향을 끼치게 되어 결합도가 상승해 위에서 설명한 유지보수 및 디버깅에 관련된 장점을 하나도 누릴 수 없기 때문에 객체 지향 프로그래밍으로 설계한 의미가 없어진다.  

 

이론은 이렇지만, 단순히 글로써 설명하면 크게 와닿지 않기 때문에 예제를 통해 이해하고자 했다.

 


결합도가 낮은 코드

 

결합도가 낮은 코드의 예시로 먼저 인터페이스를 사용했다. 

인터페이스는 추상화가 적용된 개념으로, 추후 설명해야 할 듯 싶다.

간단하게 설명하자면 여러 객체들의 공통적이고 핵심적인 기능을 추출하고 불필요한 부분은 제거해 일반화시킨, 

동일한 목적 하에 동일한 기능을 수행하게끔 강제하는 기능이다. 

 

이해할 때 살짝 억지스럽기는 하지만 그대로 현실에 비유해서 하는게 제일 도움이 되었다. 

 

예를 들어, 어떤 사람이 글을 작성한다고 가정하자.

그 사람의 직업에 따라 글을 작성하는 것이 달라질 것이다.

학생이라면 과제, 기자라면 기사, 직장인이라면 서류, 소설 작가라면 소설 등등. 

 

public class Novelist {
	public void writeNovel() {
		System.out.println("소설가가 소설을 작성합니다.");
	}
}

 

public class Reporter {
	public void writeArticle() {
		System.out.println("기자가 기사를 작성합니다.");
	}
}

 

public class Student {
	public void writeProject() {
		System.out.println("학생이 과제를 작성합니다.");
	}
}

 

이렇게 각각 소설가 / 기자 / 학생의 클래스가 있을 경우, 소설가가 글을 집필하는 것을 표현하려면 

 

public class Tests {
	public static void main(String[] args) {
		Novelist target = new Novelist();
		
		target.writeNovel();
	}
}

이런 식으로 소설가에 해당하는 클래스를 인스턴스로 생성하고, 그 안에 있는 writeNovel() 메소드를 호출한다.

 

여기서 소설가 대신 학생이 과제를 작성하는 것을 표현하고자 한다면 

 

public class Tests {
	public static void main(String[] args) {
		/*
		Novelist target = new Novelist();
        
		target.writeNovel();
		*/
        
		Student target = new Student();
		
		target.writeProject();
	}
}

코드는 이런 식이 될것이다.

위의 코드는 모든 코드가 수정되어야 한다.

 

이를 개선하기 위해 소설가 / 기자 / 학생 클래스에 추상화를 적용시킨다면, 아래와 같을 것이다. 

 

public interface writer {
	public void write();
}

 

세 개의 클래스의 공통적이고 핵심적인 기능, 쓰는 행위를 write라는 메소드로 따로 인터페이스로 추출해냈다.

 

이 인터페이스를 구현해 다시 클래스를 정의한다면, 아래와 같이 정의할 수 있다.

 

public class Novelist implements writer {
    @Override
    public void write() {
    	System.out.println("소설가가 소설을 작성합니다.");
    }
}

 

public class Student implements writer {
    @Override
    public void write() {
    	System.out.println("학생이 과제를 작성합니다.");
    }
}

 

public class Reporter implements writer {
    @Override
    public void write() {
    	System.out.println("기자가 기사를 작성합니다.");
    }
}

 

그리고 소설가 / 학생 / 기자가 글을 작성하는 것을 표현하기 위한 코드는 아래와 같다.

 

public class Tests {
	public static void main(String[] args) {
    	Writer target = new Novelist();
        // Writer target = new Student();
        // Writer target = new Reporter();
        
        target.write();
    }
}

 

Writer라는 타입으로 생성된 target은 어떠한 클래스를 생성했던지 상관없이 writer()라는 메소드로 해당 클래스를 다룰 수 있게 되었다. 

 

주석으로 친 것과 같이 다른 클래스를 생성한다고 하더라도, target.write()는 코드의 수정이 필요하지 않다.

즉 target 인스턴스를 생성하는 코드를 제외한다면, 어떠한 클래스를 생성하던 target.write()에 영향을 미치지 못하므로 결합도가 낮은 코드가 된 것이라고 볼 수 있다. 

 


정보 은닉

 

정보 은닉(Imformation hiding)이란 외부에서 알 필요 없는 정보를 직접 접근 및 제어할 수 없도록 하는 것으로, 접근 제어자(Access Modifier)를 통해 접근 범위를 설정할 수 있는 기능을 의미한다.

 

이 정보은닉을 통해 얻을 수 있는 장점은 다음과 같다.

  • 필요 이상의 메소드나 필드가 외부에 노출되지 않기 때문에 보안이 강화된다.
  • 외부에서 내부로직에 직접 접근할 수 없기 때문에 데이터의 신뢰성이 증가한다.
  • 외부(다른 개발자)에서 해당 기능을 사용하려면 복잡한 내부로직을 알 필요 없이 해당 기능을 사용할 수 있는 방법만을 알면 되므로 개발이 편리해진다.

여기서 첫 번째와 두 번째 장점은 비슷하다고 생각된다.

 

public class Student {
	
    private String password = "1234";
    
    private void setting() {
    	System.out.println("학생이 과제를 작성하기 위해 PC를 켭니다.");
        System.out.println("학생이 비밀번호 " + password + "를 입력합니다.");
    }

    public void write() {
    	setting();
    	System.out.println("학생이 과제를 작성합니다.");
    }
}

위에서 예시로 들었던 학생 클래스에서, 학생이 과제를 하기 위해 사전 준비가 필요하다고 하자.

 

write() 메소드를 호출하게 되면 무조건 같은 클래스 내부에 있는 setting() 메소드를 호출하게 된다.

setting() 메소드에서는 필드 password를 사용한다. 

 

public class Tests {
	public static void main(String[] args) {
        Student target = new Student();
		
        System.out.println(target.password); // private 접근제어자 때문에 불가능
        target.password = "7777"; // private 접근제어자 때문에 불가능
        target.setting(); // private 접근제어자 때문에 불가능
        
        target.write();
    }
}

 

이 경우 setting() 메소드와 password 필드 모두 접근제어자가 private이기 때문에

password의 경우 외부에서 확인할 수 없고, 수정할 수도 없다.

setting() 메소드 또한 외부에서 호출할 수 없다.

 

또한 target.write()를 호출하면 이전 단계로 인해 setting() 메소드가 호출되며, setting() 메소드에서는 password 필드를 호출하는데 호출하는 시점에서는 이에 대해 전혀 알지 못하더라도 필요한 기능은 그대로 사용할 수 있다. 

 

그렇기 때문에 데이터가 개발자의 의도와 다르게 변경되는 것을 막을 수 있어 데이터의 신뢰성이 증가하고, 필드나 메소드를 외부에서 접근할 수 없기 때문에 보안이 강화되며, 외부에서 해당 기능을 사용할 때 내부로직을 몰라도 되므로 개발이 편리하다. 

 

 

정리

  • 캡슐화는 응집도를 높이고 결합도를 낮추는 객체 지향 설계 원리이다.
  • 결합도를 낮춤으로써 유지보수가 편리해진다. 
  • 정보 은닉을 통해 데이터의 신뢰성, 보안 강화, 개발 시 편리하다는 장점을 얻을 수 있다.

 


해당 글은 개인이 공부하면서 정리한 글이기 때문에 정확하지 않은 내용이 있을 수 있습니다.

'정리 > JAVA' 카테고리의 다른 글

인터페이스  (0) 2020.06.02
다형성  (0) 2020.05.27
상속  (0) 2020.05.25
추상화  (0) 2020.05.24
객체지향과 절차지향  (0) 2020.05.22