###什么是重构?
基本上我们开发的时候,产品天天改需求,而且每次改动的需求可能会导致很多的代码实现要重新修改,没办法我们只能继续安静的去分析需求,很多时候很快代码就敲出来了,但是呢,开始构建代码的时候我们很多时候没有考虑到代码结构是否 OK?是否要调整了?是否需要优化?会不会出现很不可思议的 bug?(还有一个是我自己觉得的,就是我感觉重构之后的代码看起来很简洁、清楚,而且很装逼!哈哈)
总结:需求的共性,有什么共同特点 (预知性),总之从自身找原因,宁可花很长的时间去思考,也不要短时间内去开发。
###什么时候需要重构?
###重构的难点在哪里?
###如何来实施代码重构
注:推荐大家可以看看《重构改善既有代码的设计》
####创建普通的 maven 项目,导入以下 jar 包
mysql
mysql-connector-java
8.0.13
com.alibaba
fastjson
1.2.54
####建立商品实体对象
package com.restructure.demo;
import java.io.Serializable;
/**
* 商品
*/
public class ShopDO implements Serializable {
private static final long serialVersionUID = 5370587994637698136L;
/**
* 商品ID
*/
private Long id;
/**
* 商品名称
*/
private String shopName;
/**
* 商品价格
*/
private Long shopPrice;
/**
* 商品可售库存
*/
private Integer number;
/**
* 出货口
*/
private Integer port;
/**
* 出货口状态
*/
private Integer portStatus;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getShopName() {
return shopName;
}
public void setShopName(String shopName) {
this.shopName = shopName;
}
public Long getShopPrice() {
return shopPrice;
}
public void setShopPrice(Long shopPrice) {
this.shopPrice = shopPrice;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Integer getPortStatus() {
return portStatus;
}
public void setPortStatus(Integer portStatus) {
this.portStatus = portStatus;
}
}
package com.restructure.demo.demo1.common;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class DBHelper1 {
public static final String url = "jdbc:mysql://localhost:3306/restructure?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true";
public static final String name = "com.mysql.cj.jdbc.Driver";
public static final String user = "root";
public static final String password = "root";
public Connection conn = null;
public PreparedStatement pst = null;
public DBHelper1(String sql) {
try {
//指定连接类型
Class.forName(name);
//获取连接
conn = DriverManager.getConnection(url, user, password);
//准备执行语句
pst = conn.prepareStatement(sql);
} catch (Exception e) {
e.printStackTrace();
}
}
public void close() {
try {
this.conn.close();
this.pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
####编写实现类
package com.restructure.demo.demo1;
import com.restructure.demo.demo1.common.DBHelper1;
import com.restructure.demo.ShopDO;
import java.sql.ResultSet;
/**
* 自动售卖机
*/
public class VendingMachine1 {
private static DBHelper1 dBHelper1 =null;
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
vendingMachine("哈比",10L,2);
}
/**
* 得到商品
* @param userName 用户
* @param price 钱
* @param port 出货口
* @return
*/
public static void vendingMachine(String userName, long price, int port) throws Exception {
//获取商品
ShopDO shopDO=getShopByPort(port);
//判断价格 & 库存
if(shopDO==null || price < shopDO.getShopPrice() || shopDO.getNumber() <=0){
System.out.println("该出货口暂无商品可销售");
return;
}
//扣减库存
deductionShopById(shopDO.getId());
ShopDO shopDO1=getShopByShopId(shopDO.getId());
System.out.println("==================================================");
System.out.println("购买用户:"+userName);
System.out.println("投入金额:"+price+"元");
System.out.println("出 货 口:"+port);
System.out.println("购买商品:"+shopDO.getShopName());
System.out.println("商品价格:"+shopDO.getShopPrice()+"元");
System.out.println("找 零:"+(price-shopDO.getShopPrice())+"元");
System.out.println("原 库 存:"+shopDO.getNumber());
System.out.println("剩余库存:"+shopDO1.getNumber());
System.out.println("==================================================");
}
public static ShopDO getShopByPort(int port) throws Exception {
ShopDO shopDO=new ShopDO();
String sql = "SELECT * FROM shop WHERE port_status =1 and port ="+port;
dBHelper1 = new DBHelper1(sql);
ResultSet ret = dBHelper1.pst.executeQuery();
while (ret.next()) {
shopDO.setId(Long.parseLong(ret.getString("id")));
shopDO.setShopName(ret.getString("shop_name"));
shopDO.setShopPrice(Long.parseLong(ret.getString("sale_price")));
shopDO.setNumber(Integer.parseInt(ret.getString("number")));
shopDO.setPort(Integer.parseInt(ret.getString("port")));
shopDO.setPortStatus(Integer.parseInt(ret.getString("port_status")));
}
ret.close();
dBHelper1.close();//关闭连接
return shopDO;
}
public static ShopDO getShopByShopId(Long shopId) throws Exception {
ShopDO shopDO=new ShopDO();
String sql = "SELECT * FROM shop where id="+shopId;
dBHelper1 = new DBHelper1(sql);
ResultSet ret = dBHelper1.pst.executeQuery();
while (ret.next()) {
shopDO.setId(Long.parseLong(ret.getString("id")));
shopDO.setShopName(ret.getString("shop_name"));
shopDO.setShopPrice(Long.parseLong(ret.getString("sale_price")));
shopDO.setNumber(Integer.parseInt(ret.getString("number")));
shopDO.setPort(Integer.parseInt(ret.getString("port")));
shopDO.setPortStatus(Integer.parseInt(ret.getString("port_status")));
}
ret.close();
dBHelper1.close();//关闭连接
return shopDO;
}
public static boolean deductionShopById(Long shopId) throws Exception {
String sql = "UPDATE shop SET number = number-1 WHERE id ="+shopId;
dBHelper1 = new DBHelper1(sql);
int ret = dBHelper1.pst.executeUpdate();
//关闭连接
dBHelper1.close();
return ret > 0;
}
}
package com.restructure.demo.demo2.common;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.sql.*;
public class DBHelper2 {
private Connection conn = null;
private PreparedStatement pst = null;
private ResultSet rst = null;
/**
* 建立数据库连接
* @return 数据库连接
*/
public Connection getConnection() {
try {
// 加载数据库驱动程序
try {
Class.forName(SQLCommon.driver);
} catch (ClassNotFoundException e) {
System.out.println("加载驱动错误");
System.out.println(e.getMessage());
e.printStackTrace();
}
// 获取连接
conn = DriverManager.getConnection(SQLCommon.url, SQLCommon.user, SQLCommon.password);
} catch (SQLException e) {
System.out.println(e.getMessage());
}
return conn;
}
/**
* insert update delete SQL语句的执行的统一方法
* @param sql SQL语句
* @return 执行是否成功
*/
public boolean executeUpdate(String sql) {
try {
// 获得连接
conn = this.getConnection();
// 调用SQL
pst = conn.prepareStatement(sql);
// 执行
return pst.executeUpdate() > 0;
} catch (SQLException e) {
System.out.println(e.getMessage());
} finally {
// 释放资源
close();
}
return false;
}
/**
* SQL 查询将查询结果:一行一列
* @param sql SQL语句
* @return 结果集
*/
public Object findOne(String sql,Class cz) {
try {
// 获得连接
conn = this.getConnection();
// 调用SQL
pst = conn.prepareStatement(sql);
// 执行
rst = pst.executeQuery();
ResultSetMetaData data=rst.getMetaData();
JSONObject jsonObject=new JSONObject();
while (rst.next()){
for (int i = 1; i <= data.getColumnCount(); i++){
//获取列字段
String fieldName = data.getColumnName(i);
jsonObject.put(fieldName,rst.getString(fieldName));
}
}
//JSON转对象
return JSON.parseObject(jsonObject.toJSONString(),cz);
} catch (SQLException e) {
System.out.println(e.getMessage());
} finally {
close();
}
return null;
}
public void close() {
// 关闭结果集对象
if (rst != null) {
try {
rst.close();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
// 关闭PreparedStatement对象
if (pst != null) {
try {
pst.close();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
// 关闭Connection 对象
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
}
}
3、抽取 SQL 为公共数据(也可以不用,毕竟该实例比较小,不过我们为了规范还是抽取先)
package com.restructure.demo.demo2.common;
public class SQLCommon {
public static final String url = "jdbc:mysql://localhost:3306/restructure?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true";
public static final String driver = "com.mysql.cj.jdbc.Driver";
public static final String user = "root";
public static final String password = "root";
}
4、修改项目结构,加入 mapper 类——执行单一原则,只管底层数据连接,不管业务
package com.restructure.demo.demo2.dao;
import com.restructure.demo.ShopDO;
import com.restructure.demo.demo2.common.DBHelper2;
/**
* 底层数据处理
*/
public class ShopMapper {
/**
* 根据出货口查询可售商品信息
* @param port 出货口
* @return 可售商品信息
* @throws Exception
*/
public ShopDO getShopByPort(int port){
DBHelper2 dBHelper2 =new DBHelper2();
String sql = "SELECT * FROM shop WHERE port_status =1 and port ="+port;
return (ShopDO) dBHelper2.findOne(sql,ShopDO.class);
}
/**
* 根据商品ID获取商品信息
* @param shopId 商品ID
* @return 商品信息
* @throws Exception
*/
public ShopDO getShopByShopId(Long shopId){
DBHelper2 dBHelper2 =new DBHelper2();
String sql = "SELECT * FROM shop where id="+shopId;
return (ShopDO) dBHelper2.findOne(sql,ShopDO.class);
}
public boolean deductionShopById(Long shopId){
DBHelper2 dBHelper2 =new DBHelper2();
String sql = "UPDATE shop SET number = number-1 WHERE id ="+shopId;
return dBHelper2.executeUpdate(sql);
}
}
5、更新售卖实现类
package com.restructure.demo.demo2;
import com.restructure.demo.OutInfo;
import com.restructure.demo.ShopDO;
import com.restructure.demo.demo2.dao.ShopMapper;
/**
* 自动售卖机
*/
public class VendingMachine2{
public static void main(String[] args){
String userName="哈比";
long price = 10L;
int port = 2;
vendingMachine(userName,price,port);
}
/**
* 得到商品
* @param price 钱
* @param port 出货口
* @return
*/
public static void vendingMachine(String userName,long price, Integer port){
//获取商品
ShopMapper shopMapper=new ShopMapper();
ShopDO shopDO=shopMapper.getShopByPort(port);
//判断价格 & 库存
if(shopDO ==null || shopDO.getId() == null || price < shopDO.getShopPrice() || shopDO.getNumber() <=0){
System.out.println("该出货口暂无商品可销售");
return;
}
//扣减库存
Boolean bool=shopMapper.deductionShopById(shopDO.getId());
if(bool){
OutInfo.log(userName,price,port,shopDO);
}
}
}
6、增加输出类
package com.restructure.demo;
import com.restructure.demo.demo2.dao.ShopMapper;
public class OutInfo {
/**
* 输出日志
* @param userName
* @param price
* @param port
* @param shopDO
*/
public static void log(String userName,long price, Integer port,ShopDO shopDO){
ShopMapper shopMapper=new ShopMapper();
ShopDO nShopDO=shopMapper.getShopByShopId(shopDO.getId());
//信息输出
long giveChance=price-nShopDO.getShopPrice();
String yNumbers=shopDO.getNumber().toString();
String nNumbers=nShopDO.getNumber().toString();
outInfo(userName,price,port.toString(),shopDO.getShopName(),shopDO.getShopPrice().toString(),giveChance,yNumbers,nNumbers);
}
/**
* 信息输出
* @param userName 用户名
* @param price 价格
* @param ports 出货口
* @param shopNames 商品名
* @param shopPrices 商品价格
* @param giveChance 找零
* @param yNumbers 原库存
* @param nNumbers 现在库存
*/
public static void outInfo(String userName,Long price,String ports,String shopNames,String shopPrices,long giveChance,String yNumbers,String nNumbers){
System.out.println("==================================================");
System.out.println("购买用户:"+userName);
System.out.println("投入金额:"+price+"元");
System.out.println("出 货 口:"+ports);
System.out.println("购买商品:"+shopNames);
System.out.println("商品价格:"+shopPrices+"元");
System.out.println("找 零:"+giveChance+"元");
System.out.println("原 库 存:"+yNumbers);
System.out.println("剩余库存:"+nNumbers);
System.out.println("==================================================");
}
}
7、疑问?
问:现在代码看起来怎么样?
答:这个代码看起来比刚开始的清爽多了,而且没有多余的代码,也加了注释,感觉舒服多了。为什么要这样优化呢?答案就在上面(6、如何来实施代码重构)我描述的那些。那么问题来了(下面)?
问:那这样就是最优化的代码了么?
答:肯定不是呀,我们还没扩展呢,如果要扩展的话(自动售卖机支持多个商品一起购买)有人会说,这不简单我只要多加一个实现类或者改下现在的入参然后循环不就好了么;但是想想,这样的扩展是我所需要的么?这样不灵活;而且可能这个需求加了之后,我后面有不需要了,只需要单个的商品购买,所以,怎么办呢?这个时候我们就可以考虑设计模式了?
问:为啥一开始没有想到这个?
答:代码的优化是随着需求一步步来的,有些需求你可能没考虑到,但是要上到生产环境的时间到了,很急很急那种,来不及修改了,只能下个版本迭代了;
问:就算用设计模式,我该怎么选择?
答:那么多设计模式,怎么选着使用哪一个是一个很专业的问题,如果用了错误的设计模式可能会导致,代码越来越复杂,耦合更高,代码质量变差,变得不容易维护,选择设计模式要有以下几点注意:
(推荐:Java 设计模式)
1、实体类、底层数据、连接数据库驱动、公共类,不需要改动
2、扩展需求,我们选择责任链模式来更改代码
3、新增 SaleShop 类
package com.restructure.demo.demo3;
/**
* 售卖商品
*/
public abstract class SaleShop {
private SaleShop saleShop;
public SaleShop getSaleShop() {
return saleShop;
}
public void setSaleShop(SaleShop saleShop) {
this.saleShop = saleShop;
}
abstract void execute(String userName, long price, Integer port);
}
4、新增 SaleShopListPort 类——多个出货口一起购买实现
package com.restructure.demo.demo3;
import com.restructure.demo.OutInfo;
import com.restructure.demo.ShopDO;
import com.restructure.demo.demo2.dao.ShopMapper;
import java.util.ArrayList;
import java.util.List;
/**
* 多个端口实现类
*/
public class SaleShopListPort extends SaleShop{
private List portList;
public SaleShopListPort(List portList){
this.portList=portList;
}
@Override
void execute(String userName, long price, Integer port) {
if(portList==null || portList.size() <= 0){
getSaleShop().execute(userName,price,port);
}else{
ShopMapper shopMapper=new ShopMapper();
String shopName="",yNumbers="",nNumbers="",portListStr="";
Long totalPrice=0L;
List shopDOLis=new ArrayList();
for (int lport:portList) {
//获取商品
ShopDO shopDO=shopMapper.getShopByPort(lport);
//判断价格 & 库存
if(shopDO == null || shopDO.getId() == null || shopDO.getNumber() <=0){
System.out.println("【"+lport+"】出货口暂无商品可销售");
return;
}
shopName = shopDO.getShopName()+","+shopName;
totalPrice = totalPrice+shopDO.getShopPrice();
shopDOLis.add(shopDO);
portListStr = lport+","+portListStr;
}
//判断总金额
if(price < totalPrice){
System.out.println("金额错误");
return;
}
//执行扣除库存
for (ShopDO shopDO:shopDOLis) {
//扣减库存
Boolean bool=shopMapper.deductionShopById(shopDO.getId());
if(bool){
ShopDO nShopDO=shopMapper.getShopByShopId(shopDO.getId());
yNumbers = shopDO.getNumber()+","+yNumbers;
nNumbers = nShopDO.getNumber()+","+nNumbers;
}
}
Long giveChance=price-totalPrice;
OutInfo.outInfo(userName,price,portListStr,shopName,totalPrice.toString(),giveChance,yNumbers,nNumbers);
}
}
}
5、新增 SaleShopOnePort 类——单个出货口一起购买实现
package com.restructure.demo.demo3;
import com.restructure.demo.OutInfo;
import com.restructure.demo.ShopDO;
import com.restructure.demo.demo2.dao.ShopMapper;
import java.util.List;
/**
* 多个端口实现类
*/
public class SaleShopOnePort extends SaleShop{
private List portList;
public SaleShopOnePort(List portList){
this.portList=portList;
}
@Override
void execute(String userName, long price, Integer port) {
if(portList==null || portList.size() <= 0){
ShopMapper shopMapper=new ShopMapper();
//获取商品
ShopDO shopDO=shopMapper.getShopByPort(port);
//判断价格 & 库存
if(shopDO ==null || price < shopDO.getShopPrice() || shopDO.getNumber() <=0){
System.out.println("该出货口暂无商品可销售");
return;
}
//扣减库存
Boolean bool=shopMapper.deductionShopById(shopDO.getId());
if(bool){
ShopDO nShopDO=shopMapper.getShopByShopId(shopDO.getId());
//信息输出
long giveChance=price-nShopDO.getShopPrice();
String yNumbers=shopDO.getNumber().toString();
String nNumbers=nShopDO.getNumber().toString();
OutInfo.outInfo(userName,price,port.toString(),shopDO.getShopName(),shopDO.getShopPrice().toString(),giveChance,yNumbers,nNumbers);
}
}else{
getSaleShop().execute(userName,price,port);
}
}
}
6、测试类
package com.restructure.demo.demo3;
import java.util.ArrayList;
import java.util.List;
/**
* 自动售卖机
*/
public class VendingMachine3 {
public static void main(String[] args) {
//通过责任链模式实习在不改动接口参数的情况下,对多个端口传入参数的实现
List portList=new ArrayList();
portList.add(1);
portList.add(2);
SaleShop saleShopListPort=new SaleShopListPort(portList);
SaleShop saleShopOnePort=new SaleShopOnePort(portList);
saleShopListPort.setSaleShop(saleShopOnePort);
String userName="哈比";
long price = 10L;
int port = 2;
saleShopListPort.execute(userName,price,port);
}
}
**总结:**上面的例子虽然也能运行,但是该设计模式不适合此场景,我们希望灵活的扩展和组装;目前的扩展使得代码更加繁琐了,耦合性贼高的,与我们的初衷违背了。
1、使用策略模式重构代码实体类、底层数据、连接数据库驱动、公共类,不需要改动
2、创建策略接口
package com.restructure.demo.demo4;
import java.util.List;
public interface Strategy {
/**
* 策略方法--自动售卖机
* @param userName 用户
* @param price 价格
* @param portList 出货口
*/
public void vendingMachine(String userName,long price, List portList);
}
3、创建策略体 VendingMachine4
package com.restructure.demo.demo4;
import java.util.List;
/**
*
*/
public class VendingMachine4 {
//持有一个具体策略的对象
private Strategy strategy;
/**
* 构造函数,传入一个具体策略对象
* @param strategy 具体策略对象
*/
public VendingMachine4(Strategy strategy){
this.strategy = strategy;
}
/**
* 策略方法
*/
public void vendingMachine(String userName,long price, List portList){
strategy.vendingMachine(userName,price,portList);
}
}
4、创建单个售卖实现类
package com.restructure.demo.demo4;
import com.restructure.demo.OutInfo;
import com.restructure.demo.ShopDO;
import com.restructure.demo.demo2.dao.ShopMapper;
import java.util.List;
/**
* 单个出货口实现类
*/
public class StrategyOneImpl implements Strategy {
@Override
public void vendingMachine(String userName, long price, List portList) {
;
if(portList!=null && portList.size() == 1){
int port = portList.get(0).intValue();
ShopMapper shopMapper=new ShopMapper();
//获取商品
ShopDO shopDO=shopMapper.getShopByPort(port);
//判断价格 & 库存
if(shopDO ==null || price < shopDO.getShopPrice() || shopDO.getNumber() <=0){
System.out.println("该出货口暂无商品可销售");
return;
}
//扣减库存
Boolean bool=shopMapper.deductionShopById(shopDO.getId());
if(bool){
OutInfo.log(userName,price,port,shopDO);
}
}
}
}
5、创建多个出货口实现
package com.restructure.demo.demo4;
import com.restructure.demo.OutInfo;
import com.restructure.demo.ShopDO;
import com.restructure.demo.demo2.dao.ShopMapper;
import java.util.ArrayList;
import java.util.List;
/**
* 多出货口实现类
*/
public class StrategyListImpl implements Strategy {
@Override
public void vendingMachine(String userName, long price, List portList) {
ShopMapper shopMapper=new ShopMapper();
String shopName="",yNumbers="",nNumbers="",portListStr="";
Long totalPrice=0L;
List shopDOLis=new ArrayList();
for (int port:portList) {
//获取商品
ShopDO shopDO=shopMapper.getShopByPort(port);
//判断价格 & 库存
if(shopDO == null || shopDO.getId() == null || shopDO.getNumber() <=0){
System.out.println("【"+port+"】出货口暂无商品可销售");
return;
}
shopName = shopDO.getShopName()+","+shopName;
totalPrice = totalPrice+shopDO.getShopPrice();
shopDOLis.add(shopDO);
portListStr = port+","+portListStr;
}
//判断总金额
if(price < totalPrice){
System.out.println("金额错误");
return;
}
//执行扣除库存
for (ShopDO shopDO:shopDOLis) {
//扣减库存
Boolean bool=shopMapper.deductionShopById(shopDO.getId());
if(bool){
ShopDO nShopDO=shopMapper.getShopByShopId(shopDO.getId());
yNumbers = shopDO.getNumber()+","+yNumbers;
nNumbers = nShopDO.getNumber()+","+nNumbers;
}
}
Long giveChance=price-totalPrice;
OutInfo.outInfo(userName,price,portListStr,shopName,totalPrice.toString(),giveChance,yNumbers,nNumbers);
}
}
6、创建测试demo
package com.restructure.demo.demo4;
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
String userName="哈比";
long price = 10L;
List portList=new ArrayList();
portList.add(1);
portList.add(2);
//这里可以采用抽象工厂模式哦!
Strategy strategy=null;
//选择并创建需要使用的策略对象
if(portList!=null && portList.size() ==1){
strategy = new StrategyOneImpl();
}else{
strategy = new StrategyListImpl();
}
VendingMachine4 vendingMachine4=new VendingMachine4(strategy);
vendingMachine4.vendingMachine(userName,price,portList);
}
}
根据上述代码,我们可以清晰的看到,这次的优化比上一次用的设计模式用的更清晰,更容易扩展,是不是美滋滋,当然了,其中有些地方还可以优化,预留在这里,大家可以把代码下下来,然后去优化下,我的讲解就到这里了,希望大家喜欢,其实重重构不是很难很难,最主要的是实际场景特别复杂,牵一发而动全身,所以小编建议大家在不了解整个的业务的情况下,不要轻易的动工;如果你要选择使用设计模式,那么你千万要选对。
我的文章偏向于代码实践讲解,希望大家多多提些意见,我好改正!另外,对于设计模式的讲解,我博客有,大家可以看看! 讲诉代码可能很枯燥,所以我添加了一点日常的话语,希望大家多多包涵。
下载地址:百度网盘,提取码:2xgs