2023.08.02 WED
17p ~ 33p
2일차 내용 ⬇️
2023.08.02 - [개발 서적 기록/오브젝트_조영호] - 2일차 - 객체 지향적인 모듈
2일차 - 객체 지향적인 모듈
2023.08.01 TUE 2p ~ 16p 1일차 내용 ⬇️ 2023.07.31 - [개발 서적 기록/오브젝트_조영호] - 1일차 - 오브젝트를 본격적으로 읽기 전에 1일차 - 오브젝트를 본격적으로 읽기 전에 2023.07.31 MON 전반적인 내용
magenta-ming.tistory.com
객체에게 자유를, 캡슐화
각 객체가, 도메인이, 서로의 세세한 부분까지 알게되면, 의존성과 결합도가 증가한다.
따라서, 각각의 도메인이 각자의 행위를 수행할 수 있도록, 자율적인 존재로 만들어야한다.
이를 통해, 변경에 취약하지 않게 개선할 수 있다.
그럼 정확히 자유를 준다는 것은 어떻게 만들 수 있을까?
간단하다. 서로의 세세한 부분을 모르게하고, 그 세세한 부분은 객체가 직접 실행할 수 있는 자유를 주는 것이다.
이것이 캡슐화다.
캡슐화는 개념적이나 물리적으로 객체 내부의 세부적인 사항을 감추는 것이다.
캡슐화를 통해서 객체 내부로 다른 객체가 접근할 수 없게 제한하게되면, 결합도는 낮아지고, 더 쉽게 코드를 변경할 수 있는 구조가 된다.
지난번 예제를 개선해보면서 캡슐화를 적용해보자.
아래는 Theater 클래스이다. 현재 Theater는 Audience와 TicketSeller, Audience 소유의 Bag, TicketSeller가 근무하는 TicketOffice까지 마음대로 접근할 수 있다.
Theater가 Audience, TicketSeller, Bag, TicketOffice 의 세세한 부분까지 알고 있는 것이다.
public class Theater {
private TicketSeller ticketSeller;
public Theater(TicketSeller ticketSeller) {
this.ticketSeller = ticketSeller;
}
public void enter(Audience audience){
if(audience.getBag().hasInvitation()){
Ticket ticket = ticketSeller.getTicketOffice().getTicket();
audience.getBag().setTicket(ticket);
}
else{
Ticket ticket = ticketSeller.getTicketOffice().getTicket();
audience.getBag().minusAmount(ticket.getFee());
ticketSeller.getTicketOffice().plusAmount(ticket.getFee());
audience.getBag().setTicket(ticket);
}
}
}
따라서, Audience, TicketSeller가 Bag, TicketOffice를 처리할 수 있도록 자유를 주자.
public class TicketSeller {
private TicketOffice ticketOffice;
public TicketSeller(TicketOffice ticketOffice) {
this.ticketOffice = ticketOffice;
}
public void sellTo(Audience audience) {
if(audience.getBag().hasInvitation()) {
Ticket ticket = ticketOffice.getTicket();
audience.getBag().setTicket(ticket);
} else {
Ticket ticket = ticketOffice.getTicket();
audience.getBag().minusAmount(ticket.getFee());
ticketOffice.plusAmount(ticket.getFee());
audience.getBag().setTicket(ticket);
}
}
}
public class Theater {
private TicketSeller ticketSeller;
public Theater(TicketSeller ticketSeller) {
this.ticketSeller = ticketSeller;
}
public void enter(Audience audience) {
ticketSeller.sellTo(audience);
}
}
이제, TicketOffice에 대한 접근은 TicketSeller만이 할 수 있다.
Theater는 더이상 전처럼 'ticketSeller.getTicketOffice()'이라던지로 접근할 수 없다.
TicketSeller를 캡슐화 함으로써, Theater는 더이상 TicketSeller에 대한 내부 구현을 알지 못한다.
TicketSeller 내의 비즈니스 로직이 바뀌더라도, Bag의 비즈니스 로직이 바뀌더라도 등등 Theater는 변경할 필요가 없다.
이로써, Theater를 변경에 취약하지 않은 코드로 만들었다.
인터페이스를 통한 캡슐화
TicketSeller를 캡슐화할때 어떻게 개선한걸까? 인터페이스를 이용했다.
TicketSeller를 캡슐화하면서, Theater는 TicketSeller의 인터페이스에 의존하게 되었다.
TicketSeller 내부에 TicketOffice를 포함하고 있고, 내부에서 티켓 금액을 차감하고 등등 모두 TicketSeller에 대한 구현이므로, Theater는 이 내부 구현을 알 수 없다.
Theater는 인터페이스에 명세된 내용만 알고 있다. '이 TicketSeller는 판매하는 행위를 할 수 있다' 만 알게 된다.
이렇게, 객체를 인터페이스와 구현으로 나누고, 인터페이스만을 공개하여 캡슐화해, 객체 간 결합도는 낮추고, 변경하기 쉬운 코드로 작성할 수 있다.
자율성을 높이면, 이해하기도 변경하기도 쉽다
캡슐화 등으로 자율성을 높이면, 변경하기 쉬운 코드를 만들 수 있다.
그리고 동시에, 이해햐기 쉬운 코드로도 만들 수 있다.
이전 게시글에서, 소극장에서 티켓을 예매하는 기능을 수행하는 예제는 원래 아래와 같은 흐름이었다.
소극장은 관람객의 가방을 열어, 그 안에 초대장이 들어 있는지 살펴본다.
가방 안에 초대장이 들어있으면 판매원을 매표소에 보관되어 있는 티켓을 관람객의 가방 안으로 옮긴다.
가방 안에 초대장이 들어 있지 않다면, 관람객의 가방에서 티켓 금액만큼의 현금을 꺼내, 매표소에 적립한 후에, 매표소에 보관되어 있는 티켓을 관람객의 가방 안으로 옮긴다.
그리고 아래와 같은 이유로, 예상에서 벗어낫기에 잘못된 소프트웨어 모듈로 간주하기로 했다.
- 소극장은 관람객의 허락없이, 직접 가방을 열어보지 않아야한다.
- 소극장은 판매원의 허락없이, 직접 매표소의 티켓과 현금에 접근, 관리하지 않아야한다.
이 잘못된 소프트웨어 모듈은 우리가 실생활에서 이해하기 어려운 흐름이다.
여기서, Theater와 Audience, TicketSeller가 Bag, TicketOffice 간에 자율성을 높인다면, 캡슐화 등으로 세부 구현을 모르도록 하게되고, 우리가 이해할 수 있는 흐름으로 변한다.
TicketSeller을 캡슐화해서, TicketSeller가 직접 매표소의 티켓과 현금에 접근, 관리할 수 있으므로, 이해하기 쉬운 흐름으로 바뀌었다.
자율성을 높이면, 응집도가 높아진다
응집도가 높은 객체는 밀접하게 연관된 작업만을 수행하고, 연관성 없는 작업은 다른 객체에게 위임하는 객체를 말한다.
응집도가 높은 객체는 객체 스스로가 자신의 데이터와 행위를 책임지기 때문에 응집도가 높은 것이다.
외부의 간섭은 최소한으로 하고(외부 의존성을 최소한으로 하고), 메시지를 통해서 협력해야한다.
( 인터페이스의 메소드를 활용하는 등이 그 메시지를 통해서 협력하는 하나의 예다. )
따라서 자신의 데이터를 스스로, 자율성 있게 처리할 수 있는 객체는 자율성은 높아지고, 결합도는 낮아지고, 응집도는 높아진다.
결론적으로,
1. 코드를 변경하고 이해하기 쉽기 위해서는 의존성이 낮아져야한다.
2. 그래서 의존성을 최소한으로 만들어, 객체 간의 결합도를 낮춰야한다.
3. 결합도를 낮추기 위해서는 세부적인 내용을 숨기는 캡슐화를 사용할 수 있다.
4. 이를 통해서 자율성과 응집도를 높인다.
5. 캡슐화하고 자율성과 응집도를 높이는 과정에서, 객체는 자신을 스스로 책임지게 되면서 다른 객체로부터 책임이 이동한다.
객체지향 프로그래밍 OOP
모듈은 데이터와 프로세스(기능)을 가지고 있다.
예제를 통해 설명하면 Theater라면, enter라는 프로세스와 Audience, TicketSeller, Bag, TicketOffice라는 데이터를 가지고 있는 것이다. ( 개선 전 예제 기준 )
그리고 객체 지향 프로그래밍은 기능이나 데이터 대신 객체가 중심이 되어 개발하는 방식이다.
객체가 중심이되기 위해서는, 객체는 자신의 데이터를 스스로 처리해야한다.
따라서, 데이터와 프로세스가 동일한 모듈 내에 위치해야한다.
캡슐화를 통해서 데이터와 프로세스가 동일한 모듈 내에 위치하게 만들 수 있다.
그래서 객체 지향 프로그래밍에서는 각자의 객체가 중심이 되기 위해서,
객체 모듈 내부에 데이터와 프로세스를 둠으로써, 객체 자신의 책임을 지게 만든다.
그러면서 의존성과 결합도는 낮아지고, 자율성은과 응집도는 올라가고, 변경에 유연하고 이해하기 쉬운 코드를 만들게 된다.
절차지향과 객체지향의 차이
어느 학급 학생들의 시험 점수 평균과 을 구하는 기능을 구현하는 예제를 통해 알아보자.
절차지향은 C로, 객체지향은 Java로 구현하였다.
#include <stdio.h>
#include <stdlib.h>
int main() {
int scores[10] = {1,2,3,4};
int i = 0;
printf(average(scores, 10));
}
int average(int scores[], int cnt) {
...
}
public class Scores {
int[] scores;
public Scores(int[] scores) {
this.scores = scores;
}
public double getAverage() {
return Arrays.stream(scores).average();
}
}
public class Main() {
public static void main(String[] args) {
int[] scores = {1,2,3,4};
for(int i=0; i<10; i++) {
scores[i] = args[i];
}
Score score = new Score(scores);
System.out.println(score.getAverage());
}
}
절차지향의 코드는 데이터 scores를 처리하기 위해서 의존적인 함수 average라는 함수를 만들어서 호출했다.
이 코드는 Score 객체 중심적이었다고 할 수 없는, 데이터와 그 데이터를 처리하는 로직이 분리되어 있는 절차 지향적인 코드다.
절차지향은 이렇게 프로세스와 데이터가 별도의 모듈에 위치해있다.
객체지향의 코드는 Score 객체 중심적으로 처리하기 위해서, Score 객체를 선언해서 객체 내부에서 scores 데이터를 관리하도록 했다.
그리고 객체 내부에서 getAverage 메서드를 통해서 데이터를 처리한다.
이 코드는 Score 객체 중심적인, Score 객체가 자신의 책임을 지는, 객체 내부에서 데이터와 프로세스를 관리하는 객체 지향적인 코드다.
객체지햐은 이렇게 프로세스와 데이터가 동일한 모듈에 위치해있다.
핵심 개념
캡슐화는 개념적이나 물리적으로 객체 내부의 세부적인 사항을 감추는 것이다.
객체 지향은 객체 중심적으로 구성되어 있기 때문에, 객체 내부에서 책임을 진다. 그래서 데이터와 프로세스가 동일한 모듈에 위치해있다.
레퍼런스
'개발 서적 기록 > 오브젝트_조영호' 카테고리의 다른 글
6일차 - 합성을 통한 코드 재사용과 협력 (2) | 2023.08.07 |
---|---|
5일차 - 상속과 다형성의 목적 (0) | 2023.08.04 |
4일차 - 객체 지향 설계를 위한 자세 (0) | 2023.08.04 |
2일차 - 객체 지향적인 모듈 (0) | 2023.08.02 |
1일차 - 오브젝트를 본격적으로 읽기 전에 (2) | 2023.07.31 |