适应DRY原则的方式

Way to adjust to DRY principle

我有一个网络抓取应用程序,它可以简单地从两家大书店抓取书籍。这个想法是用户将类别类型放入 URL 例如 /romances , /biographies .

控制器:

package bookstore.scraper.controller;

imports...

import java.util.List;
import java.util.Map;

@RestController
public class BookController {

    private final MostPreciseBookService mostPreciseBookService;
    private final CategorizedBookService categorizedBookService;
    private final BestSellersService bestSellersService;

    @Autowired
    public BookController(MostPreciseBookService bookOperationsService, CategorizedBookService categorizedBookService, BestSellersService bestSellersService) {
        this.mostPreciseBookService = bookOperationsService;
        this.categorizedBookService = categorizedBookService;
        this.bestSellersService = bestSellersService;
    }
    
.....


    @GetMapping("/romances")
    public Map<Bookstore, List<Book>> get15RomanticBooks() {
        return categorizedBookService.get15BooksFromRomanceCategory();
    }

    @GetMapping("/biographies")
    public Map<Bookstore, List<Book>> get15BiographiesBooks() {
        return categorizedBookService.get15BooksFromBiographiesCategory();
    }

    @GetMapping("/guides")
    public Map<Bookstore, List<Book>> get15GuidesBooks() {
        return categorizedBookService.get15BooksFromGuidesCategory();
    }

    @GetMapping("/fantasy")
    public Map<Bookstore, List<Book>> get15FantasyBooks() {
        return categorizedBookService.get15BooksFromFantasyCategory();
    }

    @GetMapping("/crime")
    public Map<Bookstore, List<Book>> get15CrimeBooks() {
        return categorizedBookService.get15BooksFromCrimeCategory();
    }
}

服务

package bookstore.scraper.book.scrapingtypes;

import bookstore.scraper.Bookstore;
import bookstore.scraper.book.Book;
import bookstore.scraper.empik.EmpikFetchingBookService;
import bookstore.scraper.merlin.MerlinFetchingBookService;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static bookstore.scraper.JSoupConnector.connect;

@Service
@Slf4j
public class CategorizedBookService {

    private final EmpikFetchingBookService empikBookService;
    private final MerlinFetchingBookService merlinFetchingBookService;

    @Value("${external.library.url.empik.romances}")
    private String romancesCategoryEmpikURL;

    @Value("${external.library.url.empik.biographies}")
    private String biographiesCategoryEmpikURL;

    @Value("${external.library.url.empik.crime}")
    private String crimeCategoryEmpikURL;

    @Value("${external.library.url.empik.guides}")
    private String guidesCategoryEmpikURL;

    @Value("${external.library.url.empik.fantasy}")
    private String fantasyCategoryEmpikURL;

    @Value("${external.library.url.merlin.romances}")
    private String romancesCategoryMerlinURL;

    @Value("${external.library.url.merlin.biographies}")
    private String biographiesCategoryMerlinURL;

    @Value("${external.library.url.merlin.crime}")
    private String crimeCategoryMerlinURL;

    @Value("${external.library.url.merlin.guides}")
    private String guidesCategoryMerlinURL;

    @Value("${external.library.url.merlin.fantasy}")
    private String fantasyCategoryMerlinURL;

    @Autowired
    public CategorizedBookService(EmpikFetchingBookService empikBookService, MerlinFetchingBookService merlinFetchingBookService) {
        this.empikBookService = empikBookService;
        this.merlinFetchingBookService = merlinFetchingBookService;
    }


    public Map<Bookstore, List<Book>> get15BooksFromRomanceCategory() {
        Map<Bookstore, List<Book>> bookstoreWith15CategorizedBooks = new HashMap<>();

        bookstoreWith15CategorizedBooks.put(Bookstore.EMPIK, empikBookService
                .get15BooksFromCategory(connect(romancesCategoryEmpikURL)));
        bookstoreWith15CategorizedBooks.put(Bookstore.MERLIN, merlinFetchingBookService
                .get15BooksFromCategory(connect(romancesCategoryMerlinURL)));

        return bookstoreWith15CategorizedBooks;
    }

    public Map<Bookstore, List<Book>> get15BooksFromFantasyCategory() {
        Map<Bookstore, List<Book>> bookstoreWith15CategorizedBooks = new HashMap<>();

        bookstoreWith15CategorizedBooks.put(Bookstore.EMPIK, empikBookService
                .get15BooksFromCategory(connect(fantasyCategoryEmpikURL)));
        bookstoreWith15CategorizedBooks.put(Bookstore.MERLIN, merlinFetchingBookService
                .get15BooksFromCategory(connect(fantasyCategoryMerlinURL)));

        return bookstoreWith15CategorizedBooks;
    }

    public Map<Bookstore, List<Book>> get15BooksFromCrimeCategory() {
        Map<Bookstore, List<Book>> bookstoreWith15CategorizedBooks = new HashMap<>();

        bookstoreWith15CategorizedBooks.put(Bookstore.EMPIK, empikBookService
                .get15BooksFromCategory(connect(crimeCategoryEmpikURL)));
        bookstoreWith15CategorizedBooks.put(Bookstore.MERLIN, merlinFetchingBookService
                .get15BooksFromCategory(connect(crimeCategoryMerlinURL)));

        return bookstoreWith15CategorizedBooks;
    }

    public Map<Bookstore, List<Book>> get15BooksFromGuidesCategory() {
        Map<Bookstore, List<Book>> bookstoreWith15CategorizedBooks = new HashMap<>();

        bookstoreWith15CategorizedBooks.put(Bookstore.EMPIK, empikBookService
                .get15BooksFromCategory(connect(guidesCategoryEmpikURL)));
        bookstoreWith15CategorizedBooks.put(Bookstore.MERLIN, merlinFetchingBookService
                .get15BooksFromCategory(connect(guidesCategoryMerlinURL)));

        return bookstoreWith15CategorizedBooks;
    }

    public Map<Bookstore, List<Book>> get15BooksFromBiographiesCategory() {
        Map<Bookstore, List<Book>> bookstoreWith15CategorizedBooks = new HashMap<>();

        bookstoreWith15CategorizedBooks.put(Bookstore.EMPIK, empikBookService
                .get15BooksFromCategory(connect(biographiesCategoryEmpikURL)));
        bookstoreWith15CategorizedBooks.put(Bookstore.MERLIN, merlinFetchingBookService
                .get15BooksFromCategory(connect(biographiesCategoryMerlinURL)));

        return bookstoreWith15CategorizedBooks;
    }
}

如您所见,有很多相同的代码。我做过这样的东西,不知道做的好不好:

public Map<Bookstore, List<Book>> get15BooksFromCategory(String merlinCategoryURL, String empikCategoryURL) {
 

   Map<Bookstore, List<Book>> bookstoreWith15CategorizedBooks = new HashMap<>();

bookstoreWith15CategorizedBooks.put(Bookstore.EMPIK, empikBookService
        .get15BooksFromCategory(connect(empikCategoryURL)));
bookstoreWith15CategorizedBooks.put(Bookstore.MERLIN, merlinFetchingBookService
        .get15BooksFromCategory(connect(merlinCategoryURL)));

return bookstoreWith15CategorizedBooks;
}

我也在考虑在控制器中创建一种方法,例如:

@GetMapping("/{category}")
    public Map<Bookstore, List<Book>> get15CategorizedBooks(@PathVariable String category) {
        ...
    }

但我很难在服务中创建通用方法。

问题到底。在服务中有那么多 @Value 注释是不是好方法?/(使用 yml 文件存储 URL)。

The question in the end. Is it good way to have that many @Value annotations in Service?/ (using yml files to store URL's).

这不是因为你注入了太细粒度的属性。这些应该被封装到一个特定的对象中:更具可读性、可维护性和可测试性。

Spring 提供 @ConfigurationProperties 来做到这一点。
它必须注释包含属性的 class。
你可以这样做:

@Component
@ConfigurationProperties("external.library.url")
public class BookStoreUrlProperties {

    private Empik empik = new Empik();
    private Merlin merlin = new Merlin();    
    // getters/setters    

    public BookStoreUrlProperties() {
    }

    public static class Empik {

        private String romances;
        private String biographies;
        private String crime;
        private String guides;
        private String fantasy;        
        // getters/setters    
    }

    public static class Merlin {

        private String romances;
        private String biographies;
        private String crime;
        private String guides;
        private String fantasy;    
        // getters/setters    
    }

}

然后像注入任何其他 bean 一样注入此 bean:

@Autowired
BookStoreUrlProperties bookStoreUrlProperties;

并使用 getter 检索 url 例如:

String RomanceUrl = bookStoreUrlProperties.getMerlin().getRomances();

我建议的另一种选择是将属性(merlin 和 empik)拆分为两个属性 classes。

关于服务 class 中的重复项,您可以通过提取参数重构轻松地将它们分解出来,因为唯一的区别是 URL 值。

例如:

public Map<Bookstore, List<Book>> get15BooksFromGuidesCategory() {
   return get15BooksFrom(guidesCategoryEmpikURL, guidesCategoryMerlinURL)
}

public Map<Bookstore, List<Book>> get15BooksFromBiographiesCategory() {
   return get15BooksFrom(biographiesCategoryEmpikURL, biographiesCategoryMerlinURL)
}

public Map<Bookstore, List<Book>> get15BooksFrom(String bookStoreEmpikURL, String bookStoreMerlinURL) {

    bookstoreWith15CategorizedBooks.put(Bookstore.EMPIK, empikBookService
            .get15BooksFromCategory(connect(bookStoreEmpikURL)));
    bookstoreWith15CategorizedBooks.put(Bookstore.MERLIN, merlinFetchingBookService
            .get15BooksFromCategory(connect(bookStoreMerlinURL)));

    return bookstoreWith15CategorizedBooks;
}