现在有一个功能,试想一下你会怎么做。有一个用餐的方法,每个人想要用餐,都要做家务,根据做的家务的好坏程度,来决定有什么样的饭吃。要实现这个功能你会怎么设计?
是否可以这么做,将家务提取成一个接口,类似于
public interface Housework{
/**
*洗碗
*@return 完成等级
*/
public CompletionLevel washDishes();
/**
*洗碗
*@return 完成等级
*/
public CompletionLevel mopFloor();
}
然后用餐的方法为:
pubic void eat(Housework housework){
//TODO 根据完成等级来决定用餐类型
}
当然上面就是一个简单的例子,来看看JDK中自带的经典例子吧——Thread类,和Runnable接口,学过java的人都知道,Thread是一个java用来操作线程的类。而且实现线程的其中两种方式就是:1.继承Thread类,重写run()方法;
public class MyThread extends Thread{
@Override
public void run(){
//TODO 线程要做的事情
}
}
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程序中非常重要的两个原则:
- 封装变化的概念
- 面向接口编程(编程中使用接口,而不是具体实现类)
具体案例
一个网上商城,需要根据不同的用户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 通过反射去获取策略
}
}
更为推荐第二种方式吧。
以上的代码都是我参考了网上的一些经典案例,然后自己写伪代码写出来的。可能有许多不足之处。以后慢慢改进补充