假设如今要设计一个麦各种书籍的电子商务汪涵的(Shoping Card)系统,一个最简单的状况就是把全部货品的单价乘上数量,可是实际状况确定要比这复杂。好比本网站可能对全部的教材类图书实行每本两元的折扣;对连环画类图书提供每本10%的促销折扣,而非教材类的计算机图书有5%的折扣;对其他书没有折扣。因为有这样复杂的折扣算法,使得价格计算问题须要系统地解决。html
那么怎么样才能解决这个问题呢?java
其实,解决方法不止一种,例如咱们能够把全部逻辑放在客户端利用条件语句判断决定使用哪种算法;也能够利用继承在子类里面实现不一样打折算法;还能够利用策略模式将环境和各类算法分开,将具体实现与客户端解耦。算法
实现这个策略的UML图以下:dom
抽象策略类(DiscountStrategy)测试
package com.strategy.booksale; /** * 抽象策略类,定义了抽象算法 * @author LLS * */ abstract public class DiscountStrategy { //抽象方法 abstract public double calculateDiscount(); }
10%的折扣促销类(PercentageStrategy)网站
package com.strategy.booksale; /** * 折扣销售图书类 * @author LLS * */ public class PercentageStrategy extends DiscountStrategy { //保存单价、数量、总额 private double percent = 0.0; private double price = 0.0; private int copies = 0; public PercentageStrategy(double price, int copies,double percent) { this.percent=percent; this.price = price; this.copies = copies; } public double getPercent() { return percent; } public void setPercent(double percent) { this.percent = percent; } //覆盖父类的抽象方法 public double calculateDiscount() { return copies * price * percent; } }
平价打折类(FlatRateStrategy)this
package com.strategy.booksale; /** * 平价销售图书,不进行打折算法类 * @author LLS * */ public class FlatRateStrategy extends DiscountStrategy { //保存图书单价、数量、总额 private double amount; private double price = 0; private int copies = 0; public FlatRateStrategy(double price, int copies) { this.price = price; this.copies = copies; } public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } //覆盖了抽象类的方法 public double calculateDiscount() { return copies * amount; } }
不打折类(NoDiscountStrategy) spa
package com.strategy.booksale;
public class NoDiscountStrategy extends DiscountStrategy
{
private double price = 0.0;
private int copies = 0;
public NoDiscountStrategy(double price, int copies)
{
this.price = price;
this.copies = copies;
}
public double calculateDiscount()
{
return price*copies;
}
}
维护抽象类的引用设计
package com.strategy.booksale;
/**
* 维护抽象策略类的引用
* @author LLS
*
*/
public class Context {
//维护策略抽象类的一个引用
DiscountStrategy discountStrategy;
//传入具体策略对象
public Context(DiscountStrategy discountStrategy)
{
this.discountStrategy=discountStrategy;
}
//根据具体策略对象调用其方法
public void ContextInterface()
{
discountStrategy.calculateDiscount();
}
}
客户端测试类(Test)code
package com.strategy.booksale; public class Test { public static void main(String[] args) { //维护抽象策略类 Context context; //采用平价打折,单价为10元,数量5本 context=new Context(new FlatRateStrategy(10, 5)); //采用百分比打折10% context=new Context(new PercentageStrategy(10, 5,0.1)); } }
这样利用策略模式已经解决了多种打折的问题,可是你有没有发现策略只是实现了在不一样打折方法或不一样算法行为之间得灵活切换,并无控制实例化哪个算法,须要用哪个优惠方式是由客户端决定的,因此客户端与具体的实现类之间耦合性很大,还须要进一步解耦。
策略模式更注重于n选1的状况,这也就是说若是我想组合几种不一样的打折策略,策略就会显得不恰当,由于你须要将多个打折方法都写到一个类里面去,为解决这种状况,能够配合装饰(Decorator)模式一块儿使用。
装饰模式适合给一个类添加额外的职责,而且对客户端透明。
咱们来看一张表示装饰模式的图,这张图即代表了它的添加功能特性也它的透明性。
你们很容器想到简单工厂,它就是一个封装产生对象的过程的类,经过传入字符串的方式决定实例化哪个类,可是它也有不足,若是咱们须要加入新的打折策略时,就须要改动工厂里面的代码,这违反了OCP原则。
咱们能够利用反射来动态决定实例化哪一个策略,配置文件和反射类以下:
配置文件设置
<?xml version="1.0" encoding="UTF-8"?>
<config>
<!-- 标签 -->
<strategy-class>
<!-- 折扣策略类 -->
<strategy id="com.strategy.booksale.DiscountStrategy" class="com.strategy.booksale.PercentateStrategy"></strategy>
</strategy-class>
</config>
反射类
package com.strategy.booksale;
import java.util.HashMap;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* 反射类
* @author LLS
*
*/
public class Reflection{
//采用饿汉式单利模式,可能占内存
private static Reflection instance=new Reflection();
//系统缺省配置文件名称
private final String sysconfig="sys-config.xml";
//保存具体策略键值对
private Map strategyMap =new HashMap();
//读取出来的document对象
private Document doc;
private Reflection()
{
try {
doc=new SAXReader().read(Thread.currentThread().getContextClassLoader().getResourceAsStream(beansConfigFile));
} catch (Exception e) {
e.printStackTrace();
}
}
//获得实例 的方法
public static Reflection getInstance() {
return instance;
}
/**
* 根据策略 编号,取得的具体策略
* @param beanId
* @return
*/
public synchronized Object getStrategyObject(Class c)
{
//判断serviceMap里面是否有service对象,没有建立,有返回
if (strategyMap.containsKey(c.getName())) {
return strategyMap.get(c.getName());
}
//返回指定ID的Element对象
Element strategyElement=(Element)doc.selectSingleNode("//strategy[@id=\""+c.getName()+"\"]");
String className=strategyElement.attributeValue("class");
Object strategyObject=null;
try {
strategyObject = Class.forName(className).newInstance();
strategyMap.put(c.getName(), strategyObject);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
return strategyObject;
}
}
改写后的客户端以下:
package com.strategy.booksale;
public class Test {
public static void main(String[] args)
{
//维护抽象策略类
Context context;
//利用反射动态决定使用哪种打折策略
DiscountStrategy discountStrategy=(DiscountStrategy)Reflection.getInstance().getStrategyObject(DiscountStrategy.class);
//客户端只须要识别策略抽象类便可,与具体的算法解耦
context=new Context(discountStrategy);
}
}
这样一来客户端彻底不知道有什么算法,也不知道该实例化哪个,减小了客户端的职责。
最后,咱们用装饰模式来解决策略不能够组合多个打折方式的不足,装饰模式的主要做用便可以给一个对象动态添加多种功能,下面是我画的类图,有了类图代码能够本身实现,让它们共同实现了同一个抽象类。
左边部分是装饰模式负责动态组合各类打折方法,右边是策略模式动态选择其中一种打折策略,之因此它们的功能能够一块儿使用,这里是由于他们实现了一个共同的接口 (Interface)。
有些问题若是咱们站在接口或抽象类的角度去考虑,用接口和抽象的方式去思考,有时问题会容易解决一些,而不要一直在某个具体的类范围内考虑问题,即把思考的角度范围扩展,从高层次考虑更容容易解决下面层次的问题。
思想上移一些,站在这个问题的更高一层。