现在有一个功能,试想一下你会怎么做。有一个用餐的方法,每个人想要用餐,都要做家务,根据做的家务的好坏程度,来决定有什么样的饭吃。要实现这个功能你会怎么设计?
是否可以这么做,将家务提取成一个接口,类似于

  1. public interface Housework{
  2. /**
  3. *洗碗
  4. *@return 完成等级
  5. */
  6. public CompletionLevel washDishes();
  7. /**
  8. *洗碗
  9. *@return 完成等级
  10. */
  11. public CompletionLevel mopFloor();
  12. }

然后用餐的方法为:

  1. pubic void eat(Housework housework){
  2. //TODO 根据完成等级来决定用餐类型
  3. }

当然上面就是一个简单的例子,来看看JDK中自带的经典例子吧——Thread类,和Runnable接口,学过java的人都知道,Thread是一个java用来操作线程的类。而且实现线程的其中两种方式就是:1.继承Thread类,重写run()方法;

  1. public class MyThread extends Thread{
  2. @Override
  3. public void run(){
  4. //TODO 线程要做的事情
  5. }
  6. }

2.实现Runnable接口。

public class MyThread implements Runnable{

    @Override
    public void run(){
    //TODO 线程要做的事情
    }

}

仔细想想上面的例子,run方法,是Runnable定义的,意思是线程要做的事情,但是Runnable没有规定线程一定要做什么事,只是说,既然你要用线程,那行,那你就必须实现run()方法。我本身不关心,你需要怎么做,你写你自己的实现。
而具体要启动线程的时候

Thread t1 = new Thread(new MyThread());
t1.start();

只需要在Thread的构造器中传入任意实现了Runnable的类,就可以通过start()方法启动一个线程做run()方法里的事情。
其实这种提供一个标准接口,剩下算法有实现类提供实现的做法,的本身就是一种设计模式叫策略模式
**

策略模式(Strategy Pattern)

策略模式比较标准的定义:

定义一系列的算法,把它们一个个封装起来,并且使他们可相互替换。
本模式使得算法的变化可以独立于使用它的客户。
(Define a family of algorithms,encapsulate each one and make them interchangeable.Strategy lets the algorithmvary independently from clients that use it.)

通俗的来讲:在java中策略模式就是为了解决某一个问题的一堆相似算法,它们实现了同一个接口,并且易于维护拓展更多的算法,由客户端来选择要使用哪一种算法。

常见的场景:

  • 一个代码中有大量if-else。或者switch case 语句的时候,可以考虑用策略模式

策略模式的好处:

  • 开闭原则,只需要添加相关策略类就行
  • 避免使用多重条件转移语句

策略模式体现了java程序中非常重要的两个原则:

  1. 封装变化的概念
  2. 面向接口编程(编程中使用接口,而不是具体实现类)

具体案例

一个网上商城,需要根据不同的用户VIP级别类型,对用户进行打折。
通常的做法,先定义一个枚举,来设置保存用户类型

public enum UserVIPTypeEnum {
    /** 普通用户 */
    ORDINARY("0","普通用户"),
    /** VIP1 */
    VIP1("1","VIP1"),
    /** VIP2 */
    VIP2("2","VIP2"),
    /** VIP1 */
    VIP3("3","VIP3"),
    /** VIP1 */
    VIP4("4","VIP4"),
    ;
}

然后在代码中,也许会这么一个获取折扣的方法

//根据用户vip类型获取折扣
public double getDiscount(UserVIPTypeEnum vipType){
    switch(vipType){
        case ORDINARY:
            return 1;
        case VIP1:
            return 0.9;
        case VIP2:
            return 0.8;
        case VIP3:
            return 0.7;
        case VIP4:
            return 0.6;
        default:
            return 1;
    }
}

然后每次加一个vip类型,加一个case
上面这种做法,每次加了一个VIP类型(参考鹅厂的超级会员等级),都要修改原来的方法,非常不灵活且不符合开闭原则。

所以可以用策略模式,来优化这种代码。

获取折扣无非就是每个VIP都要做的一个公共方法。可以将获取折扣值,提取成一个接口,这个就是策略接口。

public interface UserDiscountStrategy{

    //获取用户折扣,当然这个方法还可以传一些计算折扣的必要参数,例如历史消费金额
    public double getDiscount(BigDecimal totalHisConsumeAmount);
}

现在策略接口写好了,就需要写不同VIP的折扣策略实现类

public class OrdinaryUserDiscount implements UserDiscountStrategy{
    @Override
    public double getDiscount(BigDecimal totalHisConsumeAmount){
        //TODO 计算折扣
        return xxx;
    }
}
public class VIP1Discount implements UserDiscountStrategy{
    @Override
    public double getDiscount(BigDecimal totalHisConsumeAmount){
        //TODO 计算折扣
        return xxx;
    }
}

诸如此类,每加一个vip类型,就加一个折扣获取的实现。

那么问题来了
具体这些折扣类,怎么用,在哪里获取?
解决这个问题可以有两种做法,一种是在一个静态的map容器中,将每个策略对象放进去,就像这样

public class DiscountHandle{
    private DiscountHandle(){}  
    private static Map<String,UserDiscountStrategy> map = new hashMap<>();
    static{
        map.put("0",new OrdinaryUserDiscount());
        map.put("1",new VIP1UserDiscount());
        map.put("2",new VIP2Discount());
        ...
    }

    public static UserDiscountStrategy getDiscountStrategy(String userVipType){
        if(!map.containsKey(userVipType)){
            //TODO 可以抛出异常,或者可以给个默认折扣策略
        }
        return map.get(userVipType);
    }

}

这种做法,可能需要注意并发,不要在策略实现类中使用全局变量就好。因为方法中的变量保存在栈帧中,问题不大。(当然这个也只是初步设想的伪代码,具体的还是要考虑好并发问题)

第二种更灵活的就是将策略写在配置文件里,通过反射去创建具体策略。

user-vip-type:
    0: com.edu.strategy.OrdinaryUserDiscount
  1: com.edu.strategy.VIP1Discount
  2: com.edu.strategy.VIP2Discount
public class DiscountHandle{
    private DiscountHandle(){}  
    public static UserDiscountStrategy getDiscountStrategy(String userVipType){
        //根据传入参数获取配置文件内容
        String className = PropertyUtils.getValue("user-vip-type." + userVipType);
        if(StringUtils.isBlank(className)){
            //TODO 可以抛出异常,或者可以给个默认折扣策略
        }
    }

    private static UserDiscountStrategy getDiscountStrategyByClassName(String className){
        //TODO 根据className 通过反射去获取策略
    }

}

更为推荐第二种方式吧。

以上的代码都是我参考了网上的一些经典案例,然后自己写伪代码写出来的。可能有许多不足之处。以后慢慢改进补充