驱动提取注入工具_提取,注入,杀死:打破层次结构–第2部分

驱动提取注入工具

  在第一部分中,我解释了这种方法背后的主要思想,并开始了本示例。 请阅读部分一个阅读这篇文章之前,

尽管已经表达了Extract,Inject,Kill的主要思想,但为完成自己的目的而完成练习是很好的。 这是我们停止的地方:

让我们看一下VoucherPricingService,它现在是层次结构底部唯一的具体类。

public class VoucherPricingService extends UserDiscountPricingService {
 
     private VoucherService voucherService;
 
     @Override
     protected double applyAdditionalDiscounts(double total, User user, String voucher) {
         double voucherValue = voucherService.getVoucherValue(voucher);
         double totalAfterValue = total - voucherValue;
         return (totalAfterValue > 0) ? totalAfterValue : 0;
     }
 
     public void setVoucherService(VoucherService voucherService) {
         this.voucherService = voucherService;
     }
 }

请注意,它使用VoucherService类来计算凭证值。

public class VoucherService {
     public double getVoucherValue(String voucher) {
          Imagine that this calculate the voucher price.
          Keeping it simple so we can understand the approach.
         return 0;
     }
 }

在开始之前,让我们先对VoucherPricingService.java进行一些测试

@RunWith(MockitoJUnitRunner.class)
 public class VoucherPricingServiceTest {
 
     private static final User UNUSED_USER = null;
     private static final String NO_VOUCHER = null;
     private static final String TWENTY_POUNDS_VOUCHER = '20';
     
     @Mock private VoucherService voucherService;
     private TestableVoucherPricingService voucherPricingService;
 
     @Before
     public void initialise() {
         voucherPricingService = new TestableVoucherPricingService();
         voucherPricingService.setVoucherService(voucherService);
         when(voucherService.getVoucherValue(TWENTY_POUNDS_VOUCHER)).thenReturn(20D);
     }
 
     @Test public void
     should_not_apply_discount_if_no_voucher_is_received() {
         double returnedAmount = voucherPricingService.applyAdditionalDiscounts(1000, UNUSED_USER, NO_VOUCHER);
 
         assertThat(returnedAmount, is(1000D));
     }
 
     @Test public void
     should_subtract_voucher_value_from_total() {
         double returnedAmount = voucherPricingService.applyAdditionalDiscounts(30D, UNUSED_USER, TWENTY_POUNDS_VOUCHER);
         
         assertThat(returnedAmount, is(equalTo(10D)));
     }
     
     @Test public void
     shoudl_return_zero_if_voucher_value_is_higher_than_total() {
         double returnedAmount = voucherPricingService.applyAdditionalDiscounts(10D, UNUSED_USER, TWENTY_POUNDS_VOUCHER);
         
         assertThat(returnedAmount, is(equalTo(0D)));
     }
 
 
     private class TestableVoucherPricingService extends VoucherPricingService {
 
         @Override
         protected double applyAdditionalDiscounts(double total, User user, String voucher) {
             return super.applyAdditionalDiscounts(total, user, voucher);
         }
     }
 }

需要注意的一点是,User参数不用于任何东西。 因此,让我们将其删除。

现在是时候在VoucherPricingService上使用Extract,Inject,Kill。 让我们提取 VoucherPricingService.applyAdditionalDiscounts(double,String)方法的内容,并将其添加到名为VoucherDiscountCalculation的类中。 我们将其称为calculateVoucherDiscount()方法。 当然,让我们首先编写测试。 他们需要测试与测试时完全相同的东西
VoucherPricingService.applyAdditionalDiscounts(double,String)。 我们还借此机会将VoucherService对象传递给VoucherDiscountCalculation的构造函数。

@RunWith(MockitoJUnitRunner.class)
 public class VoucherDiscountCalculationTest {
 
     private static final String NO_VOUCHER = null;
     private static final String TWENTY_POUNDS_VOUCHER = '20';
 
     @Mock
     private VoucherService voucherService;
     private VoucherDiscountCalculation voucherDiscountCalculation;
 
     @Before
     public void initialise() {
         voucherDiscountCalculation = new VoucherDiscountCalculation(voucherService);
         when(voucherService.getVoucherValue(TWENTY_POUNDS_VOUCHER)).thenReturn(20D);
     }
 
     @Test public void
     should_not_apply_discount_if_no_voucher_is_received() {
         double returnedAmount = voucherDiscountCalculation.calculateVoucherDiscount(1000, NO_VOUCHER);
 
         assertThat(returnedAmount, is(1000D));
     }
 
     @Test public void
     should_subtract_voucher_value_from_total() {
         double returnedAmount = voucherDiscountCalculation.calculateVoucherDiscount(30D, TWENTY_POUNDS_VOUCHER);
 
         assertThat(returnedAmount, is(equalTo(10D)));
     }
 
     @Test public void
     should_return_zero_if_voucher_value_is_higher_than_total() {
         double returnedAmount = voucherDiscountCalculation.calculateVoucherDiscount(10D, TWENTY_POUNDS_VOUCHER);
 
         assertThat(returnedAmount, is(equalTo(0D)));
     }
 
 }
public class VoucherDiscountCalculation {
     private VoucherService voucherService;
 
     public VoucherDiscountCalculation(VoucherService voucherService) {
         this.voucherService = voucherService;
     }
 
     public double calculateVoucherDiscount(double total, String voucher) {
         double voucherValue = voucherService.getVoucherValue(voucher);
         double totalAfterValue = total - voucherValue;
         return (totalAfterValue > 0) ? totalAfterValue : 0;
     }
 }

如果您注意到了,在进行提取时,我们借此机会为我们的新类和方法提供了适当的名称,并将它们的基本依赖性传递给构造函数,而不是使用方法注入。 现在,让我们更改VoucherPricingService中的代码以使用新的VoucherDiscountCalculation并查看所有测试是否仍通过。

public class VoucherPricingService extends UserDiscountPricingService {
 
     private VoucherService voucherService;
 
     @Override
     protected double applyAdditionalDiscounts(double total, String voucher) {
         VoucherDiscountCalculation voucherDiscountCalculation = new VoucherDiscountCalculation(voucherService);
         return voucherDiscountCalculation.calculateVoucherDiscount(total, voucher);
     }
 
     public void setVoucherService(VoucherService voucherService) {
         this.voucherService = voucherService;
     }
 }

凉。 所有测试仍然通过,这意味着我们具有相同的行为,但是现在在VoucherDiscountCalculation类中,并且我们准备进入Inject阶段。
现在, VoucherDiscountCalculation注入PricingService中,PricingService是层次结构中的顶级类。 与往常一样,让我们​​添加一个测试该新协作的测试。

@RunWith(MockitoJUnitRunner.class)
 public class PricingServiceTest {
 
     private static final String NO_VOUCHER = '';
     private static final String FIVE_POUNDS_VOUCHER = '5';
 
     private TestablePricingService pricingService = new TestablePricingService();
     private ShoppingBasket shoppingBasket;
 
     @Mock private PriceCalculation priceCalculation;
     @Mock private VoucherDiscountCalculation voucherDiscountCalculation;
 
     @Before 
     public void initialise() {
         this.pricingService.setPriceCalculation(priceCalculation);
         this.pricingService.setVoucherDiscountCalculation(voucherDiscountCalculation);
     }
 
     @Test public void
     should_calculate_price_of_all_products() {
         Product book = aProduct().named('book').costing(10).build();
         Product kindle = aProduct().named('kindle').costing(80).build();
         shoppingBasket = aShoppingBasket()
                                 .with(2, book)
                                 .with(3, kindle)
                                 .build();
 
         double price = pricingService.calculatePrice(shoppingBasket, new User(), NO_VOUCHER);
 
         verify(priceCalculation, times(1)).calculateProductPrice(book, 2);
         verify(priceCalculation, times(1)).calculateProductPrice(kindle, 3);
     }
 
     @Test public void
     should_calculate_voucher_discount() {
         Product book = aProduct().named('book').costing(10).build();
         when(priceCalculation.calculateProductPrice(book, 2)).thenReturn(20D);
         shoppingBasket = aShoppingBasket()
                                 .with(2, book)
                                 .build();
 
         double price = pricingService.calculatePrice(shoppingBasket, new User(), FIVE_POUNDS_VOUCHER);
         
         verify(voucherDiscountCalculation, times(1)).calculateVoucherDiscount(20, FIVE_POUNDS_VOUCHER);
     }
 
     private class TestablePricingService extends PricingService {
 
         @Override
         protected double calculateDiscount(User user) {
             return 0;
         }
 
         @Override
         protected double applyAdditionalDiscounts(double total, String voucher) {
             return 0;
         }
 
     }
 }

这是更改后的PriningService。

public abstract class PricingService {
 
     private PriceCalculation priceCalculation;
     private VoucherDiscountCalculation voucherDiscountCalculation;
 
     public double calculatePrice(ShoppingBasket shoppingBasket, User user, String voucher) {
         double discount = calculateDiscount(user);
         double total = 0;
         for (ShoppingBasket.Item item : shoppingBasket.items()) {
             total += priceCalculation.calculateProductPrice(item.getProduct(), item.getQuantity());
         }
         total = voucherDiscountCalculation.calculateVoucherDiscount(total, voucher);
         return total * ((100 - discount)  100);
     }
 
     protected abstract double calculateDiscount(User user);
 
     protected abstract double applyAdditionalDiscounts(double total, String voucher);
 
     public void setPriceCalculation(PriceCalculation priceCalculation) {
         this.priceCalculation = priceCalculation;
     }
 
     public void setVoucherDiscountCalculation(VoucherDiscountCalculation voucherDiscountCalculation) {
         this.voucherDiscountCalculation = voucherDiscountCalculation;
     }
 
 }

现在是时候 VoucherPricingService类和杀死 PricingService.applyAdditionalDiscounts(双总,字符串凭证)模板方法,因为它不再使用。 我们也可以杀死 VoucherPricingServiceTest类和固定PricingServiceTest取出applyAdditionalDiscounts从可测试类()方法。

因此,现在当然,我们的层次结构中不再有具体的类,因为VoucherPricingService是唯一的类。 现在,我们可以安全地将UserDiscountPricingService提升为具体。

现在,我们的对象图如下所示:

我们的层次结构又短了一层。 现在我们唯一需要做的就是再次应用Extract,Inject,Kill UserDiscountPricingService内部的逻辑提取到另一个类中(例如UserDiscountCalculation), UserDiscountCalculation注入PricingService中,最后杀死 UserDiscountPricingService和calculateDiscount(User user)模板方法。 UserDiscountPricingService,

由于之前已经介绍了该方法,因此不再需要逐步进行。 让我们看一下最终结果。

这是代表我们从哪里开始的图:

在最后一次Extract,Inject,Kill重构之后,这是我们得到的:

上图所示的最终模型的有趣之处在于,现在我们不再有任何抽象类。 所有类和方法都是具体的,每个类都是可独立测试的。

最终的PricingService类就是这样的:

public class PricingService {
 
     private PriceCalculation priceCalculation;
     private VoucherDiscountCalculation voucherDiscountCalculation;
     private PrimeUserDiscountCalculation primeUserDiscountCalculation;
 
     public PricingService(PriceCalculation priceCalculation, VoucherDiscountCalculation voucherDiscountCalculation,
                           PrimeUserDiscountCalculation primeUserDiscountCalculation) {
         this.priceCalculation = priceCalculation;
         this.voucherDiscountCalculation = voucherDiscountCalculation;
         this.primeUserDiscountCalculation = primeUserDiscountCalculation;
     }
 
     public double calculatePrice(ShoppingBasket shoppingBasket, User user, String voucher) {
         double total = getTotalValueFor(shoppingBasket);
         total = applyVoucherDiscount(voucher, total);
         return totalAfterUserDiscount(total, userDiscount(user));
     }
 
     private double userDiscount(User user) {
         return primeUserDiscountCalculation.calculateDiscount(user);
     }
 
     private double applyVoucherDiscount(String voucher, double total) {
         return voucherDiscountCalculation.calculateVoucherDiscount(total, voucher);
     }
 
     private double totalAfterUserDiscount(double total, double discount) {
         return total * ((100 - discount)  100);
     }
 
     private double getTotalValueFor(ShoppingBasket shoppingBasket) {
         double total = 0;
         for (ShoppingBasket.Item item : shoppingBasket.items()) {
             total += priceCalculation.calculateProductPrice(item.getProduct(), item.getQuantity());
         }
         return total;
     }
 
 }

有关最终代码的完整实现,请查看https://github.com/sandromancuso/breaking-hierarchies

注意:对于这三部分的博客文章,我使用了三种不同的方法来绘制UML图。 手工使用ArgoUMLAstah社区版 。 我对后者很满意

参考:Crafted Software博客上,从我们的JCG合作伙伴 Sandro Mancuso处提取,注入,杀死:破坏层次结构(第3部分)


翻译自: https://www.javacodegeeks.com/2012/06/extract-inject-kill-breaking_13.html

驱动提取注入工具

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值