@[toc]javascript
既然是程序解耦,那咱们必需要先知道啥是耦合,耦合简单来讲就是程序的依赖关系,而依赖关系则主要包括java
一、 类之间的依赖 二、 方法间的依赖mysql
好比下面这段代码:spring
public class A{
public int i;
}
public class B{
public void put(A a){
System.out.println(a.i);
}
}
复制代码
上面这个例子中A类和B类之间存在一种强耦合关系,B
类直接依赖A
类,B
类的put
方法非A
类类型不可,咱们把这种状况叫作强耦合关系。sql
实际开发中应该作到:编译期不依赖,运行时才依赖。怎么理解呢?咱们很容易想到多态向上转型,是的,编译时不肯定,运行时才肯定,固然接触面更广一点的童鞋会想到接口回调,是的接口回调方式也能有效的解耦!以下代码:数据库
//一个接口叫作Inter,里面定义了一个happy()方法,有两个类A、B实现了这个接口
interface Inter{
void happy();
}
class A implements Inter{
@Override
public void happy() {
System.out.println("happy...A");
}
}
class B implements Inter{
@Override
public void happy() {
System.out.println("happy...B");
}
}
public class Test{
public void happys(Inter inter){
inter.happy();
}
}
复制代码
是的,如上代码正是典型的接口回调,Test
类中的happys
方法参数变的相对灵活起来,代码中Test类
与A类
、B类
之间就存在一种弱耦合关系,Test类
的happys
方法的参数可使A类
类型也能够是B类
类型,不像强耦合关系中非A类
类型不可的情形。编程
从某一意义上来说使用类的向上转型或接口回调的方式进行解耦都是利用多态的思想!设计模式
固然解耦的方式还有不少,从根本意义上讲实现低耦合就是对两类之间进行解耦,解除类之间的直接关系,将直接关系转换成间接关系,从而也有不少设计模式也对程序进行解耦,好比:适配器模式、观察者模式、工厂模式....总之,必须明确一点:耦合性强的程序独立性不好!安全
先来看一段代码:并发
//一、注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver()); //若是把jdbc的MySQLjar包依赖去除直接编译失败提示没有mysql
//二、获取链接
Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/ufida","root","root");
//三、获取操做数据库的预处理对象
PreparedStatement pstm=conn.prepareStatement("select * from client");
//四、执行SQL,获得结果集
ResultSet rs=pstm.executeQuery();
//5\遍历结果集
while(rs.next()){
System.out.println(rs.getString("name"));
}
//六、释放资源
rs.close();
pstm.close();
conn.close();
复制代码
等等等等,好熟悉好怀念的代码.....
没错就是jdbc的代码,不是用来怀旧的,而是若是这样设计,你会以为这样的程序耦合性如何?又如何进行解耦?先仔细思考一番。
一分钟过去了..... 两分钟过去了.....
好了,咱们都知道jdbc链接MySQL须要一个mysql-connector
的jar包,若是咱们把这个jar包依赖或者这个jar包给去掉,显然上面的这个程序会编译报错,以下图
//一、new的方式注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver()); //若是把jdbc的MySQLjar包依赖去除直接编译失败提示没有mysql相关的jar包
改成以下方式
//二、反射的方式注册驱动
Class.forName("com.mysql.jdbc.Driver"); //改用这种方式注册驱动会发现不会编译失败,相比上面的方式相对解耦,可是依然存在缺陷:若链接改成Oracle数据库,这里的字符串又要进行改动!
复制代码
正如注释的解释同样,又一个缺陷就浮现了:若链接改成Oracle数据库,这里的字符串又要进行改动!
因而对于这个jdbc程序来讲就有这样的一个解耦思路:
第一步:经过反射来建立对象,尽可能避免使用new关键字 第二步:经过读取配置文件来获取建立的对象全限定类名
顺着jdbc程序的解耦思路,咱们再来看看传统dao、service、controller的程序耦合性分析
因为只是一个demo,省去dao层的操做.....
定义一个Service接口
public interface IAccountOldService{
public void save();
}
复制代码
Service接口实现类
public class AccountServiceOldImpl implements IAccountOldService{
@Override
public void save() {
System.out.println("save成功一个帐户....");
}
}
复制代码
controller代码:
public class AccountCencollertOld {
public static void main(String[] args) {
IAccountOldService iaccount=new AccountServiceOldImpl ();
iaccount.save(); //运行结果:save成功一个帐户....
}
}
复制代码
到这里,有何想法?表面上来看是没有一点问题的,So Beautiful,但仔细的看。表现层与业务层、业务层与持久层牢牢的互相依赖关联,这与咱们开发程序的高内聚低耦合原则相违背,哦My God,So Bad!咱们顺着jdbc程序的解耦思路,咱们应该尽可能避免使用new关键字,咱们发现这些层里面service层new 持久层dao,controller表现层new 业务层service....太糟糕了
那么对此,你有何解耦思路?
别想了,工厂模式实现程序解耦你值得拥有!顺着jdbc程序的解耦思路:
一、经过读取配置文件来获取建立的对象全限定类名 二、经过反射来建立对象,尽可能避免使用new关键字
首先在resources目录下中写一个bean.properties配置类,具体内容以下
accountServiceOld=com.factory.service.impl.AccountServiceOldImpl
复制代码
接着使用工厂方法代码:
/** * 一个建立Bean对象的工厂 * * 一、须要一个配置文件来配置咱们的service和dao 配置文件的内容:惟一标识=全限定类名(key-value) * 二、经过读取配置文件中配置的内容,反射建立对象 * * 场景:主要是service调用dao,controller调用service的程序。这里面耦合性很是的高,互相new互相依赖 * * 为了解耦,利用工厂模式进行 */
public class BeanFactoryOld {
private static Properties props;
static{
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);//加载其对应路径下的配置文件
}catch (Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
//根据bean的名称获取bean对象
public static Object getBean(String beanName){
Object bean=null;
try {
String beanPath= props.getProperty(beanName);
bean = Class.forName(beanPath).newInstance(); //这里的newInstance建立实例(默认无参构造器)每次执行都须要建立一次
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
}
复制代码
此时,controller的代码就能够编写为
/** * 这里模拟一个controller调用service * */
public class AccountCencollertOld {
public static void main(String[] args) {
// IAccountOldService iaccount=new AccountServiceOldImpl (); //使用工厂方法再也不经过new方式
IAccountOldService iaccount= (IAccountOldService) BeanFactoryOld.getBean("accountServiceOld");
iaccount.save(); //运行结果:save成功一个帐户.... 说明成功调用了service
}
}
复制代码
经过运行结果,属实没毛病,成功下降程序耦合!So Beautiful!先高兴一会吧,由于立刻出现.....可是,随之而来的问题又出现了,咱们对这个controller进行一下改写
for(int i=0;i<5;i++){
IAccountOldService iaccount= (IAccountOldService) BeanFactoryOld.getBean("accountServiceOld");
iaccount.save();
}
复制代码
打印结果:
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个帐户....
com.factory.service.impl.AccountServiceImpl@677327b6
save成功一个帐户....
com.factory.service.impl.AccountServiceImpl@14ae5a5
save成功一个帐户....
com.factory.service.impl.AccountServiceImpl@7f31245a
save成功一个帐户....
com.factory.service.impl.AccountServiceImpl@6d6f6e28
save成功一个帐户....
复制代码
打印的是五个不一样的对象,说明是多例的,每次调用getBean的时候都会newInstance出一个新对象,以下
针对单例多例状况,咱们再对service业务层代码进行修改:
public class AccountServiceImpl implements IAccountService {
//定义类成员
private int i=1;
@Override
public void save() {
System.out.println("save成功一个帐户....");
System.out.println(i);
i++;
}
}
复制代码
运行controller代码,运行结果
save成功一个帐户....
1
save成功一个帐户....
1
save成功一个帐户....
1
save成功一个帐户....
1
save成功一个帐户....
1
复制代码
why?多例由于每次都是新的对象,上面也验证过了,所以每次建立新对象都会初始化一次,从新赋值,因此都是1,若是咱们把类成员改成局部成员变量以下
public class AccountServiceOldImpl implements IAccountOldService {
// private int i=1;
@Override
public void save() {
int i=1; //改成局部变量
System.out.println("save成功一个帐户....");
System.out.println(i);
i++;
}
}
复制代码
不用猜,运行结果一样是1。算了仍是运行一下吧哈哈哈
save成功一个帐户....
1
save成功一个帐户....
1
save成功一个帐户....
1
save成功一个帐户....
1
save成功一个帐户....
1
复制代码
说了这么多,经过观察service和dao之间单不单例好像无所谓,由于他们之间并无业务方法中改变的类成员,因此并不须要多例来保证线程安全。那说这些有何意义?不要忘了,因为使用了工厂改进以下中的.newInstance
建立实例(默认无参构造器)每次执行都须要建立一次,这样就很差了(浪费资源),所以咱们要设计出只newInstance
建立一次实例就很完美了,这也是我为啥要在service
和controller
中都添加一个Old
关键字的缘由了,接下来咱们来看看工厂是如何改进的!
为了避免被搞晕,咱们从新写代码,也就是重头开始写代码~其实就是把Old去掉~
因为只是一个demo,省去dao层的操做.....
定义一个Service接口
public interface IAccountService {
public void save();
}
复制代码
Service接口实现类
public class AccountServiceImpl implements IAccountService{
@Override
public void save() {
System.out.println("save成功一个帐户....");
}
}
复制代码
controller代码:
/** * 这里模拟一个controller调用service * */
public class AccountCencollert {
public static void main(String[] args) {
// IAccountService iaccount=new AccountServiceImpl();
IAccountService iaccount= (IAccountService) BeanFactory.getBean("accountService");
iaccount.save(); //运行结果:save成功一个帐户.... 说明了成功调用了service
}
}
复制代码
改进的工厂方法代码:
public class BeanFactory {
private static Properties props;
//定义一个map容器,用于存放建立的对象
private static Map<String,Object> beans; //改进的代码============
static{
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);//加载其对应路径下的配置文件
////////////////////如下是改进的代码=======================
//实例化容器
beans=new HashMap<String,Object>();
//取出配置文件中全部的key值
Enumeration<Object> keys = props.keys();
//遍历枚举
while(keys.hasMoreElements()){
//取出每一个key
String key = keys.nextElement().toString();
//根据key取出对应的value (这里由于每一个value值对应着类路径)
String beanPath = props.getProperty(key);
//反射建立对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key,value);
}
}catch (Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
//随着代码的改进,咱们就能够简化下面的获取bean对象的方法,以下代码
/** * 根据bean的名称获取对象(单例) */
public static Object getBean(String beanName){
//经过Map容器对应key来获取对应对象
return beans.get(beanName); //这里经过Map容器中获取,这样就不会每次都建立一次实例!
}
//再也不使用下面的方法
/* //根据bean的名称获取bean对象 public static Object getBean(String beanName){ Object bean=null; try { String beanPath= props.getProperty(beanName); bean = Class.forName(beanPath).newInstance(); //这里的newInstance建立实例(默认无参构造器)每次执行都须要建立一次,这样就很差了 } catch (Exception e) { e.printStackTrace(); } return bean; }*/
}
复制代码
从上面改进的工厂代码,咱们能够发现一开始就定义一个Map容器,用于存放建立的对象,为啥要先定义一个Map容器呢?用一个容器将这个实例装起来,这是因为不把这个对象装存起来的话,这个对象不使用很容易被GC掉,况且咱们如今只使用这一个对象!
定义一个Map容器存放配置好的文件中的每一个对象,以后咱们就直接提供一个根据Map的key来取value的getBean方法,这样不只仅扩展了程序的配置文件的灵活性并且还保证了只产生一个对象,保证资源不浪费,So Beautiful !
那如何证实已是单例的模式了呢?很简单,以下设计一下service业务层、controller表现层代码便可:
service业务层:添加一个类成员属性,并在方法内部 i++;
public class AccountServiceImpl implements IAccountService {
private int i=1; //类成员属性
@Override
public void save() {
System.out.println("save成功一个帐户....");
System.out.println(i);
i++;//二次改革代码
}
}
复制代码
controller表现层: 建立调用工厂5次建立对象的方法
/** * 这里模拟一个controller调用service * */
public class AccountCencollert {
public static void main(String[] args) {
for(int i=0;i<5;i++){
IAccountService iaccount= (IAccountService) BeanFactory.getBean("accountService");
System.out.println(iaccount); //打印的是五个不一样的对象,说明是多例的
iaccount.save(); //会发现打印的i值都是1,并无自增成功
}
}
复制代码
运行代码结果:
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个帐户....
1
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个帐户....
2
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个帐户....
3
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个帐户....
4
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个帐户....
5
复制代码
发现,确实5个对象都是同一个,而且出现了改变类成员属性的现象。
若是咱们把类成员属性改成局部成员属性呢?
public class AccountServiceImpl implements IAccountService {
@Override
public void save() {
int i=1; //局部成员属性
System.out.println("save成功一个帐户....");
System.out.println(i);
i++;
}
}
复制代码
运行结果
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个帐户....
1
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个帐户....
1
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个帐户....
1
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个帐户....
1
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个帐户....
1
复制代码
看到这个结果,咱们就能联想到,以前为何servlet中为啥要避免定义类成员,缘由就在这里!多例状况下,就不会出现这种状况!!!!
到这里咱们已经基本了解了程序间的耦合与解耦,而且对单例多例也一并进行了进一步的了解。而spring中正是使用工厂模式来实现程序解耦的,spring是一个大工厂, 或许你并无察觉哈哈哈哈.....
若是本文对你有一点点帮助,那么请点个赞呗,你的赞同是我最大的动力,谢谢~
最后,如有不足或者不正之处,欢迎指正批评,感激涕零!若是有疑问欢迎留言,绝对第一时间回复!
欢迎各位关注个人公众号,里面有一些java学习资料和一大波java电子书籍,好比说周志明老师的深刻java虚拟机、java编程思想、核心技术卷、大话设计模式、java并发编程实战.....都是java的圣经,不说了快上Tomcat车,咋们走!最主要的是一块儿探讨技术,向往技术,追求技术,说好了来了就是盆友喔...