从一个例子的重构过程到Spring的IoC之使用
IoC (反向控制)是Sping的核心,用以实现系统对象的松耦合,使用IoC ,对象是被动接收依赖类而不是主动去找,下面将结合一个订单,折扣,价格的例子来说明IoC的优势.看看它是怎么降低系统对象的耦合的.
1.关于订单,折扣,价格的说明
订单是货物的订单,主要货物有计算机,文具,纸张三类,还有其它一些物件.每种货物都有不同的折扣比例,从单价中扣除折扣比例后就是进货时的真实价格.
下面将使用各种例子实现这一过程.
2.传统的翻译式的处理:
这样的代码新手常些,老手在快速编程中也难以避免,认识到这段代码是否需要重构,如何进行重构是二者的最大区别.
// 订单类
public class Order{
public static final String Type_Computer="Computer";
public static final String Type_Stationary="Stationary";
public static final String Type_Paper="Paper";
private String type;
private float price;
public Order(float price,String type){
this.price=price;
this.type=type;
}
public Order(){
this(100.0f,"");
}
public float getPrice() {
float discount=0.0f;
// 真实价格的计算直接写在了订单类的方法中
if(Type_Computer.equals(type)){
discount=0.3f;
}
else if(Type_Stationary.equals(type)){
discount=0.2f;
}
else if(Type_Paper.equals(type)){
discount=0.1f;
}
return price*(1.0f-discount);
}
}
// 运行
Order computerOrder=new Order(100.0f,Order.Type_Computer);
System.out.println("computerOrder's price is /t"+computerOrder.getPrice());
Order stationaryOrder=new Order(100.0f,Order.Type_Stationary);
System.out.println("stationaryOrder's price is /t"+stationaryOrder.getPrice());
Order paperOrder=new Order(100.0f,Order.Type_Paper);
System.out.println("paperOrder's price is /t/t"+paperOrder.getPrice());
Order otherOrder=new Order();
System.out.println("otherOrder's price is /t/t"+otherOrder.getPrice());
// 输出情况
computerOrder's price is 70.0
stationaryOrder's price is 80.0
paperOrder's price is 90.0
otherOrder's price is 100.0
优势:直白易懂,基本就是把用户的语言描述改写成代码,适用于小规模无变化的项目.
劣势:计算价格的代码属于硬编码,如果规模变大,则判断将会变得冗长难懂,如果产生变化,如增加新的类型的货物,货物折扣发生变化等,将不容易增加新的功能,修改时将容易相互影响.
3.使用简单类工厂进行重构解耦的方案(工厂模式)
// 折扣类
public interface Discount{
public float getDiscount();
}
// 计算机货物的折扣类
public class ComputerDiscount implements Discount{
public float getDiscount(){
return 0.3f;
}
}
// 文具折扣类
public class StationaryDiscount implements Discount{
public float getDiscount(){
return 0.2f;
}
}
// 纸张折扣类
public class PaperDiscount implements Discount{
public float getDiscount(){
return 0.1f;
}
}
// 其它货物折扣类
public class OtherDiscount implements Discount{
public float getDiscount(){
return 0.0f;
}
}
// 用于计算折扣的工厂
public class DiscountFactory{
public static float getDiscount(String type){
Discount discount=null;
// 转移过来的判断过程
if(Order.Type_Computer.equals(type)){
discount=new ComputerDiscount();
}
else if(Order.Type_Stationary.equals(type)){
discount=new StationaryDiscount();
}
else if(Order.Type_Paper.equals(type)){
discount=new PaperDiscount();
}
else{
discount=new OtherDiscount();
}
return discount.getDiscount();
}
}
// 订单类
public class Order{
public static final String Type_Computer="Computer";
public static final String Type_Stationary="Stationary";
public static final String Type_Paper="Paper";
private String type;
private float price;
public Order(float price,String type){
this.price=price;
this.type=type;
}
public Order(){
this(100.0f,"");
}
public float getPrice() {
float discount=DiscountFactory.getDiscount(this.type);
return price*(1.0f-discount);
}
}
输出的结果还是:
computerOrder's price is 70.0
stationaryOrder's price is 80.0
paperOrder's price is 90.0
otherOrder's price is 100.0
优势:归纳出了折扣接口及其四个子类,细分程度比上个方案高,修改折扣时不容易相互影响.
劣势:订单类Order还是和DiscountFactory类耦合在一起,使用IF进行判断的硬编码依然存在,增加新的货物折扣类时还是容易相互影响.
4.利用反射进行解耦的方案(命令模式)
// 折扣类
public interface Discount{
public float getDiscount();
}
// 计算机货物的折扣类
public class ComputerDiscount implements Discount{
public float getDiscount(){
return 0.3f;
}
}
// 文具折扣类
public class StationaryDiscount implements Discount{
public float getDiscount(){
return 0.2f;
}
}
// 纸张折扣类
public class PaperDiscount implements Discount{
public float getDiscount(){
return 0.1f;
}
}
// 其它货物折扣类
public class OtherDiscount implements Discount{
public float getDiscount(){
return 0.0f;
}
}
// 订单类
public class Order{
public static final String Type_Computer="Computer";
public static final String Type_Stationary="Stationary";
public static final String Type_Paper="Paper";
private String type;
private float price;
public Order(float price,String type){
this.price=price;
this.type=type;
}
public Order(){
this(100.0f,"Other");
}
public float getPrice() {
Discount discount = null;
String className="discount."+type+"Discount";
try{
Class cls=Class.forName(className);
discount=(Discount)cls.newInstance();// 由类得到类实例
}
catch(Exception ex){
ex.printStackTrace();
}
return price*(1.0f-discount.getDiscount());
}
}
// 输出如下:
computerOrder's price is 70.0
stationaryOrder's price is 80.0
paperOrder's price is 90.0
otherOrder's price is 100.0
优势:使用反射替代了IF判断,这有效消除了增加新货物折扣的相互影响.
劣势:如果要修改折扣大小,必须由程序员来修改类文件,这是不适应经常变化的,变化还会带来一系列的测试,编译,发布等事情.
到这里,如果不使用IoC,重构就基本结束了,相对而言,用反射方式较好些.
但是使用了IoC,我们将有更佳的选择.
5.使用Spring的Ioc进行解耦的方案
// 折扣类
public interface Discount{
public float getDiscount();
}
// 计算机货物的折扣类
public class ComputerDiscount implements Discount{
public float getDiscount(){
return 0.3f;
}
}
// 文具折扣类
public class StationaryDiscount implements Discount{
public float getDiscount(){
return 0.2f;
}
}
// 纸张折扣类
public class PaperDiscount implements Discount{
public float getDiscount(){
return 0.1f;
}
}
// 其它货物折扣类
public class OtherDiscount implements Discount{
public float getDiscount(){
return 0.0f;
}
}
// Order类
public class Order{
public static final String Type_Computer="Computer";
public static final String Type_Stationary="Stationary";
public static final String Type_Paper="Paper";
private String type;
private float price;
public Order(float price,String type){
this.price=price;
this.type=type;
}
public Order(){
this(100.0f,"Other");
}
public float getPrice() {
ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
Discount discount = null;
discount = (Discount) ctx.getBean(this.type);
return price*(1.0f-discount.getDiscount());
}
}
// 配置文件Bean.xml
// 输出如下
computerOrder's price is 70.0
stationaryOrder's price is 80.0
paperOrder's price is 90.0
otherOrder's price is 100.0
优势:使用Spring的IoC代替了反射,易懂而不容易出错.
劣势:和使用反射的情况一致.
6.进一步使用Ioc进行解耦的方案
// 折扣接口
public interface Discount{
public float getDiscount();
public void setDiscount(float discount);
}
// 计算机折扣类
public class ComputerDiscount implements Discount{
private float discount=0.0f;
public float getDiscount(){
return discount;
}
public void setDiscount(float discount){
this.discount=discount;
}
}
// 文具折扣类
public class StationaryDiscount implements Discount{
private float discount=0.0f;
public float getDiscount(){
return discount;
}
public void setDiscount(float discount){
this.discount=discount;
}
}
// 纸张折扣类
public class PaperDiscount implements Discount{
private float discount=0.0f;
public float getDiscount(){
return discount;
}
public void setDiscount(float discount){
this.discount=discount;
}
}
// 其它折扣类
public class OtherDiscount implements Discount{
private float discount=0.0f;
public float getDiscount(){
return discount;
}
public void setDiscount(float discount){
this.discount=discount;
}
}
// Order类
public class Order{
public static final String Type_Computer="Computer";
public static final String Type_Stationary="Stationary";
public static final String Type_Paper="Paper";
private String type;
private float price;
public Order(float price,String type){
this.price=price;
this.type=type;
}
public Order(){
this(100.0f,"Other");
}
public float getPrice() {
ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
Discount discount = null;
discount = (Discount) ctx.getBean(this.type);
return price*(1.0f-discount.getDiscount());
}
}
// 配置文件bean.xml
0.3
0.2
0.1
0.05
输出:
computerOrder's price is 70.0
stationaryOrder's price is 80.0
paperOrder's price is 90.0
otherOrder's price is 95.0
优势:对于折扣数额变化不需要修改类文件,也没有修改类文件的带来的系列问题.
劣势:增加新类时仍需配置xml文件,没有达到完全的即插即用.这也许是我没有学到,也许Spring也不能解决,还有一段路要走.
IoC的根在于反射,其它使用XMl文件进行配置的框架如Struts,Hibernate也在反射上做了很多文章,可以说不充分理解和运用反射就难以把握现代框架,永远是一个使用者.
可以说反射,面向接口等是java界的大道,某个API的运用,某个效果的实现只是旁门左道,对于一个有志于站在时代风口浪尖的程序员应该在大道上多花功夫.