策略模式动机(Motivation)
● 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
● 如何在运行时根据需要透明的更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
1、商场收银软件
● 做一个商场收银软件,营业员根据客户购买商品单价和数量,向客户收费。
· 输入单价和数量,
· 算出每种商品的小计,
· 记录商品的清单,
· 记录总计
● 做一个商场收银软件,营业员根据客户购买商品单价和数量,向客户收费。
商场收银系统关键代码:
import java.util.Scanner;
public class Cash{
public String list = "";
public Double totalPrice = 0.00;//声明一个double变量totalPrice来计算总计
public void buttonOK(){
Scanner scan = new Scanner(System.in);
System.out.println("输入单价:");
String price = scan.nextLine();
scan = new Scanner(System..in);
System.out.println("输入数量:");
String num = scan.nextLine();
Double xiaoji = Double.parseDouble(price)*Integer.parseInt(num);//声明一个double变量xiaoji来计算每个商品的单价(price)*数量(num)后的合计
list += "单价:" + price +",数量:" + ",小计:" + xiaoji + "\n";//清单
totalPrice += xiaoji;//将每个商品小计计入总计
}
public static void main(String[] args){
Cash cash = new Cash();
boolean flag = true;
while(falg){
cash.buttonOK();
if(cash.totalPrice>10){
flag = false;
}
}
System.out.println("==========================");
System.out.println("清单:\n"+cash.list);
System.out.println("总价:"+cash.totalPrice);
}
}
Q:如果商场对商品搞活动,所有的商品打八折。
A:就在totalPrice后面乘以一个0.8
Q:商场活动结束,不打折了,还要再改一次程序代码,然后再用改过的程序去把所有机器全部安装一次
Q:商场因为周年庆,打五折,怎么变?
A:加一个下拉选择框或输入折扣就可以解决以上问题。
现在商场需求改变,商场的活动加大,需要满300返100的促销算法.
可以用简单工厂模式:先写一个父类,再继承它实现多个打折和返利子类,利用多态,完成这个代码。由需求决定写几个子类,比如八折、七折、五折、满300送100、满200送500…要几个写几个
简单工厂设计模式实现收银软件:
1、有一个先进收费抽象类CashSuper。里面有一个收取现金的方法acceptCash()
2、有3种促销方案:正常收费、打折收费、满减收费
3、工厂类,封装现金收费类
实现代码:
import java.util.Scanner;
//现金收取父类
abstract class CashSuper{
//抽象方法:收取现金,参数为原价,返回为当前价
public abstract double acceptCash(double money);
//正常收费类
class CashNormal extends CashSuper{
public double acceptCash(double money){
return money;
}
}
//打折收费类,继承CashSuper
class CashRebate extends CashSuper{
private double discount = 0.00;
//初始化时,必须要输入折扣率,如八折,就是0.8
public CashRebate(double discount){
this.discount = discount/10;
}
public double acceptCash(double money){
return this.discount*money;
}
public double getDiscount(){
return discount;
}
public void setDiscount(double discount){
this.discount = discount;
}
}
//返利收费,继承CashSuper
class CashReturn extends CashSuper{
private double baseCash;//基础金额
private double returnCash;//返利金额
//初始化时必须要输入返利条件和返利值,比如满300返100,则baseCash为300,returnCash为100
public CashReturn(double baseCash,double returnCash){
this.baseCash = baseCash;
this.returnCash = returnCash;
}
public double acceptCash(double money){
double result = money;
if(money>=baseCash)//若大于返利条件,则需要减去返利值
result=money-Math.floor(money/baseCash)*returnCash;
return result;
}
public double getBaseCash(){return baseCash;}
public void setBaseCash(double baseCash){this.baseCash = baseCash;}
public double getReturnCash(){return returnCash;}
public void setReturnCash(double returnCash){this.returnCash = returnCash;}
}
//现金收取工厂
class CashAcceptFactory{
//根据条件生成相应的对象
public static CashSuper createCashAccept(String type,double discount,double basePrice,double returnPrice){
CashSuper cs = null;
if("1".equals(type))cs = new CashNormal();
else if("2".equals(type))cs = new CashRebate(discount);
else if("3".equals(type))cs = new CashReturn(basePrice,returnPrice);
return cs;
}
}
}
//客户端程序
public class program{
public static void main(String[] args){
boolean flag = true;
String list = "";
Double totalPrice = 0.00;
while(flag){
Scanner scan = new Scanner(System.in);
System.out.println("输入单价:");
String price = scan.nextLine();
scan = new Scanner(System.in);
System.out.println("输入数量:");
String price = scan.nextLine();
scan = new Scanner(System.in);
System.out.println("输入折扣类型(1.无折扣 2.打折 3.满减):");
String price = scan.nextLine();
double discount = 0.0d;
double basePrice = 0;
double returnPrice = 0;
if("2".equals(type)){
scan = new Scanner(System.in);
System.out.println("输入折扣:");
discount = Double.parseDouble(scan.nextLine());
}
if("3".equals(type)){
scan = new Scanner(System.in);
System.out.println("返现基础金额:");
basePrice = Double.parseDouble(scan.nextLine());
scan = new Scanner(System.in);
System.out.println("返现金额:");
returnPrice = Double.parseDouble(scan.nextLine());
}
Double xianjin = Double.parseDouble(price)*Integer.parseInt(num);
CashSuper cs = CashAcceptFactory.createCashAccept(type,discount,basePrice,returnPrice);
double xiaoji = cs.acceptCash(xianjin);
list += "单价:" + price + ",数量:" + num + ",折扣:" + discount + ",小计:" + xiaoji + "\n";
totaPrice += xiaoji;
if(totaPrice>10){
flag = false;
}
}
System.out.println(list);
System.out.println("总价:" + totalPrice);
}
}
Q:如果现在需要增加一种商场促销手段,满100积分10点,以后积分到一定时候可以领取奖品。
A:加一个积分类,构造方法有两个参数:条件和返点,让它继承CashSuper,工厂也增加满100积分10点的分支条件。
简单工厂模式的缺陷:
● 折扣可能经常会变。
● 商场可能经常性的更改打折额度和返利额度。
每次维护或扩展收费方式都要改动这个工厂,以至于代码需要重新编译部署,不符合开闭原则,所以不是最好的方法。
改造现金收取工厂
//现金收取工厂
class CashAcceptFactory{
//根据条件生成响应的对象
public static CashSuper createCashAccept(String type,double discount,double basePrice,double returnPrice){
CashSuper cs = null;
if("1".equals(type))cs = new CashNormal();
if("2".equals(type))cs = new CashNormal(discount);
if("3".equals(type))cs = new CashNormal(basePrice,returnPrice);
return cs;//现金收取控制类
}
}
class CashContest{
private CashSuper cs;//声明一个现金收费父类对象
public CashContext(CashSuper csuper){
//通过构造方法,传入具体的收费策略对象(正常,打折或返利)
this.cs = csuper;
}
public double GetResult(double money){
//根据不同收费策略,获得合计结果
return cs.acceptCash(money);
}
}
每次维护或扩展收费方式都要改动工厂是因为工厂与具体的收费方式类耦合。
如果面对算法的市场变动,不希望改动这个工厂(即现金收取控制类),则现金收取控制类中就不能出现具体的收费方式类,所以用它们的父类CashSuper代替,并为其传入具体的收费方式对象。
改造后的客户端代码:
public class program{
public static void main(String[] args){
boolean flag = true;
String list = "";
Double totalPrice = 0.00;
while(flag){
Scanner scan = new Scanner(System.in);
System.out.println("输入单价:");
String price = scan.nextLine();
scan = new Scanner(System.in);
System.out.println("输入数量:");
String price = scan.nextLine();
scan = new Scanner(System.in);
System.out.println("输入折扣类型(1 无折扣 2打折 3满减):");
String price = scan.nextLine();
double discount = 0.0d;
double basePrice = 0;
double returnPrice = 0;
CashSuper cs = null;
if("1".equals(type))cs = new CashNormal();
else if("2".equals(type)){
scan = new Scanner(System.in);
System.out.println("输入折扣:");
discount = Double.parseDouble(scan.nextLine());
cs = new CashRebate(discount);
}
else if("3".equals(type)){
scan = new Scanner(System.in);
System.out.println("返现基础金额:");
basePrice = Double.parseDouble(scan.nextLine());
scan = new Scanner(System.in);
System.out.println("返现金额:");
returnPrice = Double.parseDouble(scan.nextLine());
cs = new CashReturn (basePrice,returnPrice);
}
Double xianjin = Double.parseDouble(price)*Integer.parseInt(num);
CashContext cc = new CashContext(cs);
//将相应的策略对象作为参数传入CashContext构造函数中
double xiaoji = cs.GetResult(xianjin);
list += "单价:" + price + ",数量:" + num + ",折扣:" + discount + ",小计:" + xiaoji + "\n";
totaPrice += xiaoji;
if(totaPrice>10){
flag = false;
}
}
System.out.println(list);
System.out.println("总价:" + totalPrice);
}
}
改造之后的收银UML图:
策略模式(与简单工厂类似)
● 策略模式定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的用户。
● 策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
策略模式的结构图:
模式涉及到三个角色:
● 环境(Context)角色:持有一个Strategy类的引用(上下文对象),负责和具体的策略类监护。
● 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个借口或抽象类实现。此角色给出所有的具体策略类所需的接口。
● 具体策略(ConcreteStrategy)角色:封装了具体的算法或行为。
策略模式基本代码:
//Strategy类,定义所有支持的算法的公共接口
abstract class Strategy
{
public abstract void algorithmInterface();
}
//ConcreteStrategy封装了具体的算法或行为,继承于Strategy
class ConcreteStrategyA extends Strategy
{
public void algorithmInterface()
{
System.out.println("算法A实现");
}
}
class ConcreteStrategyB extends Strategy
{
public void algorithmInterface()
{
System.out.println("算法B实现");
}
}
class ConcreteStrategyC extends Strategy
{
public void algorithmInterface()
{
System.out.println("算法C实现");
}
}
//Context用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用
classContext
{
Strategy strategy;
public Context(Strategy strategy)
//初始化时,传入具体的策略对象
{
this.strategy = strategy;
}
public void contextInterface()
{
strategy.algorithmInterface();
//根据具体的策略对象,调用其方法
}
}
//客户端代码
public class program{
public static void main(String[] args){
Context context;
context = new Context(new ConcreteStrategyA());
context.contextInterface();
context = new Context(new ConcreteStrategyB());
context.contextInterface();
context = new Context(new ConcreteStrategyC());
context.contextInterface();
}
}
收银软件中:
● CashAuper就是抽象策略
● 正常收费CashNormal\打折收费CashRebate和返利收费CashReturn就是三个具体策略,也就是策略模式中说的具体算法
● 改造的现金收取控制类CashContext就是环境角色
● 客户端
● 简单工厂模式:传条件进工厂类,工厂类就会返回一个对象调用者,供调用者使用——最终给用户使用的是工厂类返回的类
● 策略模式:创建一个Context类(可以看做是工厂模式中工厂类的替代品)的对象context,传一个要使用的策略实例对象A给context,然后使用context调用A中的某些方法——最终给用户用的是Context类。
● 简单地说:
◇ 工厂有进货也有出货,然后使用出货。
◇ 策略有进货没出货,然后使用得货者。
使用策略模式的情况:
● 当一个系统中有许多类,它们之间的区别仅在于它们的行为,希望动态地让一个对象在许多行为中选择一种行为时;
● 当一个系统需要动态地在集中算法中选择一种时;
● 当一个对象有很多的行为,不想使用多重的条件选择语句来选择使用哪个行为时。
策略模式的优点:
1、策略模式中策略类都实现同一个接口,所以使得他们之间可以自由切换。
2、避免使用多重条件选择语句(if else),充分体现面向对象实际思想。
3、扩展性良好。
策略模式的缺点:
1、把分支判断放回到了客户端,改变算法的同时还要更改客户端的程序
2、缺乏工厂模式的优势,在客户端要进行判断
3、策略类增多。
本质:分离算法,选择实现。