ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [수정 예정] (OCP)개방 폐쇄 원칙 위배를 극복한 전략 패턴 적용 사례
    개발/Spring Boot 2024. 7. 12. 14:39

    PhotoProvider의 클래스 다이어그램

    public interface PhotoProvider {
        String crawlingImageURL(String page_url);
        byte[] getImageBytes(String image_url);
        String getHostname();
    }
    public abstract class AbstractPhotoProvider implements PhotoProvider {
        @Override
        public byte[] getImageBytes(String image_url) {
            return ConnectionAction.getImage(getHostname(), image_url);
        }
    }
    @Component
    public class HaruFilm extends AbstractPhotoProvider {
        @Override
        public String crawlingImageURL(String page_url) {
            Document crawledPage = JsoupAction.getDocument(page_url);
            return JsoupAction.returnSrcBySelectedElements(crawledPage, "div.main_cont > img");
        }
    
        @Override
        public String getHostname() {
            return "haru9.mx2.co.kr";
        }
    }
    @Component
    public class OnePercent extends AbstractPhotoProvider {
    
        @Override
        public String crawlingImageURL(String page_url) {
            Document crawledPage = JsoupAction.getDocument(page_url);
            return JsoupAction.returnSrcBySelectedElements(crawledPage, "div.main_cont > img");
        }
    
        @Override
        public String getHostname() {
            return "bc1.mx2.co.kr";
        }
    }

     

    다음은 위 클래스 구조를 활용하는 원래 코드이다.

    @Component
    public class CrawlingComponentImpl implements CrawlingComponent {
        private final HaruFilm haruFilm;
        private final OnePercent onePercent;
    
        @Autowired
        public CrawlingComponentImpl(@Qualifier("haruFilm") PhotoProvider haruFilm,
                                     @Qualifier("onePercent") PhotoProvider onePercent) {
            this.haruFilm = (HaruFilm) haruFilm;
            this.onePercent = (OnePercent) onePercent;
        }
    
        @Override
        public byte[] getImageFromHaruFilm(String page_url) {
            String image_url = haruFilm.crawlingImageURL(page_url);
            return haruFilm.getImageBytes(image_url);
        }
    
        @Override
        public byte[] getImageFromOnePercent(String page_url) {
            String image_url = onePercent.crawlingImageURL(page_url);
            return onePercent.getImageBytes(image_url);
        }
        
        //...

    기존의 코드는 @Qualifier 어노테이션을 통해 PhotoProvider를 구현한 클래스 중 haruFilm이나 onePercent라는 이름을 가진 Bean을 주입받아 각 메서드에서 주입받은 모듈들의 서비스를 사용하여 이미지를 가져오는 기능을 구현한다.

     

    하지만 이 코드는 OCP(개방 폐쇄 원칙)을 위반하고 있다. 새로운 PhotoProvider가 추가될 때마다 클라이언트의 코드가 변경되기 때문이다. OCP 원칙에 따르면, 확장을 위해 기존의 코드는 수정 없이 새로운 기능을 추가할 수 있어야 한다. 즉, 소프트웨어는 확장에 개방적(Open)이어야 하지만, 수정에는 폐쇄적(Close)이어야 한다.

     

    이를 해결하기 위해 전략(Stategy Pattern)을 적용했다. 전략 패턴을 사용하여 다양한 전략을 동적으로 변경할 수 있고, 클라이언트의 코드의 변경 없이 새로운 전략을 추가할 수 있다. 새로운 구조에서는 PhotoProvider의 각 구현체가 고유의 전략을 제공하고, CrawlingComponentImpl 클래스는 PhotoProvider의 이름을 기준으로 필요한 전략을 동적으로 선택하여 이미지를 가져온다. 이를 통해 PhotoProvider가 추가되더라도 클라이언트의 코드의 변경 없이 확장이 가능해진다.

    @Component
    public class CrawlingComponentImpl implements CrawlingComponent {
        private final Map<String, PhotoProvider> providers;
    
        @Autowired
        public CrawlingComponentImpl(List<PhotoProvider> providerList) {
            this.providers = providerList.stream()
                    .collect(Collectors.toMap(provider -> provider.getClass().getSimpleName().toLowerCase(), Function.identity()));
        }
    
    
        @Override
        public byte[] getImage(String providerName, String page_url) {
            PhotoProvider provider = providers.get(providerName.toLowerCase());
            String image_url = provider.crawlingImageURL(page_url);
            return provider.getImageBytes(image_url);
        }
    }

    빈 주입도 정상적으로 이뤄지는 것을 볼 수 있다.

    새로운 PhotoProvider가 추가되더라도 CrawlingComponentImpl 클래스의 코드는 변경되지 않으며, 확장에 대해서는 개방적이고 변경에 대해서는 폐쇄적인 구조를 유지할 수 있었다. 

     

    이와 같은 변경을 통해 OCP 원칙을 지키는 코드를 작성할 수 있었고, 코드의 확장성과 유지보수성이 크게 향상되었음을 느낄 수 있었다.

    댓글

Designed by Tistory.