《Agile Software Development 》-1-OCP原则

本文深入浅出地介绍了开放-封闭原则(OCP),通过一个具体的JDBC示例展示了如何通过抽象和封装来提高代码的可扩展性和灵活性。

话说:

最近不能老写那么“基层”的代码,对吧?好歹来点有思想深度的东西,装装逼喽。
今天就来介绍下我理解的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、一个词语概括:求同存异!我不同意你的代码逻辑,但誓死捍卫你的的代码,不会直接废弃,而是按照我的方式做扩充。


好了,再会,期待连载否?

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值