java通过设计模式简化代码(SPI机制)
设计模式原则(本次使用到的)
面向接口编程
模板方法
简单工厂模式
javaSPI机制
前闭后开原则
业务背景
现在有普通片,儿童片,新片,这几种类型的影片,用户可以选择需要租赁的天数和个数,然后程序自动结算出价格
传统开发模式
switch (movieType) {
case 'COMMON':
// 计算价格方法
break;
case 'CHILDREN':
// 计算价格方法
break;
case 'NEW':
// 计算价格方法
break;
default :
throw new Exception("无法获取影片类型");
}
思考问题:通过传统的方式写,有哪些优缺点
优点:
代码逻辑简单,一眼能看懂
缺点:
维护困难:如果需求变更,需要添加一个影片类型,那需要对之前的代码进行修改,有修改,就有bug的风险
改进之后的模式
定义一个接口,以后所有的影片类型都实现这个接口,去完成自己独特的价格计算
package com.entity;
public class Movie {
/**
* 影片名称
*/
private String movieName;
/**
* 影片价格
*/
private Double moviePrice;
/**
* 影片库存
*/
private Integer movieInventory;
/**
* 影片类型;1:普通篇;2:儿童片;3:新片
*/
private Integer movieType;
/**
* 租的天数
*/
private Double leaseDay;
/**
* 租几部
* @return
*/
private Integer leaseNumber;
public String getMovieName() {
return movieName;
}
public void setMovieName(String movieName) {
this.movieName = movieName;
}
public Double getMoviePrice() {
return moviePrice;
}
public void setMoviePrice(Double moviePrice) {
this.moviePrice = moviePrice;
}
public Integer getMovieInventory() {
return movieInventory;
}
public void setMovieInventory(Integer movieInventory) {
this.movieInventory = movieInventory;
}
public Integer getMovieType() {
return movieType;
}
public void setMovieType(Integer movieType) {
this.movieType = movieType;
}
public Double getLeaseDay() {
return leaseDay;
}
public void setLeaseDay(Double leaseDay) {
this.leaseDay = leaseDay;
}
public Integer getLeaseNumber() {
return leaseNumber;
}
public void setLeaseNumber(Integer leaseNumber) {
this.leaseNumber = leaseNumber;
}
@Override
public String toString() {
return "Movie{" +
"movieName='" + movieName + '\'' +
", moviePrice=" + moviePrice +
", movieInventory=" + movieInventory +
", movieType=" + movieType +
", leaseDay=" + leaseDay +
", leaseNumber=" + leaseNumber +
'}';
}
}
package com.movie;
import com.entity.Movie;
/**
* 影片的计算价格接口
*/
public interface IMovePrice {
/**
* 计算价格
* @param movie 影片对象
* @return
*/
double calculatePriceByMovie(Movie movie);
}
package com.movie;
import com.entity.Movie;
public class ChildrenMoviePrice implements IMovePrice {
@Override
public double calculatePriceByMovie(Movie movie) {
double value = movie.getLeaseDay() * 4 + movie.getLeaseNumber() * 2.5;
System.out.println("租了儿童片:" + movie.getLeaseDay() + "天,租了" + movie.getLeaseNumber() + "部,价格为:" + value);
return value;
}
}
package com.movie;
import com.entity.Movie;
public class CommonMoviePrice implements IMovePrice {
@Override
public double calculatePriceByMovie(Movie movie) {
double value = movie.getLeaseDay() * 2 + movie.getLeaseNumber() * 1.5;
System.out.println("租了普通片:" + movie.getLeaseDay() + "天,租了" + movie.getLeaseNumber() + "部,价格为:" + value);
return value;
}
}
package com.movie;
import com.entity.Movie;
public class NewMoviePrice implements IMovePrice {
@Override
public double calculatePriceByMovie(Movie movie) {
double value = movie.getLeaseDay() * 3 + movie.getLeaseNumber() * 4.5;
System.out.println("租了新片:" + movie.getLeaseDay() + "天,租了" + movie.getLeaseNumber() + "部,价格为:" + value);
return value;
}
}
我们加入了三种影片类型,分别实现接口IMovePrice,去完成各自影片类型的价格计算
package com.movie;
import com.entity.Movie;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
public class MoviePriceFactory {
private IMovePrice movePrice;
private static Map<String,String> propertiesMap = new HashMap<>();
// 是不是很熟悉,springioc也是通过set方式注入的
public void setMovePrice(IMovePrice movePrice) {
this.movePrice = movePrice;
}
public MoviePriceFactory() {}
public MoviePriceFactory(IMovePrice movePrice) {
this.movePrice = movePrice;
}
// 调用方法,计算价格
public double getMoviePrice (Movie movie) {
return movePrice.calculatePriceByMovie(movie);
}
// 读取movieprice.properties文件内容,得到Map<String,String>
// key : 影片类型的Key;value:对应的接口实现类
public static Map<String,String> getPropertiesMap () throws IOException {
if (propertiesMap != null && propertiesMap.size() > 0) {
return propertiesMap;
}
Properties properties = new Properties();
properties.load(MoviePriceFactory.class.getClassLoader().getResourceAsStream("movieprice.properties"));
Iterator<Map.Entry<Object, Object>> iterator = properties.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Object, Object> next = iterator.next();
propertiesMap.put(next.getKey().toString(), next.getValue().toString());
}
return propertiesMap;
}
// 通过影片类型得到对应的接口对象
public static IMovePrice getIMovePriceMethod (String movieType) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
String implName = getPropertiesMap().get(movieType);
Class c = Class.forName(implName);
IMovePrice movePrice = (IMovePrice)c.newInstance();
return movePrice;
}
}
在这里,加入了movieprice.properties,维护影片类型key和对象实现的关系,后期如果有新的影片类型,我只需要实现IMovePrice接口,编写对应的计算逻辑,然后在movieprice.properties中加入对应关系即可,不需要再修改其他代码,我们做的都是扩展!!!
COMMONMOVIEPRICE=com.movie.CommonMoviePrice
CHILDRENMOVIEPRICE=com.movie.ChildrenMoviePrice
NEWMOVIEPRICE=com.movie.NewMoviePrice
package com.movie;
import com.entity.Movie;
import java.io.IOException;
public class TestJava {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
Movie movie = new Movie();
movie.setLeaseDay(2d);
movie.setLeaseNumber(2);
// 将获取对应的影片对象交给用户,是不是很熟悉,看到了springioc控制反转的影子
MoviePriceFactory moviePriceFactory = new MoviePriceFactory(MoviePriceFactory.getIMovePriceMethod("CHILDRENMOVIEPRICE"));
System.out.println(moviePriceFactory.getMoviePrice(movie));
}
}
传统的模式中,我们是程序通过一些判断完成逻辑,而改进之后,我们把决定权交给了调用者(用户)