객체지향 프로그래밍에서는 클래스 내부 변수를 private 으로 선언하여 외부에 노출하지 않는 것을 선호합니다. 또한 무의미한 setter 와 getter 는 객체를 객체가 아닌 단순한 데이터 Sturucture 로 간주해 버릴 수도 있으니, 신중하게 외부로 내부의 속성을 노출하라고 말합니다. 그 이유는 무엇일까요?
아래의 클래스를 한번 봅시다.
public class Point {
public double x;
public double y;
}
이렇게 했을때 문제는 무엇일까요? 문제는 외부에서 Point Class 의 Property 에 직접적으로 붙을 수 있다는 것입니다.
예를 들면 아래와 같이 말이죠.
Point point = new Point();
point.x = 10;
point.y = 20;
위와 외부에서 사용하게 되는데, 이렇게 되면 아래와 같은 단점이 생기게 됩니다.
1. 외부에서 객체 내부의 property 를 알아야 합니다.
2. 클래스가 데이터 상자 처럼 이용됩니다.
3. 여러 특성의 Point 들이 있으면 어떻게 쉽게 바꿀 수 없다.
그럼 첫번째 문제점이 어떤 문제점을 야기할 수 있었을까요? 객체 내부를 알게 되면, 객체 내부의 직접적인 결합도를 가지게 됩니다. 그래서 객체 내부의 변수들이 추가되거나 바뀌게 되면 외부에서도 직접적인 관계를 가지고 있기에 변화의 영향으로 외부 코드도 변경되게 됩니다. 예를 들면 아래와 같습니다.
예를 들면 아래와 같이 point 의 좌표들을 더한 합을 알려주는 프로세스가 있다고 해봅시다.
System.out.println(point.x + point.y);
저 함수는 point.x, point.y 를 더하는 함수입니다. 만약 point 내부에서 x 를 c 로 바꾸게 된다면 어떻게 될까요?
위의 함수는 오류를 내 뱉을 것입니다. 그리고 또한 직접적으로 사용하고 있는 모든 프로세스들에서 오류가 날 것입니다.
그렇다면 이 코드를 어떻게 좋게 바꿀 수 있을까요?
아까 위에서 말했듯 저 함수는 point.x, point.y 를 더하는 함수입니다.
여기서 가장 중요한 문장은 "모든 property 변수 값을 더해주는 함수" 라는 것입니다.
우리는 객체 내부와 외부의 소통 수단을 인터페이스라고 해봅시다. 외부에서는 객체가 이런 행위를 할 것이야 라는 생각을 하고, 해당 객체와 소통을 하는 것이죠. 그렇다면 위의 객체가 외부의 객체에게 해줘야 할 말은 "나는 이 변수들을 모두 더한거야" 라고 전해주는 것입니다.
그럼 Point 클래스에 아래와 같은 인터페이스를 추가해봅시다.
public class Point {
public double x;
public double y;
public double sumAllPoint() {
return this.x + this.y;
}
}
그럼 외부에서는 이 인터페이스를 받아서 아래와 같이 아까의 코드를 고칠 수 있을 것입니다.
System.out.println(point.sumAllPoint());
이렇게 된다면 내부 에서 x 를 c 로 바꾸거나 d 로 바꾼다고 해도 문제가 일어나지 않을 것입니다. 이렇게 고쳤음에도 불구하고 문제가 남아있습니다. 어떤 문제일까요?
누군가는 저 속성으로 붙을 수 있다는 문제입니다.
예를 들면 누군가는 저 sumAllPoint() 를 가져다 쓰는데, 누군가는 아까전처럼 point.x + point.y 를 하고 있을 수도 있다는 뜻 입니다.
그래서 우리에겐 이제 객체를 데이터 상자가 아닌, 외부와 소통할 수 있는 하나의 존재로써 각인 시켜줄 이유가 있습니다.
따라서 외부에 직접적으로 붙지 못하도록 아래와 같이 수정해봅시다.
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double sumAllPoint() {
return this.x + this.y;
}
}
위와 같이 수정해 주었습니다. 이제 외부에서 point.x 같은 것을 쓸수 없게 되었습니다.
이제 그 누구도 이 클래스를 쓴다면, .x 와 같은 직접적인 연결이 불가능하게 되었죠. 이와 같은 과정을 캡슐화 라고 합니다.
객체 내부에 속성들이 들어있고, 외부에서 이 값을 알기 위해서는 이 객체에게 요청을 해야하는 것이죠.
우리가 캡슐을 뽑기 위해서 캡슐뽑기를 돌리듯이, 캡슐 뽑기를 통해서 캡슐을 얻어야만 합니다.
캡슐화를 통해서 얻을 수 있는 장점은 아래와 같습니다.
생각해보면, 우리가 캡슐을 뽑을때, 캡슐이 캡슐뽑기로 부터 나올때까지 어떤 과정을 통해서 나오는지 알고 계신가요? 이렇듯이, 우리도 객체 내부에서 어떤 과정을 통해 우리에게 값을 전달해주는지 모릅니다. 다만 우리가 아는 것은, 이걸 통해서 캡슐을 얻을 수 있고, 이 객체는 나에게 이걸 전달해 줄거야 라는 것을 캡슐에 돈을 넣고 돌리는 행위를 통해서 얻을 수 있다는 것을 알고 있을 뿐입니다.
따라서 객체의 캡슐화 또한 외부에서 객체가 어떻게 이값을 주는지는 알 수 없으나, 객체의 해당 인터페이스(행위)를 이용하면 이 객체가 나에게 이 값을 전달해 줄거야 라는 것을 알 수 있다는 것입니다. 따라서 외부에서는 객체 내부의 프로세스에 대한 직접적인 연결도가 사라지게 됩니다.
또한 만약 하나의 행위가 수정되야 한다면, 객체 내부에서만 변경하면 됩니다. 아까와 같은 경우에는 point.x + point.y 는 결국 point 안의 좌표를 더하는 행위인데, 이런 행위들이 흩어져 있었을 것입니다. 즉 객체를 단순 데이터를 담아두는 상자로만 인식했기에 벌어지는 일이죠. 저런 코드는 하나의 행위가 수정될때 여러 부분들을 수정해야 합니다.
우리는 위의 과정을 통해서 처음 단점으로 설명한 1번과 2번의 단점을 해소하였습니다. 그렇다면 3번 단점은 어떻게 해결할까요?
바로 아래와 같은 방법으로 해결가능합니다.
예를들면, Point 좌표는 반드시 모든 값을 더하는 기능이 있어야 해! 라고 우리끼리 정의해보았다고 합시다.
그렇다면 우리는 Point 객체를 만들기 위해서 설계도를 만들어야 할 것입니다. 근데 아직 세부적으로 추가될 기능들은 모르겠으나, 일단 저 기능은 만족해야해 라고 해봅시다. 그렇다면 설계도를 만들기 위해서 자바의 interface 를 사용해 봅시다. 아래와 같이 말이죠.
public interface Point {
double sumAllPoint();
}
그럼 우리의 극좌표 클래스는 아래와 같이 구성될 것입니다.
public class PolarPoint implements Point {
private double x;
private double y;
public PolarPoint(double x, double y) {
this.x = x;
this.y = y;
}
public double sumAllPoint() {
return this.x + this.y;
}
}
그러면 다른 3개의 좌표를 가진 APoint 를 만들어야 한다고 해봅시다. 우리는 어떻게 만들 수 있을까요?
public class APoint implements Point{
private double x;
private double y;
private double z;
public APoint(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public double sumAllPoint() {
return this.x + this.y + this.z;
}
}
이렇게 만들 수 있습니다. 이렇게 해서 우리가 어떤 이점을 얻을 수 있을까요?
하나의 구체적인 예시를 보시죠. 예를 들면 외부에서 나는 어떤 Point 건 상관이 없으니, 아무나 들어와서 나랑 일하자! 라고 말했다고 해봅시다. 그럼 아래와 같은 코드가 될것입니다.
public static void main(String[] args) {
Point point = new PolarPoint(10, 20);
System.out.println(point.sumAllPoint());
}
또한 PolarPoint 말고 APoint 도 가능하겠죠.
public static void main(String[] args) {
Point point = new APoint(10, 20, 30);
System.out.println(point.sumAllPoint());
}
이것이 가능한 이유는 무엇일까요? 바로 외부에서 객체 내부에 직접적으로 연관되지 않고, 객체가 이런 일을 할것이야 라는 행위에 의존하여 객체와 소통하려했기 때문입니다. 즉 이 Point 객체들은 sumAllPoint() 를 가지고 있어서 나랑 소통할 수 있어 라는 하나의 기대감을 가질 수 있었기 때문입니다.
이로서 우리는 3번째 문제도 해결하게 되었습니다.
우리는 위의 과정을 통해서 단순한 데이터 자료구조를 벗어나, 하나의 소통을 하는 존재인 객체로써 Point 클래스를 바꿀 수 있었습니다.
'Java' 카테고리의 다른 글
Java Stream (0) | 2021.11.11 |
---|---|
젠킨스 JVM 메모리 설정 (0) | 2021.10.20 |
Switch 문 반복을 없애기 (0) | 2021.10.07 |
JVM 구조 (0) | 2021.10.07 |
[M1] Apache Jmeter로 Socket Server Test 하기 (0) | 2021.09.24 |