话说:
最近不能老写那么“基层”的代码,对吧?好歹来点有思想深度的东西,装装逼喽。
今天就来介绍下我理解的OCP原则吧,这是《Agile Software Development 》这本书中介绍的一个软件编写原则。
目录
1.What?
2.How?
3.总结
难度系数:★★☆☆ ☆
建议用时:1.5H
1.What?
书名:《Agile Software Development Principles,Patterns,and Practices》
敏捷软件开发 原则、模式与实践
Robert C,.Martin 清华大学出版社 邓辉 孟岩译 不要怕,是中文的。
概念
开放-封闭原则(OCP) - Open-Close Principle
个人理解:代码可扩展性好,灵活,适应变化;不能随便修改源代码。只是增加功能而已;很有点 AOP(面向切面)的感觉。
书本解释:软件实体(类、模块、函数 等等)应该是可扩展的,但是不可修改的。
2个特征:
1)对于扩展是开放的—— Open for extension
2)对于更改是封闭的—— Close for modification
不能因为需求的变动,就不断修改代码;而是让代码可以再原有基础上,增加新的代码。关键是抽象。之所以能够抽象,是因为有继承、多态这种关系。这本书中用C++做的例子,是一个关于按照图形顺序画图的案例,思维方式类似。
有什么用呢?
2.How?
开发工具:Eclipse
项目:普通Java Project
整体结构:
我这里用JDBC来做例子。在不用封装的时候,我们是这么写增删改查的。我们从不封装==》封装==》抽象为接口一步步介绍我自己对这个原则的体会。
这里我们用简单的Book对象来做案例,数据库准备如下:
create database myagile;
use myagile;
#创建图书表 简单模拟
create table book (
ID int auto_increment primary key,
name varchar(50),
author varchar(50) comment "作者"
)comment = "图书表";
#模拟数据
insert into book (name,author) values
("爱你就像爱生命","王小波"),
("炊事班的故事","不知道");
增删改查代码如下(未封装)
你会发现大量重复代码…..是的。
JdbcBookConn——单纯连接数据库代码
package com.hmc.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
*
*2018年3月4日
*User:Meice
*上午9:45:55
*/
public class JdbcBookConn {
//测试连接
public static void main(String[] args) {
//单纯连接数据库
//1.加载 驱动
try {
Class.forName("com.mysql.cj.jdbc.Driver");//6.0以上版本都多了cj这层 注意驱动包放置位置,build path
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//2.连接数据库
String url = "jdbc:mysql://localhost:3306/myagile?serverTimezone=GMT%2B8";//?serverTime解决数据库驱动包和数据库时区不一致问题
String user = "root";
String password = "119913";
try {
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);//com.mysql.cj.jdbc.ConnectionImpl@5c0369c4
} catch (SQLException e) {
e.printStackTrace();
}
}
}
JdbcBookSelect-查
package com.hmc.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
*
*2018年3月4日
*User:Meice
*上午10:18:30
*/
public class JdbcBookSelect {
//查
public static void main(String[] args) {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/myagile?user=root&password=119913&serverTimezone=GMT%2B8";
Connection conn = DriverManager.getConnection(url);
System.out.println(conn);
//1.查询数据库内容
String sql = "select * from book";
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
//遍历结果集,取值
while(rs.next()) {
String name = rs.getString("name");
String author = rs.getString("author");
System.out.print("图书:"+name+" 作者:"+author);
System.out.println();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
JdbcBookAdd-增
package com.hmc.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
*
*2018年3月4日
*User:Meice
*上午10:26:20
*/
public class JdbcBookAdd {
//增
public static void main(String[] args) {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/myagile?serverTimezone=GMT%2B8";
String user = "root";
String password = "119913";
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
//新增
// String sql = "insert into book (name,author) values ('《三国志》','陈寿')";
String sql = "insert into book (name,author) values(?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
//在执行之前,赋值形参
ps.setString(1, "《西游记》");
ps.setString(2, "吴承恩");
int result = ps.executeUpdate();
if(result>0) {
System.out.println("恭喜!增加成功!");
}else {
System.out.println("遗憾,增加失败!");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
JdbcBookUpdate-改
package com.hmc.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
*
*2018年3月4日
*User:Meice
*上午10:34:14
*/
public class JdbcBookUpdate {
//改
public static void main(String[] args) {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://127.0.0.1:3306/myagile?user=root&password=119913&serverTimezone=GMT%2B8";
Connection conn = DriverManager.getConnection(url);
System.out.println(conn);
String sql = "update book set name = ?,author = ? where ID = ?";
//执行修改
PreparedStatement ps = conn.prepareStatement(sql);
//赋参
ps.setString(1, "大话西游之月光宝盒");
ps.setString(2, "周星驰");
ps.setInt(3, 4);
//执行
int result = ps.executeUpdate();
if(result >0) {
System.out.println("修改成功!");
}else {
System.out.println("修改失败!");
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
JdbcBookDel-删
package com.hmc.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
*
*2018年3月4日
*User:Meice
*上午10:44:07
*/
public class JdbcBookDel {
//删
public static void main(String [] args) {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/myagile?user=root&password=119913&serverTimezone=GMT%2B8";
Connection conn = DriverManager.getConnection(url);
System.out.println(conn);
String sql = "delete from book where ID = 4 ";
PreparedStatement ps = conn.prepareStatement(sql);
int result = ps.executeUpdate();
if(result >0) {
System.out.println("恭喜!删除成功!");
}else {
System.out.println("遗憾,删除失败!");
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
注意:这个过程要求不许复制、粘贴哈。
小结:你会发现,在不用封装的情况下,有大量重复代码。每操作一次数据库,都需要连接数据库,关闭资源;而且在执行增、删、改的时候,他们都只是SQL语句不同,参数不同而已,其他代码一模一样!所以自然会封装起来。不经历一个想吐的过程,就不能体会到封装的帅气!
封装后:
DBConn
package com.hmc.utils;
/**
*
*2018年3月4日
*User:Meice
*上午10:49:15
*/
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBConn {
//封装成方法
//加载驱动 获取连接
static {//只加载一次
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//把获取Connection封装成方法
public static Connection getConn() {
String url = "jdbc:mysql://127.0.0.1:3306/myagile?serverTimezone=GMT%2B8";
String user = "root";
String password = "119913";
try {
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
//继续优化
//优化增删改
public static int Cud1(String sql) {
Connection conn = getConn();
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);//前提是SQL语句中值要赋值完毕
int result = ps.executeUpdate();
return result;
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return 0;
}finally {
DBConn.CloseJdbc(null, ps, conn);
}
//这个方法弊端在于:参数给固定了,问题是实际中,是用户赋参数,我们不能决定的!用 OCP原则来说,就是封闭的!只是站在代码的角度,方便了!
}
//优化增删改
public static int Cud2(String sql,Object[] params) {
Connection conn = DBConn.getConn();
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);
//执行语句之前,要为参数赋值
if(params != null) {//避免空指针
for(int i=0;i<params.length;i++) {
try {
ps.setObject((i+1), params[i]);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//执行增或者改或者删除
int result = ps.executeUpdate();
return result;
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
return 0;
}finally {
DBConn.CloseJdbc(null, ps, conn);
}
}
//关闭资源
public static void CloseJdbc(ResultSet rs,PreparedStatement ps,Connection conn) {
try {
if(rs != null) rs.close();
if(ps != null) ps.close();
if(conn != null) conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//测试
public static void main(String[] args) {
System.out.println(getConn());//com.mysql.cj.jdbc.ConnectionImpl@5c0369c4
}
}
封装成工具类后,方法可以直接调用了,对比DBConn中的Cud1()和Cud2()你会发现,Cud1()方法把参数写固定了,这样的代码根本就是“臭代码”,因为只是实现了单纯的功能,一点扩展性都没用,废代码一堆。因为实际运用中,参数是用户传递的,参数是不固定的,传什么参数,不是我们程序猿可以决定的,是客户!
所以Cud2()就比较好,而且不论用户传什么参数,只要是增删改的sql语句,都可以用这个方法,这个方法就是灵活的,可扩展的,这里可以很好体现OCP原则。
看看我们测试起来,会多么方便:
JdbcBookCurd -传统main()方法测试
package com.hmc.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
import com.hmc.utils.DBConn;
/**
*
*2018年3月4日
*User:Meice
*上午11:02:48
*/
public class JdbcBookCurd {
//这个就把封装好的工具包DBConn用起来
//增删改查 CURD
public static void main(String[] args) {
//查
Connection conn = DBConn.getConn();
String sql = "select * from book";
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while(rs.next()) {
System.out.println(rs.getString("name")+" "+rs.getString("author"));
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {//关闭资源
DBConn.CloseJdbc(rs, ps, conn);
}
//增 删 改 类似....省略
//测试增删改 Cud1()
String sql2 = "insert into book (name,author) values ('《茶花女》','歌德')";
String sql3 = "update book set name = '《红楼梦》' where ID = 100";
String sql4 = "delete from book where ID = 10000";
int result = DBConn.Cud1(sql2);
System.out.println(result);
}
}
TestJdbc-Junit测试
package com.hmc.test;
import org.junit.Test;
import com.hmc.utils.DBConn;
/**
*
*2018年3月4日
*User:Meice
*上午11:38:23
*/
public class TestJdbc {
//用单元测试
/**
* 测试增删改 封装的方法 最终版
*/
@Test
public void testCud2( ){
String sql = "insert into book (name,author) values(?,?)";
Object[] params = {"《别独自用餐》","不知道"};
int result = DBConn.Cud2(sql, params);
System.out.println(result);
}
}
接下来,俺老孙要长篇大论啦!!!
可是,似乎还是没有讲明白:对于扩展是开放的?对于修改是封闭的?到底是什么?
好,书本上说了,这个原则有一个重大特征:抽象!所以,我们把这些方法抽象成接口(抽象类)。为什么要抽象?抽象以后,就可以有具体实现类来实现具体细节;而且抽象的东西,万事万物都可以用。说点通俗的:人生可以简单的抽象为:吃喝拉撒睡;柴米油盐酱醋茶。不论你贫穷还是富有,你都摆脱不了这些;
在比如:人生可以抽象为这样几个阶段:少年;青年;中年;老年 每个阶段99%会遇到对应的问题:少年要读书;青年要处对象;中年面临中年危机;老年面临退休和各种老年疾病;我们把人生这么抽象出来之后,每个人都可以去实现这个”抽象“出来的人生,然后具体去实现底层的每个阶段的细节;你虽然每个阶段都是独特的,但是你遇到的问题,就是这些已经抽象出来的问题。抽象的东西,已经全部涵盖了。代码世界,最基础的就是增删改查。我们提前抽象出来,任何人用,直接具体去实现这些抽象方法。每次诞生一个宝宝,我们就实现这样一个“抽象”的接口,然后就可以为他一生各个阶段准备了。每个宝宝不论多么独特,我们只用在具体实现“类”中增加属于他自己独特一面的“代码”即可。
换到代码世界中来。
今天是图书的增删改查;明天是员工的增删改查;后天是商品的增删改查,不论任何的增删改查,都只需要实现这样”抽象出来“的接口,然后具体实现细节即可。这就是”对于扩展是开放的“;
每个抽象出来的方法没有方法体。但是方法的返回值和参数是固定的;这就是“对于更改是封闭的”体现。为什么要这样?因为这个抽象,已经是编写这个接口或者抽象类的人认为的最抽象的状态,没必要也不能修改内部,要修改可以,自己重新抽象一个。我们不去动原有的东西,“求同存异”,只是增加我们“个性”的东西。既保持已有的,又增加我所独特的。
有点啰嗦,抽象出来接口如下:
package com.hmc.dao;
/**
*
*2018年3月4日
*User:Meice
*上午11:47:47
*/
import java.util.List;
public interface BookDao {
//查
List<?> getList();
Object getObiById(Integer id);
//增删改
int Cud(String sql,Object... params);
/**
* 增
* int addObj(Object obj);
* int delObj(int id);
* int updateObj(Object obj);
*/
}
那么,以后如果要员工增删改查、商品增删改查…..直接实现这个接口,然后具体实现方法即可,扩展性很好,不用更改原有的底层的DBConn里面的东东,保持了代码的独立,有扩展了功能。
比如:我要对Book进行增删改查,只需要这么做:
BookDaoImpl
package com.hmc.daoImpl;
import java.util.List;
import com.hmc.dao.BookDao;
import com.hmc.utils.DBConn;
/**
*
*2018年3月4日
*User:Meice
*上午11:51:52
*/
public class BookDaoImpl implements BookDao{
@Override
public List<?> getList() {
// TODO Auto-generated method stub
return null;
}
@Override
public Object getObiById(Integer id) {
// TODO Auto-generated method stub
return null;
}
@Override
public int Cud(String sql, Object... params) {
return DBConn.Cud2(sql, params);
}
}
我要对员工实现CURD呢?
EmployeeDaoImpl
package com.hmc.daoImpl;
import java.util.List;
import com.hmc.dao.BookDao;
import com.hmc.utils.DBConn;
/**
*
*2018年3月4日
*User:Meice
*上午11:54:02
*/
public class EmployeeDaoImpl implements BookDao{
@Override
public List<?> getList() {
// TODO Auto-generated method stub
return null;
}
@Override
public Object getObiById(Integer id) {
// TODO Auto-generated method stub
return null;
}
@Override
public int Cud(String sql, Object... params) {
return DBConn.Cud2(sql, params);
}
}
我要对商品实现CURD呢?
GoodsDaoImpl
package com.hmc.daoImpl;
import java.util.List;
import com.hmc.dao.BookDao;
import com.hmc.utils.DBConn;
/**
*
*2018年3月4日
*User:Meice
*上午11:53:17
*/
public class GoodsDaoImpl implements BookDao{
@Override
public List<?> getList() {
// TODO Auto-generated method stub
return null;
}
@Override
public Object getObiById(Integer id) {
// TODO Auto-generated method stub
return null;
}
@Override
public int Cud(String sql, Object... params) {
return DBConn.Cud2(sql, params);
}
}
3.总结
1、普通Java Project,数据库驱动包放哪里?
JavaWeb项目,MySQL驱动包放到WEB-INF下面的lib包里面;IDEA的话,手动添加Dependency;
普通项目一般放在src或者项目下面,然后Build Path(可以看到包的结构),效果是一样的。
添加后效果:项目右键==》Properties ==>Build Path
2.
java.lang.Exception: No tests found matching [{ExactMatcher:fDisplayName=testCud2], {ExactMatcher:fDisplayName=testCud2(com.hmc.test.TestJdbc)], {LeadingIdentifierMatcher:fClassName=com.hmc.test.TestJdbc,fLeadingIdentifier=testCud2]] from
原因:Junit测试方法必须是:
1)无返回值void
2)不能带参。而我带参了。
3、Eclipse代码突然有了底色?变得五颜六色了?
解决办法:restart 呵呵。
4、一个词语概括:求同存异!我不同意你的代码逻辑,但誓死捍卫你的的代码,不会直接废弃,而是按照我的方式做扩充。
好了,再会,期待连载否?
本文深入浅出地介绍了开放-封闭原则(OCP),通过一个具体的JDBC示例展示了如何通过抽象和封装来提高代码的可扩展性和灵活性。
507

被折叠的 条评论
为什么被折叠?



