Как избавиться от дублирования кода (2023)

Как избавиться от дублирования кода (2023)

Представьте, что мы разрабатываем функцию размещения заказа для разных пользователей:

  • Обычный пользователь должен оплатить стоимость доставки, которая составляет 10% от стоимости товаров; скидка не предоставляется
  • VIP-пользователь также должен оплатить стоимость доставки в размере 10%, но скидка будет предоставлена на 3-й товар и далее.
  • Внутреннему пользователю не нужно оплачивать стоимость доставки; скидка не предоставляется.

Давайте настроим некоторые базовые объекты, над которыми мы будем работать позже, включая объект Cart и объект Item.

private BigDecimal totalItemPrice; private BigDecimal totalDeliveryPrice; private BigDecimal payPrice; // getters and setters } public class Item { private long id; private int quantity; private BigDecimal price; private BigDecimal couponPrice; private BigDecimal deliveryPrice; // getters and setters }

Теперь мы реализуем логику 3 типов корзин для покупок и будем использовать объект Map(ключ - идентификатор продавца, значение - количество товара) в качестве входного параметра. Результатом будет объект Cart.

Давайте взглянем на реализацию обычной пользовательской корзины:

public class NormalUserCart { public Cart process(long userId, Map<Long, Integer> items) { Cart cart = new Cart(); // 1. Stream the inputs List<Item> itemList = new ArrayList<>(); items.entrySet().stream().forEach(entry -> { Item item = new Item(); item.setId(entry.getKey()); item.setPrice(Db.getItemPrice(entry.getKey())); item.setQuantity(entry.getValue()); itemList.add(item); }); cart.setItems(itemList); // 2. Set delivery and coupon price itemList.stream().forEach(item -> { item.setDeliveryPrice(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())).multiply(new BigDecimal("0.1"))); item.setCouponPrice(BigDecimal.ZERO); }); // 3. Set total prices cart.setTotalItemPrice(cart.getItems().stream().map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))).reduce(BigDecimal.ZERO, BigDecimal::add)); cart.setTotalDeliveryPrice(cart.getItems().stream().map(Item::getDeliveryPrice).reduce(BigDecimal.ZERO, BigDecimal::add)); cart.setTotalDiscount(cart.getItems().stream().map(Item::getCouponPrice).reduce(BigDecimal.ZERO, BigDecimal::add)); cart.setPayPrice(cart.getTotalItemPrice().add(cart.getTotalDeliveryPrice()).subtract(cart.getTotalDiscount())); return cart; } }

В этой корзине мы получаем товары из входных данных, извлекаем необходимую информацию, включая цены доставки, цены купонов и общие цены, и устанавливаем их в объект Cart. Поскольку скидки нет, цена купона будет равна нулю.

Чем корзина VIP-пользователя будет отличаться от обычной?

public Cart process(long userId, Map<Long, Integer> items) { //... // 2. Set delivery and coupon price itemList.stream().forEach(item -> { item.setDeliveryPrice(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())).multiply(new BigDecimal("0.1"))); if (item.getQuantity() > 2) { item.setCouponPrice(item.getPrice() .multiply(BigDecimal.valueOf(100 - Db.getUserCouponPercent(userId)).divide(new BigDecimal("100"))) .multiply(BigDecimal.valueOf(item.getQuantity() - 2))); } else { item.setCouponPrice(BigDecimal.ZERO); } }); // ... return cart; }

Поскольку VIP пользуется скидками начиная с 3-го заказа, логика, которую необходимо изменить, - это только 2-я часть кода, которая заключается в установлении разных цен на купоны. Разделы кода 1 и 3 остаются неизменными, поскольку логика получения из входных данных и вывода общих цен остается неизменной.

Переходя к последнему типу пользователей — внутренним пользователям, скидка не предоставляется, но они не платят за доставку.

public class InternalUserCart { public Cart process(long userId, Map<Long, Integer> items) { // ... itemList.stream().forEach(item -> { item.setDeliveryPrice(BigDecimal.ZERO); item.setCouponPrice(BigDecimal.ZERO); }); // ... return cart; } }

Видите ли вы дублированный код здесь?

Разделы кода 1 и 3 снова повторяются на протяжении трёх разных корзин. Если бы мы изменили логику какой-либо пользовательской корзины, нам было бы легко изменить логику и для других корзин. Это ставит под угрозу ремонтопригодность кода.

Например, если кто-то понял, что вычисление общей цены товара неверно в корзине Vip-пользователя, но он забыл, что в NormalUserCart и InternalUserCart есть одни и те же детали. Это приводит к несогласованности кода, что приведёт к ошибочной бизнес-логике.

Как же тогда мы можем улучшить код?

Как избавиться от дублирования кода (2023)

Мы можем использовать шаблон проектирования (шаблонный метод) шаблонов для реализации общего шаблона в родительском классе.

public abstract class AbstractCart { public Cart process(long userId, Map<Long, Integer> items) { Cart cart = new Cart(); List<Item> itemList = new ArrayList<>(); items.entrySet().stream().forEach(entry -> { Item item = new Item(); item.setId(entry.getKey()); item.setPrice(Db.getItemPrice(entry.getKey())); item.setQuantity(entry.getValue()); itemList.add(item); }); cart.setItems(itemList); itemList.stream().forEach(item -> { processCouponPrice(userId, item); processDeliveryPrice(userId, item); }); cart.setTotalItemPrice(cart.getItems().stream().map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))).reduce(BigDecimal.ZERO, BigDecimal::add)); cart.setTotalDeliveryPrice(cart.getItems().stream().map(Item::getDeliveryPrice).reduce(BigDecimal.ZERO, BigDecimal::add)); cart.setTotalDiscount(cart.getItems().stream().map(Item::getCouponPrice).reduce(BigDecimal.ZERO, BigDecimal::add)); cart.setPayPrice(cart.getTotalItemPrice().add(cart.getTotalDeliveryPrice()).subtract(cart.getTotalDiscount())); return cart; } protected abstract void processCouponPrice(long userId, Item item); protected abstract void processDeliveryPrice(long userId, Item item); }

Как мы можем увидеть из кода, раздел кода 2 заменён здесь двумя абстрактными методами processCouponPrice и processDeliveryPrice.

Теперь мы снова готовы внедрять NormalUserCart (обычную пользовательскую корзину):

@Service(value = "NormalUserCart") public class NormalUserCart extends AbstractCart{ @Override protected void processCouponPrice(long userId, Item item) { item.setDeliveryPrice(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())).multiply(new BigDecimal("0.1"))); } @Override protected void processDeliveryPrice(long userId, Item item) { item.setCouponPrice(BigDecimal.ZERO); } }

Аналогично, у нас может быть VipUserCart и InternalUserCart:

@Service(value = "VipUserCart") public class VipUserCart extends NormalUserCart { @Override protected void processCouponPrice(long userId, Item item) { if (item.getQuantity() > 2) { item.setCouponPrice(item.getPrice() .multiply(BigDecimal.valueOf(100 - Db.getUserCouponPercent(userId)).divide(new BigDecimal("100"))) .multiply(BigDecimal.valueOf(item.getQuantity() - 2))); } else { item.setCouponPrice(BigDecimal.ZERO); } } } @Service(value = "InternalUserCart") public class InternalUserCart extends AbstractCart { @Override protected void processCouponPrice(long userId, Item item) { item.setCouponPrice(BigDecimal.ZERO); } @Override protected void processDeliveryPrice(long userId, Item item) { item.setDeliveryPrice(BigDecimal.ZERO);
Как избавиться от дублирования кода (2023)

Вы также заметили, что мы даём имя Spring bean в аннотации “Service”. Поскольку все три класса являются XXXUserCart, мы можем использовать String для получения имени корзины покупок и использовать Spring IoC для получения AbstractCart. Благодаря этому мы внедрили шаблон заводского проектирования:

public Cart processUserCart(@RequestParam("userId") int userId) { String userCategory = Db.getUserCategory(userId); AbstractCart cart = (AbstractCart) applicationContext.getBean(userCategory + "UserCart"); return cart.process(userId, items); }

Теперь, если у нас появится новый тип пользователя, скажем, “Корпоративный” пользователь, и для него будет создано новое правило скидки, нам не нужно будет делать ничего для текущего кода. Нам нужно только создать CorporateUserCart, чтобы расширить AbstractCart для реализовать логику.

Благодаря этому мы можем расшифровать букву “О” в ТВЁРДОМ принципе:

Открыт для расширения, закрыт для модификации.

Я надеюсь, что данная статья будет полезна для вас!

Статья была взята со следующего источника:

1 комментарий

если ,такой умный, помоги, авито отдать деньги

Ответить