写完简单工厂模式后,这里说说策略模式,厉害的观众已经发现了,简单工厂在有些场景非常不合适,例如市场营销的场景,商场打八折,打五折,满五百减一百,满两百减五十,不打折等。市场经常会变动这些策略,如果是简单工厂就需要每次都改动工厂类,用来添加新的策略,然后添加具体的实现类。这样每次变动都要修改项目,二次开发工作量很大,非常的不理想。
所以仔细思考。
每加一个策略,都加一个类,这样做好吗,面向对象不是类越多越好,类的划分是为了封装,但分装的基础是抽象,相同属性和功能的对象的抽象集合才是类。
这样思考发现,每个策略都是可以归类的,比如折扣类,满减类,原价类。这些类可以继承自一个促销类(父类),然后给调用者提供一个中间类,用来获得具体的策略实例,并且提供返回计算结果的方法(此方法使用策略实例执行对应的计算)。
这样调用者只需要new中间类,提供策略(促销)方式,对应的数字参数即可,通过中间类获取结果。
这样修改策略时只需要传入不同参数即可,不再需要修改代码。(添加不同策略类型的当然要修改代码,但这种变动很少,第一次开发基本就能确定有多少策略类型)
原书UML如下:
原书案例如下:
看到这里,问题来了,假如笔者是二次开发的程序员,看到封装的方法只是返回中间类对象,并且传参内容还是不好记忆的对象,传参的逻辑也要自己编写,会开喷的。
所以这里笔者参考原书又结合了简单工厂模式,将逻辑放到工厂类(结合中间类),传参改为便于记忆的数字(当然也可以是便于理解的单词)
调用类如下:
package com.gcc.strategyModel;
public class TestCash {
/**
* CashContext是策略模式和抽象工厂的组合
* CashContext的辅助方法完成向上转型后对象的方法调用,900为原价
*/
public static void main(String[] args) throws Exception {
CashContext cash1 = new CashContext(1);
double result1 = cash1.getResult(900);
System.out.println("原价为:"+result1);
CashContext cash2 = new CashContext(2);
double result2 = cash2.getResult(900);
System.out.println("折后为:"+result2);
CashContext cash3 = new CashContext(3);
double result3 = cash3.getResult(900);
System.out.println("满减后为:"+result3);
}
}
工厂和策略结合的类如下:
package com.gcc.strategyModel;
import java.util.Properties;
/**
*工厂模式和策略模式化结合
*
*/
public class CashContext {
private CashSuper cs=null;
public CashContext() {
super();
}
/**
* 抽象工厂
* @param condition
* @throws ClassNotFoundException
*/
public CashContext(int condition) throws Exception {
//规定字典1为正常,2为折扣,3为返利,通过配置文件获得数据
Properties prop = new Properties();
prop.load(CashContext.class.getResourceAsStream("cash.properties"));
double conditionNum = Double.parseDouble(prop.getProperty("conditionNum"));
double rebateNum = Double.parseDouble(prop.getProperty("rebateNum"));
double discountNum = Double.parseDouble(prop.getProperty("discountNum"));
switch (condition) {
case 1:
cs= new CashNormal();
break;
case 2:
cs=(CashSuper) CashDiscount.class//另一种反射的写法
.getConstructor(double.class)
.newInstance(discountNum);
break;
case 3:
cs=(CashSuper) Class
.forName(prop.getProperty("rebate"))
.getConstructor(double.class,double.class)
.newInstance(conditionNum,rebateNum);
break;
default:
break;
}
}
public double getResult(double money){
return cs.acceptCash(money);
}
}
这里笔者有其它的想法,因为编写代码时突然想到java反射,配合配置文件能进一步优化代码,这里举例了两种反射方式,第一种是通过类名.class然后获得构造方法再执行实例化,第二种是通过Class.forName(),参数为需要实例化的类的全路径,然后获得构造方法再执行实例化。
笔者支持前者,原因是:这样做不仅更加简单,而且更安全,它会在编译期间得到检查(后者不会)。由于它取消了对方法调用的需要,所以执行的效率也会更高。
至于参数数据,这里通过读取配置文件的方式来获得,方便修改。
另外值得注意的是这里的获取结果方法,灵活运用了java的多态,使得一个方法有多种实现方式。
配置文件cash.properties如下:
normal=com.gcc.strategyModel.CashNormal
conditionNum=500
rebateNum=100
discountNum=0.9
rebate=com.gcc.strategyModel.CashRebate
discount=com.gcc.strategyModel.CashDiscount
促销(策略)父类以及子类如下:
package com.gcc.strategyModel;
public interface CashSuper {
/**
* 实际消费现金接口方法
* @param money
* @return
*/
public double acceptCash(double money);
}
package com.gcc.strategyModel;
public class CashDiscount implements CashSuper {
private double discount;//折扣
public CashDiscount() {
super();
}
public CashDiscount(double discount){
this.discount=discount;
}
@Override
public double acceptCash(double money) {
return money*(this.discount);
}
}
package com.gcc.strategyModel;
public class CashNormal implements CashSuper {
@Override
public double acceptCash(double money) {
return money;
}
}
package com.gcc.strategyModel;
public class CashRebate implements CashSuper {
private double rebate;//返利
private double condition;//返利条件
public CashRebate() {
super();
}
public CashRebate(double condition,double rebate){
this.rebate=rebate;
this.condition=condition;
}
/**
* 例如消费800大于返利条件500,则返利100,返回实际消费额为800-100=700
*/
@Override
public double acceptCash(double money) {
if(money>=this.condition){
money=money-rebate;
}
return money;
}
}
到此完毕
no,no!
可能有人会提出,为什么中间类不用之前的简单工厂的方式,即如下方式:
package com.gcc.strategyModel;
import java.util.Properties;
/**
*工厂模式和策略模式化结合
*
*/
public class CashContextTest {
/**
* 工厂
* @param condition
* @throws ClassNotFoundException
*/
public static CashSuper createCash(int condition) throws Exception {
CashSuper cs=null;
//规定字典1为正常,2为折扣,3为返利,通过配置文件获得数据
Properties prop = new Properties();
prop.load(CashContextTest.class.getResourceAsStream("cash.properties"));
double conditionNum = Double.parseDouble(prop.getProperty("conditionNum"));
double rebateNum = Double.parseDouble(prop.getProperty("rebateNum"));
double discountNum = Double.parseDouble(prop.getProperty("discountNum"));
switch (condition) {
case 1:
cs= new CashNormal();
break;
case 2:
cs=(CashSuper) CashDiscount.class//另一种反射的写法
.getConstructor(double.class)
.newInstance(discountNum);
break;
case 3:
cs=(CashSuper) Class
.forName(prop.getProperty("rebate"))
.getConstructor(double.class,double.class)
.newInstance(conditionNum,rebateNum);
break;
default:
break;
}
return cs;
}
}
调用如下:
CashSuper cash4 = CashContextTest.createCash(2);
double result4=cash4.acceptCash(900);
System.out.println("折后为:"+result4)
这样做在笔者看来是更加便于理解,但实际上这在使用时不知不觉耦合了底层逻辑代码。
调用者需要多知道底层接口CashSuper,不符合尽量解耦的原则。
策略模式的另外巧妙的一点就在于在中间类Context里添加了可以重用的方法如getResult(double money),进一步完成了解耦。
完毕,感谢你的观看