SRP & OCP, map & flatMap

심민혁·2025년 3월 3일

weeklypaper

목록 보기
1/18

> 2025.02.24일자 위클리 페이퍼

객체지향 프로그래밍에서 단일 책임 원칙(SRP)과 개방 폐쇄 원칙(OCP)에 대해 설명하고, 각각의 원칙을 적용한 코드 예시를 들어주세요.

  • 단일 책임 원칙(SRP)
    클래스나 모듈은 단 하나의 책임(기능)만 가져야 한다는 원칙입니다. 이는 클래스가 변경되어야 하는 이유를 최소화하고, 코드의 가독성과 유지보수성을 높이는 데 도움을 줍니다.

    예를 들어 유저를 관리하는 클래스가 있다면, 이 클래스는 유저 관리와 관련된 기능만 구현해야 합니다. 다음은 예시입니다.

public class UserManager{
	public void registerUser(User user){
    	// 사용자 등록
    }
    
    public void deleteUser(User user){
    	// 사용자 삭제
    }
    
    public void updateUser(User user){
    	// 사용자 업데이트
    }
}
  • 개방 폐쇄 원칙(OCP)
    클래스나 함수 같은 요소들은 확장에 열려 있어야 하고 변경에는 폐쇄 적이여야 한다는 원칙입니다. 즉 기존의 코드는 건들지 않고 새로운 기능을 확장할 수 있어야 합니다.

    잘못된 예시를 먼저 들어보겠습니다. 결제 코드중 한 클래스가 모든 결제를 담당할 경우에 새로운 결제방법이 들어왔을 경우 새로 확장을 하지 못하고 변경을 해야 되는 상황입니다.

public class payment {
	public void pay(String paymentType, double amount){
    	if(paymentType.equals("creditcard")){
        	//카드 결제
        }
        else if(paymentType.equals("accountTransfer"){
        	//계좌 이체
        }
        ... 앞으로 새로운 결제 방법이 생길때 마다 확장하지 못하고 변경으로 일을 처리해야함
    }
}

위에 코드처럼 결과물을 생성한다면 새로운 결제 방법이 생겼을 경우 기존의 코드를 변경하기 때문에 오류가 났을경우 기존의 코드도 오류가 생긴다는 단점이 있습니다.

위에 코드의 문제를 개선하기 위해서는 추상화 방식을 사용하여 결제 방식이 참조를 하는 방식으로 코드를 수정하는 것입니다.

public interface payment{
	void pay(double amount);
}

public class CreditcardPayment implements payment{
	@Override
    public void pay(double amount){
    	//카드 결제
    }    
}

public class AccountPayment implements payment{
	@Override
    public void pay(double amount){
    	//계좌이체 결제
    }    
}

이후 새로운 결제 방법이 생겨도 참조하는 방식으로 확장을 진행 할 수 있음.

위와 같은 방법으로 코드를 작성하였을 때 새로운 결제 방식이 생겨도 기존의 코드를 변경할 필요 없이 확장의 방법으로 코드를 작성 할 수 있게 됩니다.

Stream API의 map과 flatMap의 차이점을 설명하고, 각각의 활용 사례를 예시 코드와 함께 설명해주세요.

JAVA Stream API의 map과 flatMap은 얼핏 비슷해 보입니다. 이 두 메서드의 차이점을 적어 보겠습니다.

  • map()
    스트림의 각 요소를 변환하여 새로운 요소로 매핑하는 중간 연산입니다. 1:1 매핑을 해줍니다.

    예시 :

import java.util.List;
import java.util.stream.Collectors;

public class MapStringExample {
    public static void main(String[] args) {
        List<List<String>> wordsList = List.of(
            List.of("apple", "banana"),
            List.of("cherry", "date"),
            List.of("elderberry", "fig")
        );

        
        List<List<String>> mappedWords = wordsList.stream()
                                                  .map(List::stream)
                                                  .collect(Collectors.toList());

        System.out.println(mappedWords); 
        // [[apple, banana], [cherry, date], [elderberry, fig]]
    }
}

map을 사용한 코드는 2중 중첩 리스트를 사용할 경우에 2차원 리스트로 출력이 됩니다.

  • flatMap()
    스트림의 각 요소를 스트림으로 변환 후, 모든 스트림을 하나의 스트림으로 평면화 합니다. 1:N매핑이라고 볼 수 있습니다.

    예시 :

import java.util.List;
import java.util.stream.Collectors;

public class FlatMapStringExample {
    public static void main(String[] args) {
        List<List<String>> wordsList = List.of(
            List.of("apple", "banana"),
            List.of("cherry", "date"),
            List.of("elderberry", "fig")
        );
        
        List<String> allWords = wordsList.stream()
                                         .flatMap(List::stream)
                                         .collect(Collectors.toList());

        System.out.println(allWords); // [apple, banana, cherry, date, elderberry, fig]
    }
}
  • 차이점

    map() : 각 요소를 변환한 스트림 반환
    flatMap() : 각 요소를 변환한 스트림들을 하나의 스트림으로 평면화 하여 변환

  • 성능상 고려사항

    flatMap()은 중간 스트림을 생성하므로 메모리 사용량이 더 많을 수 있음
    대용량 데이터 처리 시 주의 필요

    단순 변환은 map()이 더 빠름
    복잡한 중첩 구조는 flatMap()이 효율적

    출처 : (https://observerlife.tistory.com/98)

profile
열심히 하고 싶습니다

0개의 댓글