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

今天,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 }

 

 

posted @ 2013-07-04 11:57  史蒂芬.王  阅读(732)  评论(4编辑  收藏  举报