
순차지향 프로그래밍의 Sequential은 코드를 위에서 아래로 읽겠다는 의미다.
절차지향 프로그래밍의 Procedure는 직역하면 ‘절차’이지만, 컴퓨터 공학에서 Procedure는 함수다.
절차지향 프로그래밍은 함수(procedure) 지향 프로그래밍이다.
int add(int a, int b) {
return a + b;
}
int main() {
int a = 2;
int b = 3;
return add(a, b);
}
@Getter
class Store {
private List<Order> orders;
private long rentalFee; // 임대료
}
@Getter
public class Order {
private List<Food> foods;
private double transactionFeePercent = 0.03; // 결제 수수료 3%
}
@Getter
public class Food {
private long price;
private long originCost; // 원가
}
class RestaurantChain {
private List<Store> stores;
/**
* 음식 체인점의 매출을 계산한다
*/
public long calculateRevenue() {
long revenue = 0;
for (Store store : stores) {
for (Order order : store.getOrders()) {
for (Food food : order.getFoods()) {
revenue += food.getPrice();
}
}
}
return revenue;
}
/**
* 음식 체인점의 순이익을 계산한다.
*/
public long calculateProfit() {
long cost = 0;
for (Store store : stores) {
for (Order order : store.getOrders()) {
long orderPrice = 0;
for (Food food : order.getFoods()) {
orderPrice += food.getPrice();
cost += food.getPrice();
}
// 결제 금액의 3%를 비용으로 잡는다.
cost += orderPrice * order.getTransactionFeePercent();
}
cost += store.getRentalFee();
}
return calculateRevenue() - cost;
}
}
RestaurantChain의 calculateRevenue, calculateProfit는 모두 절차지향적인 코드다.
RestaurantChain의 함수를 실행하기 위한 데이터로써 존재할 뿐이다.Store, Order, Food에는 아무런 책임이 존재하지 않는다. C의 구조체와 다를 바 없다.스프링을 프로젝트에서 모든 비즈니스 로직이 서비스 컴포넌트에 있는 코드를 작성하는 경우가 많다.
@Service
@RequiredArgsConstructor
class RestaurantChainService {
private final StoreRepository storeRepository;
/**
* 음식 체인점의 매출을 계산한다
*/
public long calculateRevenue(long restaurantId) {
List<Store> stores = storeRepository.findByRestaurantId(restaurantId);
long revenue = 0;
for (Store store : stores) {
for (Order order : store.getOrders()) {
for (Food food : order.getFoods()) {
revenue += food.getPrice();
}
}
}
return revenue;
}
/**
* 음식 체인점의 순이익을 계산한다.
*/
public long calculateProfit(long restaurantId) {
List<Store> stores = storeRepository.findByRestaurantId(restaurantId);
long cost = 0;
for (Store store : stores) {
for (Order order : store.getOrders()) {
long orderPrice = 0;
for (Food food : order.getFoods()) {
orderPrice += food.getPrice();
cost += food.getPrice();
}
// 결제 금액의 3%를 비용으로 잡는다.
cost += orderPrice * order.getTransactionFeePercent();
}
cost += store.getRentalFee();
}
return calculateRevenue(restaurantId) - cost;
}
}
절차지향적인 코드에서 벗어나지 못하고 클래스를 데이터를 저장하는 용도로만 사용하고 있다.
class RestaurantChain {
private List<Store> stores;
public long calculateRevenue() {
long revenue = 0;
for (Store store : stores) {
revenue += store.calculateRevenue();
}
return revenue;
}
public long calculateProfit() {
long income = 0;
for (Store store : stores) {
income += store.calculateProfit();
}
return income;
}
}
@Getter
class Store {
private List<Order> orders;
private long rentalFee; // 임대료
/**
* 가게의 매출을 계산한다.
*/
public long calculateRevenue() {
long revenue = 0;
for (Order order : orders) {
revenue += order.calculateRevenue();
}
return revenue;
}
/**
* 가게의 순이익을 계산한다.
*/
public long calculateProfit() {
long income = 0;
for (Order order : orders) {
income += order.calculateProfit();
}
return income - rentalFee;
}
}
@Getter
class Order {
private List<Food> foods;
private double transactionFeePercent = 0.03; // 결제 수수료 3%
/**
* 주문의 총 금액을 계산한다.
*/
public long calculateRevenue() {
long revenue = 0;
for (Food food : foods) {
revenue += food.calculateRevenue();
}
return revenue;
}
/**
* 주문으로 발생하는 순이익을 계산한다.
*/
public long calculateProfit() {
long income = 0;
for (Food food : foods) {
income += food.calculateProfit();
}
return (long) (income - calculateRevenue() * transactionFeePercent);
}
}
@Getter
class Food {
private long price;
private long originCost; // 원가
/**
* 음식의 총 금액을 계산한다.
*/
public long calculateRevenue() {
return price;
}
/**
* 음식으로 발생하는 순이익을 계산한다.
*/
public long calculateProfit() {
return price - originCost;
}
}
정리하자면,
RestaurantChain에서 처리하지 않는다객체지향은 가독성보다 책임에 집중한다.
객체지향으로 코드를 작성하면,
그리고
하지만, 믿는다

책임은 계약이다.
수많은 객체가 협력하는 객체지향 프로그래밍에선 협력 객체들이 계약을 제대로 지킬 것을 가정하고 프로그램을 만든다. 이를 위해 테스트 코드를 사용할 수 있다.
int absolute(int a) {
return a < 0 ? -a : a;
}
책임을 어떻게 나누고 어디에 할당하는지가 중요하다.
객체지향에서는 책임을 함수가 아닌 객체에 할당하는 것이 중요하다.
‘책임을 객체에 할당한다’로 객체지향을 설명하기엔 부족하다.
C언어에는 객체지향을 지원하기 위한 무언가가 부족하다.
interface Calculable {
/**
* 총 금액을 계산한다.
*/
long calculateRevenue();
/**
* 총 순이익을 계산한다.
*/
long calculateProfit();
}
class RestaurantChain implements Calculable {
private List<Calculable> stores;
@Override
public long calculateRevenue() {
long revenue = 0;
for (Calculable store : stores) {
revenue += store.calculateRevenue();
}
return revenue;
}
@Override
public long calculateProfit() {
long income = 0;
for (Calculable store : stores) {
income += store.calculateProfit();
}
return income;
}
}
@Getter
class Store implements Calculable {
private List<Calculable> orders;
private long rentalFee; // 임대료
@Override
public long calculateRevenue() {
long revenue = 0;
for (Calculable order : orders) {
revenue += order.calculateRevenue();
}
return revenue;
}
@Override
public long calculateProfit() {
long income = 0;
for (Calculable order : orders) {
income += order.calculateProfit();
}
return income - rentalFee;
}
}
@Getter
class Order implements Calculable {
private List<Calculable> foods;
private double transactionFeePercent = 0.03; // 결제 수수료 3%
@Override
public long calculateRevenue() {
long revenue = 0;
for (Calculable food : foods) {
revenue += food.calculateRevenue();
}
return revenue;
}
@Override
public long calculateProfit() {
long income = 0;
for (Calculable food : foods) {
income += food.calculateProfit();
}
return (long) (income - calculateRevenue() * transactionFeePercent);
}
}
@Getter
class Food implements Calculable {
private long price;
private long originCost; // 원가
@Override
public long calculateRevenue() {
return price;
}
@Override
public long calculateProfit() {
return price - originCost;
}
}
→ 엄밀히는, 객체지향에서는 책임을 객체에 할당하지 않는다.
객체를 추상화한 역할에 책임을 할당한다.
C언어의 구조체는 추상 개념을 지원하지 못하므로 절차지향적 언어다.
이로 인한 장점
내가 부탁한 책임과 역할을 할 수 있는 객체라면 협력 객체가 어떤 객체인지 신경쓰지 않아도 된다.
→ 새로운 요구사항에도 역할을 다하는 새로운 구현체만 만들면 되므로 확장에도 유연해진다.
객체지향의 본질은
객체지향의 본질은 역할, 책임, 협력 이다.
문법적 기능들은 역할, 책임, 협력을 잘 다루기 위해 존재하는 프로그래밍 언어적 기능일 뿐이다.
캡슐, 상속, 추상화, 다형성은 객체지향을 대표하는 기능적인 특징일 순 있지만 핵심은 아니다.
절차지향이 객체지향에 비해 뒤떨어진 방법론인 것은 아니다.
패러다임의 장단점을 이해하고, 어떤 방법을 쓸지 상황에 따라 정해서 사용해야 한다.
TDA: Tell, Don’t Ask
TDA 원칙을 지키며 개발하면 객체지향적 사고방식을 할 수 있다.

사용자가 물건을 구입할 때 물건값을 계산하기
public void sell(Account account, Product product) {
if (account.canAfford(product.getPrice()) {
account.withdraw(product.getPrice());
System.out.println(product.getName() + "를 구매했습니다");
} else {
System.out.println("잔액이 부족합니다");
}
}
→ Account 클래스는 수동적인 데이터 덩어리가 아니라 책임을 갖는 객체가 된다.
Getter와 Setter를 줄이라는 의미로 해석할 수도 있다.→ 외부에서 객체의 모든 데이터에 접근하게 되고, Manager, Utility 같은 클래스가 무수히 늘어난다
하지만 객체에게 모든 일을 시킬 수는 없다. Getter는 필요한 메서드일 수 있다.
Getter는 그 자체로 ‘가격을 알려줘야 하는 책임’ 등으로 결국 사용해야 하는 상황이 생긴다.