일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 네트워크 기초
- WSL #Linux #ubuntu
- 백엔드로드맵
- OOP
- 사용자계정 #WSL2
- jvm
- Java환경
- 에러해결
- 회고록
- 피드백
- 정리글 작성법
- 롤중독
- 코드스테이츠 #
- mapstruct
- 회고
- 시작 #꾸준히 #과하지않게
- String
- git #intellij
- JAVA기본
- 글 foramt
- WSL2 #권한
- 회고록_코드스테이츠
- 회고 #주간회고
- 코딩테스트
- 공부방식
- 인터넷 #ISP
- 호스트주소 #Host주소
- 코드스테이츠 #회고록
- 몰입학습 #몰입
- Git #회고록
- Today
- Total
느리더라도 꾸준히
[OOP]캡슐화에 대해서 세부적으로 알아보자. 본문
목표
객체지향 프로그래밍의 특징중 하나인 캡슐화를 사용하는 목적과 사용방식을 이해한다.
정리
캡슐화를 사용한 소스코드들은 구현한 내부 객체의 속성(필드)와 기능(메서드) 일부를 외부가 보지 못하도록 은닉해준다. 그러므로 코드의 낭비를 줄여주고 유지보수를 용이하게 만든다. 캡슐화를 습관화하여 코드를 작성시 더욱 객체 지향적인 코드가 완성 시키자.
- 필드 관점 : 외부에서 값을 변경하지 못하도록 private 접근제어자를 사용한다.
- 메서드 관점 : Tell don’t ask 법칙을 지키자. 즉 한 객체에서 사용된 메서드가 어떤 로직을 가지고 있는지 알지 못하도록 내부로직에서 사용될 메서드는 private로 로직을 끝내고 출력할 메서드만 public으로 선언하여 다른 객체에서 호출할수 있도록 만들어주자.
본문
캡슐화의 정의
캡슐화란 특정 객체 안에 관련된 속성과 기능을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것을 말한다.
객체지향의 관점으로는 구현한 내부 객체의 속성(필드)와 기능(메서드) 일부를 외부가 보지 못하도록 감추어 은닉하고 유지보수를 용이하게 만든다.
- 조금 더 세부적으로 객체의 내부 필드를 은닉하는 것은 은닉화 라고한다.
- 즉, 캡슐화는 은닉화를 포함하며, 메서드의 일부도 외부가 보지 못하도록 은닉하는 것이다.
“이를 캡슐화를 통해 객체의 (1)응집도가 높아지고 (2)결합도가 낮아진다.” 라고 표현하기도 한다.
지금은 이해 하기 좀 어렵다. 캡슐화의 목적과 원칙을 보고 다시 정의를 보도록 하자.
캡슐화의 두가지 목적
1. 외부로부터 객체의 속성과 기능이 함부로 변경되지 못하게 막는다.
아래 코드를 보자.
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);
//w.name = "최코딩"; // 불가능
}
}
class Worker {
private String name; // 변수의 은닉화. 외부로부터 접근 불가
private int age;
private int id;
public void setName(String name){
this.name = name;
}
public void getName(){
return name;
}
...(getter & setter)
}
// 출력값
근로자의 이름은 김코딩
근로자의 나이는 30
근로자의 ID는 5
외부 클래스인 GetterSetterTest 입장에서 Worker의 필드(속성)들이 private 이므로 “w.name = “최코딩”; 은 동작하지 못한다. 즉 실수로 직접 해당 필드에 값을 넣을 경우를 막을 수 있다.
그리고 외부 클래스(GetterSetterTest)는 Worker의 메서드인 setName(), setAge(), setId() 가(getter도 마찬가지) 어떻게 구현되어 있는지 알 지도 못하며, 변경도 불가능하다.
만약 Worker 클래스에서 setName(), setAge(), setId()의 접근제한자를 private로 설정하고 아래 configure 메서드에 모아 놓는다면, 더 구체적인 캡슐화를 작업할 수 있다.
class Worker{
private void setName(String name){
this.name = name;
}
private void setAge(String name){
this.age = age;
}
private void setId(String name){
this.Id = Id;
}
//option
public void configure(String name, int age, int id){
setName(name);
setAge(age);
setId(id);
}
}
----------------------
// (GetterSetterTest - main 함수속)
Worker w = new Worker();
w.configure("김코딩", 30, 5);
GetterSetterTest 에서는 configure() 내부의 메서드 로직을 알지 못하고, 변경할 수도 없다.
2. 내부에서 데이터가 변경되더라도 불필요한 외부로의 노출을 최대로 막는다.
이는 다른 객체에 영향을 주지 않기에 객체들이 독립성을 확보할 수 있다.
이 내용이 조금 와 닿지 않는다. 예시를 통해 이해해 보자.
class Seller {
private int iphonePrice;
private int galaxyPrice;
public void setIphonePrice(int iphonePrice) {
this.iphonePrice = iphonePrice;
}
public int getGalaxyPrice() {
return galaxyPrice;
}
public void setGalaxyPrice(int galaxyPrice) {
this.galaxyPrice = galaxyPrice;
}
public int getIphonePrice() {
return iphonePrice;
}
}
Seller 클래스는 iphonePrice 와 galaxyPrice를 가지고 있다.
아이폰은 20% 할인, 갤럭시는 10% 할인가로 판매자가 판매한다고 하자.
캡슐화를 적용하지 않은 코드
public class Main {
public static void main(String[] args) {
// 판매자는 아이폰과 갤럭시 폰의 가격을 100만원으로 설정했다.
Seller seller = new Seller();
seller.setIphonePrice(1000000);
seller.setGalaxyPrice(1000000);
double IphoneSellPrice = seller.getIphonePrice() * 0.9;
double GalaxySellingPrice = seller.getGalaxyPrice() * 0.8;
System.out.println("아이폰 판매 가격은 : "+ IphoneSellPrice); // 900000.0
System.out.println("갤럭시 판매 가격은 : "+ GalaxySellingPrice); // 800000.0
}
}
위 코드는 Seller 클래스를 기준으로 캡슐화를 적용하지 않은 코드이다. 할인된 가격을 설정하는 로직은 판매자가 해야 하는 역할이지만, 외부에 불필요한 로직 정보가 노출되어있다.
캡슐화를 적용시켜준 코드
class Seller {
private int iphonePrice;
private int galaxyPrice;
public void setIphonePrice(int iphonePrice) {
this.iphonePrice = iphonePrice;
}
public int getGalaxyPrice() {
return galaxyPrice * 0.8; // 수정
}
public void setGalaxyPrice(int galaxyPrice) {
this.galaxyPrice = galaxyPrice;
}
public int getIphonePrice() {
return iphonePrice * 0.9; // 수정
}
}
public class Main {
public static void main(String[] args) {
// 판매자는 아이폰과 갤럭시 폰의 가격을 100만원으로 설정했다.
Seller seller = new Seller();
seller.setIphonePrice(1000000);
seller.setGalaxyPrice(1000000);
double IphoneSellPrice = seller.getIphonePrice();
double GalaxySellingPrice = seller.getGalaxyPrice();
System.out.println("아이폰 판매 가격은 : "+ IphoneSellPrice); // 900000.0
System.out.println("갤럭시 판매 가격은 : "+ GalaxySellingPrice); // 800000.0
}
}
할인율이 Seller 클래스 안에 들어가있다.
만약 할인율이 변경된다면, 이제는 Seller 클래스만 수정해주면 된다. 즉 Main class는 Seller class에 의존하지 않고 독립적인 객체가 된 것이다.
캡슐화로 코드중복을 막을 수 있다.
지금은 Seller와 상호 작용하는 객체가 Main class 한개이므로, 이 결과만 보면 큰 차이가 나지 않는다고 생각할 수 있다. 만약 Seller 객체를 받아와서 이 할인된 가격에 여러가지 Event가 적용 된다고 가정해보자.
public class Event {
//카드할인
public double IphoneCardEvent(Seller seller){
// double IphoneSellPrice = seller.getIphonePrice() * 0.9; 캡슐화를 적용하지 않음
double IphoneSellPrice = seller.getIphonePrice();
double EventPrice = IphoneSellPrice * 0.95;
return EventPrice;
}
//카드할인
public double GalaxyCardEvent(Seller seller){
//double GalaxySellPrice = seller.getGalaxyPrice()*0.8; 캡슐화를 적용하지 않음
double GalaxySellPrice = seller.getGalaxyPrice();
double EventPrice = GalaxySellPrice * 0.95;
return EventPrice;
}
public double IphonePointEvent(Seller seller){
//double IphoneSellPrice = seller.getIphonePrice()*0.9; 캡슐화를 적용하지 않음
double IphoneSellPrice = seller.getIphonePrice();
double EventPrice = IphoneSellPrice - 5000;
return EventPrice;
}
public double GalaxyPointEvent(Seller seller){
//double IphoneSellPrice = seller.getIphonePrice()*0.8; 캡슐화를 적용하지 않음
double GalaxySellPrice= seller.getGalaxyPrice();
double EventPrice = GalaxySellPrice - 10000;
return EventPrice;
}
}
public class Main {
public static void main(String[] args) {
// 판매자는 아이폰과 갤럭시 폰의 가격을 100만원으로 설정했다.
Seller seller = new Seller();
seller.setIphonePrice(1000000);
seller.setGalaxyPrice(1000000);
Event event = new Event();
double IphoneSellPrice = event.IphonePointEvent(seller); // 5000원 할인
double GalaxySellPrice = event.GalaxyPointEvent(seller); // 10000원 추가할인
System.out.println("아이폰 판매 가격은 : "+ IphoneSellPrice); // 895000.0
System.out.println("갤럭시 판매 가격은 : "+ GalaxySellPrice); // 790000.0
}
}
포인트 이벤트를 통해 할인가에서 아이폰은 5000원, 갤럭시는 10000원을 빼주기로 했다.
Event class는 캡슐화가 적용된 코드이다. 만약 캡슐화를 적용하지 않았다면 Seller의 getGalaxyPrice() 값을 이용해, 할인 로직을 계산해야한다. 즉 코드 중복이 발생하는 것이다.
지금은 getIphonePrice* 0.9 같은 간단한 할인 로직 이지만, Seller 클래스 밖에서 getIphonePrice()에 매우 복잡한 로직을 적용한다고 상상해보면 수많은 코드 중복을 해주어야 할 것이다.
if() {(seller.getIphonePrice() - 3000) * 0.9 } // 캡슐화시 seller.getDisIphonePrice()
else if() {(seller.getIphonePrice() - 5000) * 0.9 } // seller.getDisIphonePrice()
...
아니면 이런 로직을 Seller 클래스에서 선언하면 캡슐화를 통해 더욱 코드 중복을 막을수 있을 것이다.
캡슐화를 향상시켜주는 두 가지 규칙
Tell don’t ask
위처럼 호출할 메서드를 요청만 하고 메서드의 구현로직을 몰라도 되도록 작성하는 캡슐화의 규칙을 “Tell don’t ask” 라고 부른다. 쉽게 말하자면 “데이터 달라하지 말고 해달라고하기” 라고 할 수 있다.
절차 지향 코드
// member.getExpiryDate() -> 만료 일자 데이터를 가져옴
if (member.getExpiryDate() ! null &&
member.getExpiryDate().getDate < System.currentTimeMillis()) {
// 만료 되었을 때 처리
}
member의 타입에 캡슐화 적용 후
if (member.isExpired()) {
// 만료 되었을 때 처리
}
디미터 법칙
Tell don’t ask 규칙을 잘 따를수 있도록 도와주는 디미터 법칙도 존재한다. 디미터 법칙은 아래 규칙을 따른다.
- 메서드에서 생성한 객체의 메소드만 호출
- 파라미터로 받은 객체의 메소드만 호출
- 필드로 참조하는 객체의 메소드만 호출
디미터 법칙 위반
public void processSome(Member member) {
if (member.getDate().getTime() < ...) { // 디미터 법칙 위반
....
}
else if (member.getDate().getTime() < ...)
else if (member.getDate().getTime() < ...)
}
만약 내부에서 getDate()의 타입이 바뀌어 getTime()을 호출할 수 없고, 위 소스코드처럼 많은 코드를 작성했다면 코드 중복 뿐만 아니라 수정하는데 많은 시간이 필요할 것이다.
디미터 법칙 적용
public void processSome(Member member) {
if (member.someMethod() < ...) { //
....
}
else if (member.someMethod() < ...) {
else if (member.someMethod() < ...) {
}
public class Member {
public Time someMethod() {
return getDate().getTime();
}
}
getDate()의 타입이 바뀌어 문제 상황 발생 시, someMethod만 해결해 주면 된다.
결국 절차 지향의 데이터 중심이 아닌 기능(메서드) 중심으로 코드 작성을 유도 하므로 캡슐화가 향상된다.
단어 설명
(1)응집도(자율도)
응집도는 모듈 내부의 기능적인 응집 정도를 나타낸다.
(2)결합도
어떤 모듈이 다른 모듈에 의존하는 정도를 나타내는 것이다. 객체지향관점에서는 어떤 객체(인스턴스)가 다른 인스턴스에 의존(해당 객체의 속성이나 메서드를 사용)하는 정도로 생각하면 된다.
응집도는 높을수록 좋고 결합도는 낮을수록 이상적인 객체지향 코드이다.
출처
https://bperhaps.tistory.com/entry/캡슐화란-무엇인가-어떤-이점이-있는가
https://dev-monkey-dugi.tistory.com/86#3. 신문 배달부와 지갑
'Java' 카테고리의 다른 글
[Java기본]가상 함수(메서드)란? (0) | 2022.11.15 |
---|---|
[JVM]정적 바인딩과 동적 바인딩 (0) | 2022.11.15 |
[Java기본]static 메서드는 왜 필요할까? (0) | 2022.11.06 |
[JVM]변수 선언과 할당시 관점으로 JVM 동작 이해하기 (0) | 2022.11.04 |
[Java기본]while문속의 switch문과 break; (0) | 2022.11.03 |