객체지향 기본: 캡슐화

Jin Hur·2022년 5월 27일
0

SW 설계와 구현

목록 보기
3/5
post-custom-banner

캡슐화

객체의 속성과 행위를 하나로 묶고, 실제 구현 내용 일부를 외부에 감추어 은닉하는 것이다.

캡슐화는 인터페이스(interface, 버튼)와 구현(implementation, 리모콘의 복잡한 내부 로직)을 분리하는 것에서 시작한다. 내부의 구현을 분리하고, 인터페이스를 객체의 외부에 제공하여 접근할 수 있게 한다.

전자기기를 예를 들면, TV나 컴퓨터의 내부 동작 원리와 기술은 매우 복잡하다. 하지만 사용자는 그 원리를 몰라도 간단한 버튼 등과 같은 인터페이스로 해당 전자기기가 제공하는 서비스를 누릴 수 있다. 이렇게 구현과 인터페이스를 분리하면 다음과 같은 장점을 얻을 수 있다.

인터페이스-구현 분리의 장점

1. 사용하기 쉽게 해준다.
구현을 감추고 꼭 필요한 인터페이스만 유저에게 보여줌으로써 에러를 줄이고 사용을 쉽게 하는 것이다. 컴퓨터 케이스를 벗기고 컴맹을 데려와 컴퓨터를 작동시켜보라 하면 상당히 헤맬 것이다.

클래스를 설계할 때에도 마찬가지이다. 클래스에 쓸데 없는 함수들이 많고, 멤버 변수들까지 public으로 접근을 지정하면 케이스없는 컴퓨터를 설계하는 것과 같다. 멤버 변수들까지 공개를 하고, 이를 다른 클래스가 사용하게끔 하는 것은 클래스 간의 결합을 강하게 만드는 꼴이다. 여러 클래스들이 서로의 멤버 변수에 접근하고 구현 함수에 접근을 하는 상황은 본체와 모니터의 껍질을 벗겨놓고 무수히 많은 선으로 연결해 놓은 상황과 같다.

2. 객체 간의 결합을 약하게 함으로써 유지 보수의 비용을 줄인다.
만약 본체와 모니터가 수 많은 선을 통해 연결되어 있다면 모니터를 교체할 시 많은 시간을 투입하여야 하고, 난이도 또한 엄청날 것이다. 하지만 실제론 본체와 모니터는 단 하나의 케이블로만 연결되어 있기에 본체에서 케이블 한 개만 뽑아서 새로운 모니터에 꼽기만 하면 된다. 이것이 바로 유지 보수가 적다는 것이다.

인터페이스와 구현의 분리를 통한 장점으로 대표적으로 많이 드는 또 다른 예시는 바로 '자동차'이다.


source: https://velog.io/@minthug94/%EC%BA%A1%EC%8A%90%ED%99%94-Encapsulation


캡슐화 규칙을 깨는 원인

객체가 수행하는 책임이 아닌 내부에 저장할 데이터에 초점을 맞추는 것.
ex) getter와 setter 남용

캡슐화의 진정한 의미

  • 데이터 캡슐화
    • struct 생성
  • 기능 캡슐화
    • 메서드 생성
  • 관련된 데이터와 기능을 한 곳에 묶고 기능만 노출
    • 클래스 생성
    • 데이터는 private으로 지정
  • 사용자 입장에서 중요한 부분은 강조하고 중요하지 않는 부분은 숨김
    • 사용자 관점에서 중요한 메서드는 식별 후 public으로 지정
    • 나머지 메서드는 private으로 지정

캡슐화 원칙

1. Tell Don't Ask Principle

"데이터를 묻지 말고, 기능을 실행해달라고 요청"

데이터를 묻지 말고, 기능을 실행해달라고 요청하라는 것이다. 대표적으로 getter/setter의 남용은 이러한 원칙을 깨는 것이다. 객체가 수행하는 책임이 아닌 내부에 저장할 데이터에 초점을 맞추는 것은 C언어의 구조체를 사용하는 것처럼 절차지향적 코드의 방식과 다를 게 없다. 절차지향적 코드에선 (1) 정보(information)을 얻는 것과 (2) 그 정보를 바탕으로 의사 결정을 내리는 것이 분리되어 있다. 의사결정을 내리는 로직을 사용자에게 감추고, 사용자는 본인에게 필요한 것만 요구하도록 한다.

2. Law of Demeter

"모듈 간 결합도를 최소화하라"

모듈간 결합도를 최소화하라는 뜻이다. 이를 다른 표현으로 하자면 '하나의 객체가 다른 객체를 지나치게 알게 하지 말라'는 뜻이다. 객체들간의 협력 경로를 제한하면서 결합도를 낮추라는 뜻이다. '협력 경로 제한'이라는 것은 멀리 떨어져 있는 낯선 객체에 메시지를 보내지 말라는 것이고, 쉽게 말해 '.' 또는 '->'를 줄이라는 뜻이다.


캡슐화 실습

캡슐화 사용 전

// 캡슐화 사용 전
int main() {
	int rectangle1Width = 1;
	int rectangle1Height = 2;

	int rectangle2Width = 3;
	int rectangle2Height = 4;
	
    // 직사각형 둘레 길이 구하기 
	int rectangle1Perimeter = (rectangle1Width + rectangle1Height) * 2;
	int rectangle2Perimeter = (rectangle2Width + rectangle2Height) * 2;

	return 0;
}

=> 객체지향적 코드가 아니기에 재사용성이 전혀없는 코드

캡슐화 사용 후 (낮은 수준)

// 낮은 수준 캡슐화
struct Rectangle {
	int width;
	int height;
};

int widthPlusHeight(Rectangle rectangle) {
	return rectangle.width + rectangle.height;
}

int main() {
	Rectangle rec1 = { 1, 2 };
	Rectangle rec2 = { 3, 4 };
	int rec1Perimeter = widthPlusHeight(rec1) * 2;
	int rec2Perimeter = widthPlusHeight(rec2) * 2;

	return 0;
}

=> 데이터를 담을 구조와 기능 제공의 메서드가 분리되어 있기에 이 또한 재사용성에 불리하다. 변경에도 민감해진다.

캡슐화 사용 후 (중간 수준)

// 중간 수준의 캡슐화
class Rectangle {
public:
	Rectangle(int x, int y) {
		width = x;
		height = y;
	}
	
	int widthPlusHeight() {
		return width + height;
	}

private:
	int width;
	int height;
};

int main() {
	Rectangle rec1(1, 2);
	Rectangle rec2(3, 4);
	
	int rec1Perimeter = rec1.widthPlusHeight() * 2;
	int rec2Perimeter = rec2.widthPlusHeight() * 2;

	return 0;
}

캡슐화 사용 후 (높은 수준)

// 높은 수준의 캡슐화
class Rectangle {
public:
	Rectangle(int x, int y) {
		width = x;
		height = y;
	}
	
	int getPerimeter() {
		return widthPlusHeight() * 2;
	}

private:
	int width;
	int height;

	int widthPlusHeight() {
		return width + height;
	}
};

int main() {
	Rectangle rec1(1, 2);
	Rectangle rec2(3, 4);
	
	int rec1Perimeter = rec1.getPerimeter();
	int rec2Perimeter = rec2.getPerimeter();

	return 0;
}

=> 사용자가 원하는 요구는 직사각형의 둘레 길이를 구하는 것이다. 따라서 둘레 길이를 구하는 과정 일부에 속하는 '가로+세로 길이' 또한 구현 내부에 감추어 둘레 길이를 바로 제공하도록 인터페이스를 만든다.

post-custom-banner

0개의 댓글