• 重构到模式


    今天,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 }
  • 相关阅读:
    异常处理 Exception
    C#使用SQLite出错:无法加载 DLL“SQLite.Interop.dll”,找不到指定的模块
    NullableKey:解决Dictionary中键不能为null的问题 zt
    STSdb
    C# 如何获取某个类型或类型实例对象的大小
    CORREL
    C# 深复制
    mysql数据库创建函数过程
    mysql 数据库怎样快速的复制表以及表中的数据
    代码优化(一)
  • 原文地址:https://www.cnblogs.com/stephen-wang/p/3171309.html
Copyright © 2020-2023  润新知