본문 바로가기
OOP

DIP(Dependency Inversion Principle) 의존성 역전 원칙

by Ahngyuho 2025. 2. 15.

오늘은 SOLID 원칙 중 DIP 원칙에 대해서 공부한 것을 정리해 보려고 합니다.

 

DIP : 의존성 역전 원칙

상위 모듈은 하위 모듈에 의존하지 말고, 둘 다 추상화에 의존해야 한다.

 

의존한다는 것은 Java 에서 구체적으로 표현하면 다음과 같습니다.

public interface ImageService {
    public List<String> upload(MultipartFile[] file);
}


//@Service
@RequiredArgsConstructor
public class LocalImageService implements ImageService {
    @Value("${project.upload.path}")
    private String defaultUploadPath;
    private String makeDir() {
        String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")).toString();

        //File.separator -> window 에서는 / ?
        String uploadPath = defaultUploadPath+File.separator+date;
        File uploadDir = new File(uploadPath);

        if(!uploadDir.exists()) {
            uploadDir.mkdirs();
        }

        return "/" + date;
    }

    @Override
    public List<String> upload(MultipartFile[] files) {
        String uploadPath = makeDir();
        List<String> uploadedFilePaths = new ArrayList<>();
        for(MultipartFile file : files) {
            String originalFileName = file.getOriginalFilename();
            //파일명 겹칠 수 있으므로 UUID 사용
            String uploadFilePath = uploadPath+"/" + UUID.randomUUID().toString()+"_"+originalFileName;
            File uploadFile = new File(defaultUploadPath + "/" + uploadFilePath);
            uploadedFilePaths.add(uploadFilePath);

            try{
                file.transferTo(uploadFile);
            } catch (Exception e) {

            }
        }


        return uploadedFilePaths;
    }
}



@Service
@RequiredArgsConstructor
public class CloudImageService implements ImageService {
    private final S3Client s3Client;
    @Value("${spring.cloud.aws.s3.bucket}")
    private String bucket;

    @Override
    public List<String> upload(MultipartFile[] file){
        String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd/")).toString();
        List<String> uploads = new ArrayList<>();
        // 내 계정에 asw 권한을 가지고 있어야 함
        try{
            for(MultipartFile fileItem : file){
                String fileName = date + UUID.randomUUID().toString()+"_"+ fileItem.getOriginalFilename();
                PutObjectResponse response = s3Client.putObject(
                        PutObjectRequest.builder()
                                .contentType(fileItem.getContentType())
                                //yaml 에 넣어준 것 여기에 자동으로 들어갈까?
                                //아니다 ...
                                .bucket(bucket)
                                .key(fileName)
                                .build(),
                        RequestBody.fromBytes(fileItem.getBytes())
                );

                uploads.add("/" + fileName);

            }
        }catch (Exception e){
            e.printStackTrace();
        }

        return uploads;
    }
}

 

위에 코드를 그림으로 표현하면 다음과 같습니다.

 

 

ProductSerice  가 상위 모듈이고 LocalImageService, CluodImageService 가 하위 모듈 입니다.

상위 모듈, 하위 모듈 둘다 추상화에 의존해야 한다는 말의 의미는 ProductSerice 라는 고수준 모듈과 LocalImageService, CluodImageService 모듈이 ImageService 라는 추상화(Interface)에 의존해야 한다는 것을 DIP 라고 합니다.

 

이건 하나의 구체적인 예시 중 하나로 DIP 원칙을 준수하는 방법은 여러가지 있습니다.

DI , 서비스 로케이터, 팩토리 .... 등등

 

하지만 핵심은 상위 모듈과 하위 모듈이 추상화를 통해 의존해야 한다는 것입니다.

 

상위 모듈 → 추상화 ← 하위 모듈

이런 형식을 만족하면서 구현해야 한다는 원칙이 DIP 입니다!

 

DI 도 예시를 한번 들어보면 

DI(Dependency Injection) 는 객체를 외부에서 주입해주는 방법입니다.

 

아래 DI 를 하지 않았을 때 코드입니다.

public class CloudImageService {
    public void uploadImage(String imageName) {
        System.out.println("Uploading " + imageName + " to cloud storage.");
    }
}

public class ProductService {
    private CloudImageService imageService; // ❌ 직접 의존 (DIP 위반)

    public ProductService() {
        this.imageService = new CloudImageService(); // ❌ 직접 객체 생성 (강한 결합)
    }

    public void uploadProductImage(String imageName) {
        imageService.uploadImage(imageName); // ❌ 특정 구현체에 의존
    }
}

 

이 코드는 ProductService (상위 모듈) → CloudImageService (하위모듈)  를 바로 의존하고 있습니다. 

 

 public ProductService() {
        this.imageService = new CloudImageService(); // ❌ 직접 객체 생성 (강한 결합)
    }

 

new 를 통해서 직접적으로 구체적인 클래스에 의존하고 있습니다. 이건 DIP 위반입니다.

 

그렇다면 DI 를 통해 DIP 원칙을 준수하는 코드를 한번 보겠습니다.

 

public class ProductService {
    private ImageService imageService; // ✅ 추상화(인터페이스)에 의존

    public ProductService(ImageService imageService) { // ✅ 생성자 주입
        this.imageService = imageService;
    }

    public void uploadProductImage(String imageName) {
        imageService.uploadImage(imageName); // ✅ 특정 구현체를 몰라도 됨!
    }
}

public class Main {
    public static void main(String[] args) {
        ImageService imageService = new CloudImageService(); // ✅ 클라우드 사용
        // ImageService imageService = new LocalImageService(); // ✅ 로컬 저장소 사용
        ProductService productService = new ProductService(imageService); // ✅ DI 적용!

        productService.uploadProductImage("example.png");
    }
}

 

이렇게 되면 ProductService (상위 모듈) →  ImageService(추상화)  ← CloudImageService (하위모듈)

 

이런 식으로 외부에서 객체를 주입해줌으로써 DIP 원칙을 준수하는 객체지향 기술이 DI 입니다!

 

핵심은 DIP 란 상위 모듈과 하위 모듈이 추상화를 통해 서로 의존하는 것이고

상위 모듈 →  추상화  ← 하위모듈

이런 형태를 유지하게 하는 것이 DIP 입니다!

 

'OOP' 카테고리의 다른 글

Spring 의 IoC 와 DI  (0) 2025.02.04
MVC 패턴  (1) 2025.01.08
USE CASE  (1) 2024.12.02