모듈·캡슐화·객체·인터페이스
주문 금액 계산, 재고 변경, 알림 전송이 한 코드에 뒤섞이면 할인 규칙 하나를 고쳐도 재고와 알림을 함께 시험해야 한다. 반대로 아무 기준 없이 파일만 잘게 나누면 한 기능을 바꿀 때 여러 파일을 오가야 한다. 좋은 분리는 같은 이유로 변하는 상태와 행위를 묶고, 외부에는 필요한 계약만 드러내는 것이다.
코드가 커질수록 파일 수보다 변경 경계가 중요하다
주문 금액 계산, 재고 변경, 알림 전송이 한 코드에 뒤섞이면 할인 규칙 하나를 고쳐도 재고와 알림을 함께 시험해야 한다. 반대로 아무 기준 없이 파일만 잘게 나누면 한 기능을 바꿀 때 여러 파일을 오가야 한다. 좋은 분리는 같은 이유로 변하는 상태와 행위를 묶고, 외부에는 필요한 계약만 드러내는 것이다.
모듈은 변경 단위를, 캡슐화는 내부 보호 원칙을, 인터페이스 타입은 코드 수준의 동작 계약을, 객체와 조합은 상태와 행위를 묶어 더 큰 행동을 만드는 방법을 제공한다.
먼저 되짚기: 함수와 인터페이스에서 시작한다
함수는 입력과 결과·효과를 가진 처리단위였고, 인터페이스는 두 요소가 교환하는 입력·출력·오류의 약속이었다. 여러 함수와 상태를 하나의 책임으로 묶으면 모듈이 되고, 허용한 함수만 외부 계약으로 내놓으면 내부 변경을 가둘 수 있다.
함수와 인터페이스를 변경 경계로 확장한다
함수와 상태를 같은 변경 이유로 묶은 모듈이 책임 경계를 만든다. 캡슐화는 그 경계의 공개 연산에서 상태 규칙을 지키게 하고, 인터페이스 타입은 외부가 의존할 동작 계약을 정한다. 주문 모듈과 결제 계약의 연결에서 이 세 역할을 함께 본다.
모듈
모듈은 관련 기능과 데이터를 하나의 책임 아래 묶고 공개 경계를 정한 구성 단위다. 모듈 안의 요소는 같은 이유로 함께 변하도록 응집시키고, 바깥에는 필요한 인터페이스만 제공한다. 언어와 시스템에 따라 파일·패키지·네임스페이스·라이브러리 또는 서비스로 구현될 수 있지만, 모든 모듈이 독립 배포되거나 다른 곳에서 재사용되어야 하는 것은 아니다. 핵심은 변경 책임과 공개 경계를 분명히 하는 것이다.
캡슐화
캡슐화(encapsulation)는 내부 상태와 구현을 숨기고 허용된 연산만 노출하는 원칙이다. 숨김 자체가 목적이 아니라, 상태를 올바르게 유지하는 규칙을 한 경계에서 강제하는 것이 목적이다. 잔액을 직접 고치지 못하게 하고 입금, 출금 연산만 제공하면 음수 금지와 기록 규칙을 한곳에서 지킬 수 있다.
객체 조합 전에 캡슐화를 다시 잇는다
캡슐화는 상태를 직접 노출하지 않고 허용된 연산으로 규칙을 강제한다. 객체 조합에서도 각 객체가 자기 상태 규칙을 지키면서 다른 객체의 공개 계약에 의존해야 한다.
조합은 내부상태 공유가 아니라 계약 협력이다
주문 객체가 결제 객체를 부품으로 가져도 결제 내부 잔액을 직접 고치지 않고 authorize() 같은 공개 연산을 호출한다. 조합이 캡슐화를 깨는 지름길이 되지 않게 한다.
값은 바뀌어도 지킬 조건은 남는다
불변조건(invariant)은 정한 관찰 지점마다 성립해야 하는 상태의 조건이다. 여기서는 다른 코드가 공개된 연산을 호출하기 직전과, 연산이 정상적으로 끝나 결과를 돌려줄 때 지킬 상태 규칙을 말한다. 상태를 처음 만들 때도 이 조건을 만족시켜야 뒤의 연산이 그 상태를 믿고 사용할 수 있다.
잔액이 음수가 되면 안 되는 계좌에서 잔액 100에 출금 20을 적용하면 80이 된다. 값은 바뀌었지만 전후 모두 잔액 ≥ 0을 만족한다. 출금 120은 이 조건을 깨므로 거부하고 원래 상태를 보존해야 한다. 불변조건은 값 자체를 고정한다는 뜻이 아니라, 값이 바뀌는 동안에도 정한 지점에서 지킬 약속이다.
내부 처리 도중 잠시 조건이 맞지 않는 순서로 값을 고칠 수는 있다. 다만 다른 코드가 그 중간 상태를 보지 못하게 보호하고, 공개된 연산의 결과로 돌려주기 전에는 조건을 복구해야 한다. 이 절의 계좌·주문 계약에서는 거절·오류로 끝나도 외부에 남는 상태가 비음수 불변조건을 지켜야 한다. 모든 실패에서 모든 값을 원래대로 되돌린다는 뜻은 아니다. 주문은 결과에 따라 거절·확인대기 상태를 남길 수 있고, 그때도 합계 규칙은 유지한다. 접근을 숨겼다는 사실만으로 이 보호가 저절로 생기지는 않는다. 여러 연산이 동시에 실행되거나 저장 중 실패하는 경우의 통제는 뒤에서 별도로 배우며, 여기서는 먼저 무엇을 지켜야 하는지 정한다.
인터페이스 타입
인터페이스 타입은 구현과 독립적으로 제공해야 할 동작의 형식 계약이다. 어떤 이름의 연산이 어떤 입력을 받고 어떤 결과를 돌려주는지 정하지만 내부 알고리즘과 저장방식은 정하지 않는다. 여러 구현이 같은 계약을 지키면 사용하는 코드는 구체 구현 대신 이 계약에 의존할 수 있다. 다만 입력·결과의 자료형이 맞는다고 실제 승인·상태 변화까지 같아지는 것은 아니다. 성공 결과의 의미와 오류 때 남는 상태는 각 구현의 계약시험으로 확인한다.
객체와 조합
객체와 조합은 상태와 행위를 객체로 묶고 작은 객체를 조합해 큰 행위를 만드는 모델이다. 객체는 식별 가능한 상태와 그 상태를 다루는 연산을 함께 가진다. 조합은 한 객체가 다른 객체를 부품으로 가지고 그 기능을 위임하는 관계다.
결제 방법을 조합하는 주문 모듈
상자 안의 대상과 화살표의 조건을 함께 읽으세요. 좁은 화면에서는 같은 관계를 세로로 재배치한 도식을 보여 줍니다.
주문 모듈 안에서 placeOrder가 주문 상태와 합계 비음수 규칙을 지킨다. 모듈은 Payment의 pay(amount) 결과 계약을 사용하며 카드·계좌 구현은 같은 계약을 제공한다. 결과는 성공(승인번호)·거절(이유)·기술오류(확인 필요)다. 조합이 결제 내부 상태의 직접 수정을 허용하지는 않는다.
주문 모듈은 주문 상태와 검증 함수를 묶고, 상태를 직접 수정하지 못하게 placeOrder 같은 연산만 공개한다. OrderService 객체는 구체 카드 코드가 아니라 Payment 인터페이스 타입을 사용한다. 실행할 때 카드 또는 계좌 구현을 부품으로 넣으면 주문 로직을 바꾸지 않고 결제 방법을 교체할 수 있다. 이것이 조합을 통한 행동 구성이다.
도식의 pay(amount) -> 결과는 amount라는 금액 입력을 pay에 전달하고 화살표 오른쪽의 결과를 돌려받는다는 표기다. 교육용 공통 결과를 성공(승인번호), 거절(이유), 기술오류(확인 필요)로 정하면 카드와 계좌 구현 모두 이 셋의 의미를 지켜야 한다. 주문 모듈은 내부 카드사 번호가 아니라 이 결과를 보고 확정·거절·확인대기 상태를 고른다. 따라서 계좌 구현을 추가해도 주문 코드에 결제사별 결과 해석을 새로 흩어 놓을 필요가 없다.
시험에서는 실제 외부 결제 대신 정한 결과를 반환하는 시험 대역을 넣어 주문의 판단을 먼저 확인할 수 있다. 성공 대역에서는 확정, 거절 대역에서는 거절, 오류 대역에서는 확인대기인지 비교한다. 이것은 주문 모듈의 결과 해석을 분리해 확인하는 방법이며, 실제 카드·계좌 구현이 외부 기관과 올바르게 통신하는 검증까지 대신하지 않는다.
ME-1: 두 변경요구를 경계에 배치한다
주문 시스템에 다음 요구가 들어왔다고 하자.
- 카드 외에 계좌이체 결제를 추가한다.
- 주문 합계가 음수가 되지 않도록 규칙을 강화한다.
첫 요구는 PaymentMethod.pay(amount) 같은 인터페이스 계약 아래 새 구현을 조합하는 변화다. 이 예의 PaymentMethod는 앞 도식의 Payment와 같은 교육용 결제 계약을 가리킨다. 두 번째는 주문 상태를 바꾸는 공개 연산에서 total >= 0 불변조건을 지키게 하는 변화다.
결제수단 추가 때문에 주문 내부 필드를 모두 공개하거나, 합계 규칙 때문에 모든 결제 구현을 수정해야 한다면 경계가 잘못 얽혀 있는 신호다. 같은 파일에 있다는 이유가 아니라 같은 이유로 변하는지를 본다.
실행 예에서는 합계 3000원·상태 접수인 주문에 결제 구현을 넣어 placeOrder를 호출한다. 주문 모듈이 합계가 0 이상인지 먼저 검사하고 결제 pay에 3000원을 전달하며, 성공 승인번호를 받으면 주문을 확정한다. 카드 대신 계좌 구현을 넣어도 같은 성공 결과라면 같은 확정 상태가 된다. 새 입력으로 계산한 후보 합계 -3000원은 유효한 주문 상태에 반영하거나 결제를 호출하기 전에 거부하고 기존 합계와 접수 상태를 보존한다. 즉 음수 합계를 이미 저장해 둔 주문이 정상이라는 가정이 아니다. 거절이면 거절 상태, 기술오류이면 확인대기 상태와 원인을 남겨 성공으로 가장하지 않는다. 이 상태 전이와 결제 결과는 교육용 계약이며 실제 실패·동시 처리의 보호 방법은 다음 절과 뒤 전문 장에서 배운다.
풀이를 가리고 세 실행 결과를 추적한다
각 경우는 서로 독립적이며 시작 주문의 합계는 3000원, 상태는 접수다. placeOrder는 후보 합계를 검사한 뒤에만 결제를 호출한다. 외부 승인 효과는 이 시험의 대상이 아니며 결제 결과는 대역으로 주어진다. 각 경우의 결제 호출 여부와 호출 뒤 남을 합계·상태를 적어 보자.
| 경우 | 후보 합계 | 결제 대역의 결과 |
|---|---|---|
| A | -3000원 | 호출되면 성공을 반환하도록 설정 |
| B | 3000원 | 거절(한도 초과) |
| C | 3000원 | 기술오류(응답 확인 필요) |
상태와 호출 여부를 각각 대조하기
A는 입력 검증에서 거부하므로 성공 대역을 준비해도 결제 호출은 0회다. 기존 합계 3000원·접수 상태를 보존한다. B는 결제를 호출한 뒤 합계 3000원·거절 상태와 이유를 남긴다. C는 결제를 호출한 뒤 합계 3000원·확인대기 상태와 원인을 남긴다. C의 기술오류를 결제가 확실히 일어나지 않았다는 뜻으로 바꾸거나 바로 새로 승인하지 않는다.
세 경우 모두 외부에 보이는 합계는 0 이상이다. 상태 보존과 상태 전이는 실패 종류에 따라 다르며, 불변조건을 지킨다는 말만으로 모든 실패의 결과를 하나로 합치지 않는다. 이 비교는 주문의 판단 시험이며 실제 외부 승인·동시 처리의 안전성을 검증하지 않는다.
상속과 조합을 판단한다
상속은 기존 타입의 성질과 동작을 물려받아 더 구체적인 타입을 만드는 관계다. “B는 A의 한 종류이며 A를 기대하는 모든 곳에서 같은 약속을 지킨다”가 참이고 공통 규칙이 안정적일 때 유용하다. 조합은 “A가 B의 기능을 사용한다”는 관계로, 부품을 바꾸거나 여러 행동을 섞기 쉽다.
| 판단 | 상속 | 조합 |
|---|---|---|
| 관계 | ~의 한 종류 | ~을 가지고 사용 |
| 변경 결합 | 부모 내부 변화가 자식에 퍼질 수 있음 | 인터페이스 계약을 통해 영향 제한 |
| 실행 중 교체 | 일반적으로 어려움 | 부품 교체가 쉬움 |
| 적합한 조건 | 안정적인 분류와 대체 가능성 | 기능 위임·정책 교체·다중 조립 |
기본 선택은 조합으로 두고, 진짜 대체 가능한 종류 관계가 명확할 때 상속을 검토하면 잘못된 계층을 줄일 수 있다. 모듈 경계는 조직 편의보다 함께 변하는 책임으로 정한다.
실패조건과 방어
자료만 숨긴 캡슐화에서는 접근만 제한하고 상태를 올바르게 바꾸는 규칙은 여러 곳에 흩어져 있다. 불변조건을 지키는 연산을 경계 안에 모은다.
비대한 인터페이스는 일부 구현에서 필요하지 않은 연산까지 구현하도록 강제한다. 소비자가 실제로 필요한 작은 계약으로 나눈다.
잡동사니 모듈에는 관련 없는 공통 함수가 한곳에 모여 모든 코드가 이 모듈에 의존하게 된다. 변경 이유와 업무 책임이 같은지 다시 나눈다.
잘못된 상속은 코드 재사용만을 위해 종류 관계가 아닌 대상을 부모·자식으로 묶는 경우다. 부모를 자식으로 바꿔도 계약이 유지되는지 확인하고 아니면 조합한다.
왕복 위임은 모듈이 아무 판단 없이 다른 모듈 호출만 전달하는 경우다. 독립 책임이 없다면 계층을 제거하거나 책임을 명확히 한다.
자체 10점 문제
모듈·캡슐화·객체 조합·인터페이스 타입의 관계를 설명하시오.
요구어는 설명이다. 변경 이유·상태 규칙·공개계약·구현 교체를 연결한다.
10점 풀이 방향과 모범답안 보기
10점 답안 목차
- 모듈의 책임경계
- 캡슐화와 불변조건
- 인터페이스 타입
- 객체 조합
- 과도한 상속·공개상태 실패
10점 답안 도식
주문 경계 그림의 상태 규칙과 Payment 의존을 함께 설명한다. Card·계좌 구현이 같은 계약을 지킬 때 결제수단을 바꿔도 주문의 판단은 유지된다.
모범답안 축약본: 변경 이유와 상태 규칙으로 경계를 잡는다
문제: 모듈·캡슐화·객체·인터페이스 타입의 관계와 설계기준을 설명하시오. 모듈은 관련 기능과 데이터를 하나의 책임 아래 묶고 공개 경계를 정한 구성 단위이며 반드시 독립 배포·재사용 단위인 것은 아니다. 캡슐화는 내부 상태를 직접 노출하지 않고 상태 불변조건을 지키는 연산만 허용한다. 객체는 상태와 행동을 한 개체로 다루는 모델이고, 인터페이스 타입은 구체 구현과 분리된 연산의 형식 계약이다. 주문 모듈이 주문 상태와 검증을 묶어 placeOrder만 공개하고 주문 서비스가 Payment 인터페이스를 통해 카드·계좌 구현을 조합하면 내부 변경을 가둘 수 있다. 분리는 같은 이유로 변하는 책임의 응집도, 바깥 의존 수, 계약 크기로 판단한다. 관련 없는 공통기능을 모은 잡동사니 모듈, 종류 관계가 아닌 상속, 판단 없이 호출만 넘기는 계층은 책임을 다시 나누거나 조합으로 교정한다.
자체 25점 문제
결제수단 확장과 주문 상태규칙 변경을 동시에 수용하는 모듈 경계를 설계하시오.
요구어는 설계다. 서로 다른 변경이유를 분리하고 공개계약과 상태 불변조건을 제시한다.
25점 풀이 방향과 모범답안 보기
25점 답안 목차
- 변경이유 분리
- Order 상태·불변조건
- Payment 공개계약
- 구현체 조합
- 오류·취소 계약
- 테스트 대역
- 새 구현 추가 영향범위
- 경계 누수 점검
25점 답안 도식
주문 모듈의 상태 경계와 결제 계약을 적용한다. 주문 담당은 total 불변조건과 세 결과 대역을 검증하고, 결제 담당은 PaymentMethod의 입력 단위·결과 의미와 실제 외부 통신을 따로 검증한다.
25점 모범답안
주문 모듈은 품목·합계·상태처럼 주문 규칙 때문에 함께 변하는 데이터를 소유하고, 공개 연산 전후에 합계 음수 금지 같은 불변조건을 검사한다. 내부 필드를 직접 수정하는 통로는 공개하지 않는다.
결제는 PaymentMethod 인터페이스로 금액 입력, 성공·거절·오류 결과를 계약하고 카드·계좌이체 구현을 조합한다. 새 결제수단 추가는 인터페이스 소비 코드와 새 구현 중심으로 제한하며 주문의 상태규칙을 건드리지 않는다.
ME-1처럼 결제수단 추가와 주문 규칙 강화를 서로 다른 변화로 검증한다. 구현 세부를 외부에 노출하거나 모든 구현이 공통 부모 내부상태에 강하게 묶이면 조합과 캡슐화의 이점을 잃는다.
교육용 계약에서는 접수 상태의 합계 3000원 주문을 placeOrder로 검증해 PaymentMethod.pay에 넘기고 성공·거절·기술오류를 각각 확정·거절·확인대기로 바꾼다. 음수인 후보 합계는 주문 상태에 반영하거나 결제를 호출하기 전에 거부하며 기존의 유효한 합계와 상태를 보존한다. 새 계좌 구현도 같은 입력 단위와 결과 의미를 지키게 하고, 주문 담당은 세 결과 대역과 음수 입력으로 상태 규칙을 시험한다. 결제 담당은 별도로 실제 외부 통신을 확인한다. 주문 필드를 공개하거나 카드 내부 결과를 주문에서 직접 해석하는 대안은 불변조건 우회·구현 결합 때문에 제외한다.
출처와 적용 경계
본문은 안정된 모듈성·캡슐화·객체 조합 기초와 자체 주문/결제 예를 사용한다. 구현과 분리한 메서드 형식·호출 계약의 구체 언어 예는 Oracle Java Tutorial의 Interfaces(JDK 8용 자료)에서 확인할 수 있다. 이는 본문의 가상 결제 계약을 실제 Java 선언으로 검증했다는 뜻이나 최신 Java 문법 전체의 설명이 아니다. 구체 언어의 interface/class/module 문법과 가시성 규칙은 해당 언어 사양을 확인한다.
30초 정리
- 모듈은 함께 변하는 기능과 데이터를 묶고, 캡슐화는 상태 규칙을 지키는 연산만 노출한다.
- 인터페이스 타입은 구현과 독립적인 형식 계약이며 객체 조합은 작은 행동을 부품처럼 연결한다.
- 안정적인 종류 관계가 아니면 상속보다 조합을 우선하고, 비대한 계약과 책임 없는 모듈을 경계한다.
자기교정과 조건 변경
| 잘못 쓴 답 | 수정 | 복습 |
|---|---|---|
| 모듈은 파일 하나다 | 핵심은 같은 변경이유와 공개 경계다 | 모듈 |
| 캡슐화는 모든 필드를 private로 만드는 문법이다 | 상태 규칙을 허용된 연산으로 강제하는 목적을 본다 | 캡슐화 |
| 인터페이스는 내부 알고리즘까지 정한다 | 외부 동작 계약과 구현을 분리한다 | 정의 |
| 조합하면 상대 객체의 내부상태를 직접 써도 된다 | 공개 계약을 통해 협력한다 | 조합 |
| 새 결제수단마다 주문 모듈 전체를 고친다 | 안정된 결제 인터페이스 뒤에 새 구현을 추가한다 | ME-1 |
환불 기능이 추가되면? 결제 계약에 환불 의미가 필요한지와 주문상태 전이가 필요한지를 분리한다.
각 결제수단의 결과형식이 다르면? 인터페이스의 공통 결과 계약과 구현별 세부정보 경계를 설계한다.
주문 합계가 외부에서 직접 수정 가능하면? 불변조건을 우회하므로 공개 setter 대신 의미 있는 연산으로 좁힌다.