重构到模式 - 策略模式+ 职责链模式解决图书打折的问题

今天,BJDP的伍斌老师提出一个有意思的题

 

 

假设出版社要促销一套哈利波特图书,该套图书共5集,每集单册购买8元。若任意两集各买一本,打95折;若任意三集各买一本,打9折;若任意四集各买一本,打8折;若所有这五集都各买一本,打75折。上述优惠之外的单册还是按8元一本计价。比如五集各买一本之外再加一本第一集,五本书打75折,这本另加的第一集按8元计价。
 
这个问题的答案是51.2。但是用程序应该如何实现呢?
 
先是写了套代码:
 1 public double buy(Map<Book, Integer> books) {

 2 

 3         int count = count(books);

 4         double minPrice = count * 8;    //TODO I really hate this 8.

 5 

 6         //TODO I doubt it could work well if the count is too big.

 7         //TODO I think it needs another alogrithm to count if bought all 5 kinds books, not only 5 piece of books.

 8         for (int i = 0; i < count; i++) {

 9              Discount discount1 = DiscountFactory.create(i);

10              Discount rest = DiscountFactory.create(count - i);

11              double price = discount1.price() + rest.price();

12              System.out.println(discount1.price() +"+" + rest.price());

13              minPrice = Math.min(price, minPrice);

14         }

15 

16         return minPrice;

17     }

但是这套代码还是有很多问题的。

比如:图书的种类没有记入统计,而是仅仅按照数量来计算的。

 

对于这个题目来说,首先打折的方式可以用策略模式来实现

1 public interface Discount {

2     public double price();

3 }
1 public class Trois implements Discount {

2     public double price() {

3         return 8 * 3 * 0.9;

4     }

5 }
1 public class Quatre implements Discount {

2     public double price() {

3         return 8 * 4 * 0.8;

4     }

5 }
1 public class Cenq implements Discount {

2     public double price() {

3         return 8 * 5 * 0.75;

4     }

5 }

还有不打折的情形

 

 1 public class None  implements Discount {

 2 

 3     private int count;

 4     public None(int count) {

 5         this.count= count;

 6 

 7     }

 8     public double price() {

 9         return 8 * count;

10     }

11 }

再配合一个工厂,那么外部就可以使用了。

 1 public class DiscountFactory {

 2     public static Discount create(int count) {

 3         switch (count) {

 4             case 3:

 5                 return new Trois();

 6             case 4:

 7                 return new Quatre();

 8             case 5:

 9                 return new Cenq();

10             default:

11                 return new None(count);

12         }

13     }

14 }

对于总价计算部分,我是这样考虑的:

首先,挑出几本书,进行打折计算,然后再挑出一些,直到最后无法打折为止。

但是挑选的策略有很多种组合,这里采用了两种策略:

1. 按照最多组合优先

    即,先选5本组合的,再选4本组合的,从数量多的数目开始挑选,确保剩下的尽可能的出现组合,而获得更多的优惠。

2.按照最优组合优先

    即,两个4本比一个5本加一个3本更优惠。

    那么就按照上述同样的算法,只是先挑选4本的。

而先选择3本毫无优势可言,则直接放弃。

 

  1 package discount; /**

  2  * Created with IntelliJ IDEA.

  3  * User: wanghongliang

  4  * Date: 13-7-4

  5  * Time: 上午9:14

  6  * To change this template use File | Settings | File Templates.

  7  */

  8 

  9 import java.util.*;

 10 

 11 import book.Book;

 12 import book.HarryPotter;

 13 

 14 public class Strategy {

 15 

 16     public double buy(Map<Book, Integer> books) {

 17 

 18         double price1 = buyAsLowerAsPossible(clone(books));

 19         double price2 = buyAsSmartAsPossible(clone(books));

 20 

 21         return  Math.min(price1, price2);

 22     }

 23 

 24     private Map<Book, Integer> clone(Map<Book, Integer> books) {

 25         Map<Book, Integer> target = new HashMap<Book, Integer>();

 26         Iterator iterator = books.keySet().iterator();

 27         while(iterator.hasNext()) {

 28             Book book = (Book)iterator.next();

 29             Integer value = books.get(book);

 30             target.put(book, value);

 31         }

 32         return target;

 33     }

 34 

 35     public double buyAsSmartAsPossible(Map<Book, Integer> books)  {

 36         double minPrice =  new None(count(books)).price();

 37 

 38         double price = 0;

 39         while(count(books) > 0) {

 40             int dis = pickBooksAsSmartAsPossible(books);

 41             Discount discount = DiscountFactory.create(dis);

 42             price += discount.price();

 43         }

 44 

 45         return Math.min(minPrice, price);

 46     }

 47 

 48     public double buyAsLowerAsPossible(Map<Book, Integer> books) {

 49         double minPrice =  new None(count(books)).price();

 50 

 51         double price = 0;

 52         while(count(books) > 0) {   //There's book left.

 53             int dis = pickBooksAsMoreAsPossible(books);

 54             Discount discount = DiscountFactory.create(dis);

 55             price += discount.price();

 56         }

 57 

 58         return Math.min(minPrice, price);

 59     }

 60 

 61     private int pickBooksAsSmartAsPossible(Map<Book, Integer> books) {

 62         if (hasBooks(books, 4)) {

 63             removeBooks(books, 4);

 64             return 4;

 65         }

 66         if (hasBooks(books, 5)){

 67             removeBooks(books, 5);

 68             return 5;

 69         }

 70         if (hasBooks(books, 3)) {

 71             removeBooks(books, 3);

 72             return 3;

 73         }

 74         removeBooks(books, 1);

 75         return 1;

 76     }

 77     private int pickBooksAsMoreAsPossible(Map<Book, Integer> books) {

 78         if (hasBooks(books, 5)){

 79             removeBooks(books, 5);

 80             return 5;

 81         }

 82         if (hasBooks(books, 4)) {

 83             removeBooks(books, 4);

 84             return 4;

 85         }

 86         if (hasBooks(books, 3)) {

 87             removeBooks(books, 3);

 88             return 3;

 89         }

 90         removeBooks(books, 1);

 91         return 1;

 92     }

 93 

 94     private boolean hasBooks(Map<Book, Integer> books, int kinds) {

 95          return books.keySet().size() >= kinds;

 96     }

 97 

 98     private void removeBooks(Map<Book, Integer> books, int kinds) {

 99         List<Book> keys = new ArrayList<Book>();

100         List<Integer> values = new ArrayList<Integer>();

101         sortBooksDesc(books, keys, values);

102         reduceCount(books, kinds, keys);

103         removeEmptyBookKeys(books);

104     }

105 

106     private void sortBooksDesc(Map<Book, Integer> books, List<Book> keys, List<Integer> values) {

107         Iterator iterator = books.keySet().iterator();

108         while(iterator.hasNext()) {

109             Book book = (Book)iterator.next();

110             Integer count = books.get(book);

111             int index = findPlace(values, count);

112             keys.add(index, book);

113             values.add(index, count);

114         }

115     }

116 

117     private void removeEmptyBookKeys(Map<Book, Integer> books) {

118         removeBook(books, HarryPotter.BOOK_1);

119         removeBook(books, HarryPotter.BOOK_2);

120         removeBook(books, HarryPotter.BOOK_3);

121         removeBook(books, HarryPotter.BOOK_4);

122         removeBook(books, HarryPotter.BOOK_5);

123     }

124 

125     private void reduceCount(Map<Book, Integer> books, int kinds, List<Book> keys) {

126         for (int i = 0; i < kinds; i++) {

127             Book book = keys.get(i);

128             System.out.println("reduce from " + book.index);

129             Integer count = books.get(book);

130 

131             if (count > 0 )  {

132                 books.put(book, count - 1);

133             }

134         }

135     }

136 

137     private int findPlace(List<Integer> values, Integer value) {

138         for (int i = 0; i < values.size(); i++) {

139              if (values.get(i) <= value) {

140                   return i;

141              }

142         }

143         return values.size();

144     }

145 

146     private void removeBook(Map<Book, Integer> books, Book key) {

147         if(books.containsKey(key) && books.get(key) ==0) {

148             System.out.println("remove book " + key.index);

149             books.remove(key);

150         }

151     }

152 

153     private int count(Map<Book, Integer> books) {

154         int count = 0;

155         Iterator iterator = books.keySet().iterator();

156 

157         while(iterator.hasNext()) {

158             Book book = (Book)iterator.next();

159             count += books.get(book);

160         }

161 

162         return count;

163     }

164 }


上述代码经过如下测试,都已经通过了。

 1 public static void main(String [] args) {

 2         Strategy strategy = new Strategy();

 3         Map<Book, Integer> cart = new HashMap<Book, Integer>() ;

 4 

 5         cart.put(HarryPotter.BOOK_1, 2);

 6         cart.put(HarryPotter.BOOK_2, 2);

 7         cart.put(HarryPotter.BOOK_3, 2);

 8         cart.put(HarryPotter.BOOK_4, 1);

 9         cart.put(HarryPotter.BOOK_5, 1);

10 

11         double price = strategy.buy(cart);

12         System.out.println("the discounted price is :" + price); //Hope it is 51.2;

13 

14 

15         cart.put(HarryPotter.BOOK_1, 1);

16         cart.put(HarryPotter.BOOK_2, 1);

17         cart.put(HarryPotter.BOOK_3, 2);

18         cart.put(HarryPotter.BOOK_4, 3);

19         cart.put(HarryPotter.BOOK_5, 4);

20 

21         price = strategy.buy(cart);

22         System.out.println("the discounted price is :" + price); //Hope it is 75.2;

23 

24 

25         cart.put(HarryPotter.BOOK_1, 2);

26         cart.put(HarryPotter.BOOK_2, 2);

27         cart.put(HarryPotter.BOOK_3, 2);

28         cart.put(HarryPotter.BOOK_4, 2);

29         cart.put(HarryPotter.BOOK_5, 2);

30 

31         price = strategy.buy(cart);

32         System.out.println("the discounted price is :" + price); //Hope it is 60;

33     }

 

 

注:如果上述组合不是僵化的而是每次都可以灵活选择的话,那么对于5,5,3和5,4,4这种情形就可以做出优选了。

 

总结:

    策略模式一般来说需要配合工厂模式,让外部可以调用。

    职责链模式的基本表达可以写成:doE(doD(doC(doB(doA(param)))));   也就是说,对同一个事物的不停采取不同策略操作。

             但是在本例中,下次采取什么策略需要进行计算,所以不是简单的传递,而是:

  while (....) {            

          Strategy strategy = StrategyFactory.create(param));

          strategy.execute(param);

  }

上述代码没有符合这个模式,还需要继续改进。

 

 再补充一下,图书信息:
1 public class Book {

2     public String name;

3     public int index;

4     public Book(String name, int index) {

5         this.name = name;

6         this.index = index;

7     }

8 }
1 public class HarryPotter {

2     public static final Book BOOK_1 = new Book("philosophy stone", 1);

3     public static final Book BOOK_2 = new Book("secret chamber", 2);

4     public static final Book BOOK_3 = new Book("prisoner of azkaban", 3) ;

5     public static final Book BOOK_4 = new Book("goblet of fire", 4) ;

6     public static final Book BOOK_5 = new Book("order of phoenix", 5);

7     //NOT FOR SALE currently public static final book.Book BOOK_6 = new book.Book("half blood prince");

8     //NOT FOR SALE currently public static final book.Book BOOK_7 = new book.Book("deathly hallow");

9 }

 遗漏了一个2本的情况

1 public class Duex implements Discount {

2     public double price() {

3         return 8 * 2 * 0.95;

4     }

5 }

 

工厂也要变一下

 1 public class DiscountFactory {

 2     public static Discount create(int count) {

 3         switch (count) {

 4             case 2:

 5                 return new Duex();

 6             case 3:

 7                 return new Trois();

 8             case 4:

 9                 return new Quatre();

10             case 5:

11                 return new Cenq();

12             default:

13                 return new None(count);

14         }

15     }

16 }

 

 

你可能感兴趣的:(职责链模式)