最近在思考我们spring为什么这么流行,除了开源社区活跃,拥有庞大的使用用户群体还与它的设计思想有很大关联性,主要有下面2方面的原因:
1.spring遵从了公认的软件设计原则
2.spring对设计模式模式的使用
从这两个方面来设计一个框架,来满足程序架构的一般特性,简化开发,使系统开发更快,使修改付出的代价更少,所以spring流行起来了.
公认的软件设计原则
软件设计七大重要原则:
1、单一职责原则(SRP)
就一个类而言,应该仅有一个引起它变化的原因。如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到破坏。软件要做的许多内容,就是发现职责并把那些职责相互分离。如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责,就应该考虑类的职责分离。
单一原则的好处:
- 类的辅助性降低,实现什么职责都有清晰明确的定义;
- 可读行提高,复杂性降低,那当然可读性提高了;
- 可维护性提高,可读性提高,那当然更容易维护;
- 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做的好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
举个例子:
我们系统里抽象了一个订单接口对外提供服务OrderService,然后这个接口有createOrder,updateOrder,queryOrders,findOrderById这样的方法,订单还有子订单OrderServiceDetail,子订单也有这些方法,我们不要把子订单的方法和订单的方法混在了一起,这样在修改订单的方法的时候就不用修改子订单的方法,也减少了相应功能的测试.
2、开放封闭原则(OCP)
软件实体(类、模块、函数等)应该可以扩展,但是不可以修改,即对于扩展是开放的,对于更改是封闭的。运用开放-封闭原则可以使得软件面对需求的改变却可以保持相对稳定,从而使得软件可以在第一个版本以后不断推出新的版本。换句话说,当面对需求,对程序的改动是通过增加新的代码进行的,而不是更改现有的代码。无论模块多么封闭,都会存在一些无法对之封闭的变化。既然不可能完全封闭,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离这些变化,等到变化发生时立即采取行动。于是,在我们最初编写代码时,假设变化不会发生。当变化发生时,我们就创建抽象来隔离以后发生的同类变化。
开放-封闭原则是面向对象设计的核心所在,遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现出频繁变化的那些部分做出抽象,然后,对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。
开放封闭原则的优点:
- 开闭原则对测试的影响;
- 开闭原则可以提高复用性;
- 开闭原则可以提高可维护性;
- 面向对象开发的要求;
举个例子:
我们的OA系统,一件事情的处理要经过很多步骤,而这些步骤可能会增加也有可能会减少,我们在写程序的时候应该让这些步骤独立起来去做自己的事,对这个每个步骤自己来说他们对外是封闭的,但我们可以增加一件事情的处理步骤,处理流程而不会影响到整个流程,比较方便扩展这个特点又是开放的
3、里氏代换原则(LSP)
定义:子类型必须能够替换掉它们的父类型。也就是说,一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它觉察不出父类对象和子类对象的区别,即在软件里面,把父类都替换成它的子类,程序的行为没有变化。只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能被复用,而子类也能够在父类的基础上增加新的行为。
里氏代换原则的优点:
- 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
- 提高代码的重用性;
- 子类可以形似父类,但又异于父类;
- 提高代码的可扩展性;
- 提高产品或项目的开发性;
坏处:
- 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
- 降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
- 增强了耦合性。当父类的常量、变量和方法修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果--大段代码需要重构。
为了让继承的好处大于坏处,引入了里氏替换原则(Liskov Substitution Principle,LSP):所有引入基类的地方必须能够透明的使用其子类对象。 通俗的讲,只要父类出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。
子类可以扩展父类的功能,但不能改变父类原有的功能。
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
- 子类中可以增加增加特有的方法;
里氏替换原则为良好的继承定义了一个规范,一句简单的定义包含了4层含义。
- 子类必须完全实现父类的方法;
- 在类中调用其他类时,务必使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了 LSP 原则。
- 如果子类不能完整的实现父类的方法,或者父类的某些方法在子类中已经发生"畸变",则建议断开继承关系,采用依赖、聚集、组合等关系代替继承。
- 子类可以有自己的个性;
- 覆盖或实现父类的方法时输入参数可以被放大;
- 覆写或实现父类的方法时输出结果可以被缩小;
package com.xue.design_model.lishidaihuan;
/**星星的爸爸
* Created by admin on 2017/7/15.
*/
public abstract class XingErFather {
public void eat(){
System.out.println("fanther eat");
}
public void sleep(){
System.out.println("father sleep");
}
abstract void think();
}
package com.xue.design_model.lishidaihuan;
/**星星
* Created by admin on 2017/7/15.
*/
public class Xinger extends XingErFather {
@Override
void think() {
System.out.println("xinger think");
}
@Override
public void sleep() {
System.out.println("xinger sleep");
}
}
package com.xue.design_model.lishidaihuan;
/**
* Created by admin on 2017/7/15.
*/
public class MainTest {
public static void main(String [] args){
Xinger daughter = new Xinger();
daughter.eat();
daughter.sleep();
daughter.think();
}
}
这个就符合里氏代换原则,加入星儿把睡觉改成了洗澡这样就不符合里氏代换原则,因为他破坏了父类洗澡整个功能
4. 依赖倒置原则(DIP)
1、高层模块不应该依赖低层模块,两个都应该依赖抽象。
2、抽象不应该依赖细节,细节应该依赖抽象。
换句话说,就是要针对接口编程,不要对实现编程。依赖倒置原则是面向对象设计的标志,用哪种语言编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之就是过程化的设计了。
正是有了里氏代换原则,才使得依赖倒置原则成为可能。由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展。
依赖倒置原则用的最好的就是spring了,后面详细介绍
5、接口隔离原则(ISP)
这一原则好象是单一职责原则的升级版,接口隔离原则强调的是当一个服务类需要被即有共同功能需求又有不同功能需求的客户类使用时,不能在服务类中加进它的客户不需要的方法,比如在服务类A的客户中, B类客户需要F方法,而C类客户则不需要F方法,这时不能简单地把F方法加到服务类A中以满足B类客户的需求,而应分离接口;比如另设计一个服务类D,其中包含F方法,并把共用的功能委托给A实现,这样B客户可以使用D,而C客户继续使用A;对这一原则我有所保留的是:如果F方法对C类没有影响,直接加到A类中也无防,而且这种情况是很普遍。
我觉得接口隔离原则是单一原则的一种具体实现方式,不服来战哈哈
6.合成/聚合复用原则
在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过这些向对象的委派达到复用已有功能的目的.这个设计原则有另一个简短的表述:要尽量使用合成/聚合,尽量不要使用继承。这个就不用多介绍了,因为使用继承没有一个规范类控制违反里氏转换原则,所以合成原则很好的解决了继承中出现的问题
7.迪米特法则
又叫最少知道原则。如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。该法则首先强调的前提是在类的结构设计上,每一个类都应该尽量降低成员的访问权限,即一个类包装好自己的private状态,不需要让别的类知道的字段或行为就不要公开。迪米特法则的根本思想,是强调了类之间的松耦合。类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。
这个原则体现了对java作用域public ,private ,protected 及不写时的使用方式,还体现了现在微服务的设计实现
spring中用到的设计模式
1.单例模式
//第一种写法
public class Singleton {
private static Singleton singleton = null;
private Singleton() { }
public static Singleton getInstance() {
if (singleton== null) {
singleton= new Singleton();
}
return singleton;
}
}
这种写法多线程容易出现创建多个对象,甚至造成内存泄露
public class Singleton
{
private volatile static Singleton singleton = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return singleton;
}
}
这种容易出现延迟加载的问题,加入这个类加载慢整个系统就会启动慢
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
上面这种方式,仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它只有在getInstance()被调用时才会创建;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
单例模式是设计模式中使用最为普遍的模式之一。它是一种对象创建模式,用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例。在Java语言中,这样的行为能带来两大好处:
(1)对于频繁使用的对象,可以省略new操作花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
(2)由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。
Spring的单例和狭义上的单例模式实现是不同的 狭义上的单例有以下问题:
系统与单例类名高度耦合
单例无法多态
很难做单元测试
spring通过算法来实现单例的,而不是通过单例的设计模式完成的
spring使用到的代理模式实现aop
Spring中的工厂
FactoryBean
静态方法工厂
实例方法工厂
Spring中的装饰者模式
虽然结构和狭义的装饰者不同,但是用意相同 BeanDefinitionDecorator 用于动态添加BeanDeinition的职责
Spring中的模板方法
JdbcTemplate
AbstractApplicationContext