重构到模式 - 策略模式+ 职责链模式解决图书打折的问题
今天,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 }
-----------------------------------------------------------------
现为独立咨询师。为软件企业或者其他企业的软件采购部门提供咨询,帮助改进软件开发流程,员工技术能力提升,以帮助企业在质量成本交货期三方面得到改善。