设计模式是什么
设计模式是先辈们对代码设计的经验总结。解决一些重复发生的问题,具有一定的普遍性,可以反复使用,其目的为了提高代码的重用性、可读性和可靠性。
学习设计模式的意义
设计模式是对面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
优点
- 可以提高程序员的思维能力,编程能力和设计能力
- 可以是代码和程序更加标准化,大大提高开发效率,从而缩短软件的开发周期。
- 代码的复用性高、可读性强、灵活性好、可维护性强。
java设计模式的类型
设计模式分为三类:创建型模式、结构化模式、行为型模式。
创建型
解决问题”怎么创建对象“的,主要特点是“将对象的创建与使用分离 ”。提供了单例、原型、工厂方法、抽象工厂、创造者5中创建型模式。
结构型
用于怎么将类和对象按照某种布局成更大的结构。提供了代理、适配器、桥接、装饰、外观、享元、组合7种结构型模式。
行为型
用于描述类与对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎么提供职责。提供了模板方法 、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器11种行为模式。
23种设计模式的介绍
- 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
- 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
- 工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
- 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
- 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合 度。
- 装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
- 模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
- 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它 们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互 关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数 据,而不暴露聚合对象的内部表示。
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的 解释方法,即解释器。
常用设计模式
单例模式
在某些系统中,为了节省内存资源、保证数据内容的一致性,让某些对象只能创建一个实例,这就是所谓的单例模式。例如windows中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成的内存资源浪费,出现各个窗口显示的内容不一致的问题。
特点
只产生一个对象。
单例对象由单例类创建。
单例类对外提供一个访问该单例的全局访问点。
单例模式的结构
单例类中包含一个实例,且其自己创建该实例
访问类调用单例类的生成对象的方法获取单例类
两种实现方式
饿汉式
饿汉式顾名思义对象就已经等不及被创建了,所以在类加载时就创建该 单例对象,这种写法不会出现线程安全问题。
/*
* 饿汉式单例
* 一般又称为急切式单例
* 在类加载时,就会创建此单例对象,这种写法不会出现线程安全问题
*/
public class Singleton {
//创建 Singleton 的一个对象
private static Singleton instance = new Singleton();
//让构造函数为 private
private Singleton(){}
//获取唯一可用的对象
public static Singleton getInstance(){
return instance;
}
}
懒汉式
该模式的特点是在类加载的时候没有生成单例,只有当第一次调用getInstance方法的时候才去创建这个单例。
public class Singleton {
public static volatile Singleton singleton;
private Singleton(){
}
public static Singleton creatSingleton(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
因为多个线程访问,我们需要加上volatile关键字让所有线程知道该单例对象是否已经创建。重写其构造方法为私有,让其只能通过提供的方法进行创建对象。
当一个线程进来创建对象的时候,首先要判断对象是否存在,如果不存在就进创建对象的代码块中,这里加入了synchronized来修饰里面的代码块,防止多个线程一起进来,可能之前已经有线程创建对象了,再重新进来看对象是否存在,如果这第二次判断也成功发现对象不存在,则创建一个对象。这里使用双层判断的思想叫双重检索。
工厂模式
定义
定义一个创建对象的工厂接口,将产品对象的实际创建工作推迟到工厂类中。满足创建型模式中所要求的"创建与使用分离"的特点。
按实际的业务场景将工厂模式划分为简单工厂模式和抽象工厂模式
简单工厂
我们将要创建的对象称为产品,把创建产品的对象称为工厂。如果要创建的产品不多 ,只需要一个工厂类就能完成,称为简单工厂模式。
在简单工厂中创建实例的方法通常为静态方法,因此简单工厂模式又叫静态工厂模式。
主要角色
简单工厂:是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品的方法可以被外界直接调用,创建所需要的产品。
/*
* 工厂,负责生产对象
*/
public class SimpleFactory {
//工厂中负责制造对象的方法
public Product createProduct(String className){
if(className == null){
return null;
}else{
try {
return (Product) Class.forName(className).newInstance();//反射机制
} catch (InstantiationException e) {
e.printStackTrace();
return null;
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
}
抽象产品:是简单工厂创建所有对象的父类,负责描述所有实例共有的公共接口
//抽象产品
public interface Product {
void show();
}
具体产品:简单工厂模式创建的目标
//具体产品1
public class ProductA implements Product {
@Override
public void show() {
System.out.println("具体产品1显示...");
}
}
//具体产品2
public class ProductB implements Product {
@Override
public void show() {
System.out.println("具体产品2显示...");
}
}
结构图如下图所示
抽象工厂
需求:宝马品牌车有宝马X1、X3、X5等系列,每个系列车又配有各自的系列车标X1、X3、X5(车标只能由自己工厂创建),这三个系列车和车标分别由三个工厂创建,下面通过抽象工厂模式来实现该例子。
创建车类
创建IBMWCar接口,并定义run方法。
public interface IBMWCar {
void run();
}
创建宝马X1车类,并实现IBMWCar接口,实现run方法。
public class X1Car implements IBMWCar {
@Override
public void run() {
System.out.println("X1Car run");
}
}
创建宝马X3车类,并实现IBMWCar接口,实现run方法。
public class X3Car implements IBMWCar {
@Override
public void run() {
System.out.println("X3Car run");
}
}
创建宝马X5类,并实现IBMWCar接口,实现run方法。
public class X5Car implements IBMWCar {
@Override
public void run() {
System.out.println("X5Car run");
}
}
创建Logo车标类
public interface ILogo {
void create();
}
创建宝马X1Logo车标类,并实现ILogo接口,实现create方法。
public class X1Logo implements ILogo {
@Override
public void create() {
System.out.println("创建X1Logo");
}
}
创建宝马X3Logo车标类,并实现ILogo接口,实现create方法。
public class X3Logo implements ILogo {
@Override
public void create() {
System.out.println("创建X1Logo");
}
}
创建宝马X5Logo车标类,并实现ILogo接口,实现create方法。
public class X5Logo implements ILogo {
@Override
public void create() {
System.out.println("创建X1Logo");
}
}
创建工厂接口和工厂子类
创建工厂接口,定义createCar方法。
public interface IBMWFactory {
public IBMWCar createCar();
}
创建X1工厂类,并实现工厂接口,实现createCar方法,创建X1Car对象,实现createLogo方法创建X1车标对象。
public class X1Factory implements IBMWFactory {
@Override
public IBMWCar createCar() {
return new X1Car();
}
@Override
public ILogo createLogo() {
return new X1Logo();
}
}
创建X3工厂类,并实现工厂接口,实现createCar方法,创建X3Car对象,实现createLogo方法创建X3车标对象。
public class X3Factory implements IBMWFactory {
@Override
public IBMWCar createCar() {
return new X3Car();
}
@Override
public ILogo createLogo() {
return new X3Logo();
}
}
创建X5工厂类,并实现工厂接口,实现createCar方法,创建X5Car对象,实现createLogo方法创建X5车标对象。
public class X5Factory implements IBMWFactory {
@Override
public IBMWCar createCar() {
return new X5Car();
}
@Override
public ILogo createLogo() {
return new X5Logo();
}
}
测试
public class Test {
public static void main(String[] args) {
// 创建X1工厂类,并创建X1车和X1车标
X1Factory x1Factory = new X1Factory();
IBMWCar carX1 = x1Factory.createCar();
carX1.run();
ILogo logo1 = x1Factory.createLogo();
logo1.create();
// 创建X3工厂类,并创建X3车和X3车标
X3Factory x3Factory = new X3Factory();
IBMWCar carX3 = x3Factory.createCar();
carX3.run();
ILogo logo3 = x3Factory.createLogo();
logo3.create();
// 创建X5工厂类,并创建X5车和X5车标
X5Factory x5Factory = new X5Factory();
IBMWCar carX5 = x5Factory.createCar();
carX5.run();
ILogo logo5 = x5Factory.createLogo();
logo5.create();
}
}
简单工厂与抽象工厂的区别
简单工厂
是由一个工厂对象创建产品实例,简单工厂模式的工厂类一般是使用静态方法,通过不同的参数的创建不同的对象实例可以生产结构中的任意产品,不能增加新的产品
抽象工厂
提供一个创建一系列相关或相互依赖对象的接口,而无需制定他们具体的类,生产多个系列产品生产不同产品族的全部产品,不能新增产品,可以新增产品族
代理模式
当你需要买东西的时候,通常都不是直接去买的,而是通过中间商代理来买,例如购买火车票通过12306来买
优点
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
代理对象可以扩展目标对象的功能
代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度
结构
1、抽象主题类:类通过接口或抽象类声明真实主题和代理对象实现的业务方法。
2、真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的二等真实对象,是最终要引用的对象
3、代理类:提供了与真实主题相同的接口,其内部有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
结构图
代理分为静态代理和动态代理
静态代理
抽象主题类
/*
Dao接口,定义保存功能
*/
public interface BaseDao {
void save();
}
真实主题类
/*
实际功能实现类
*/
public class UserDaoImpl implements BaseDao {
@Override
public void save() {
System.out.println("UserDaoImpl:save() 保存用户");
}
}
public class RoleDaoImpl implements BaseDao{
@Override
public void save() {
System.out.println("保存角色");
}
}
代理类
/*
* 静态(只能代理一种)代理类
*/
public class StaticDaoProxy implements BaseDao {
//接收所有实现BaseDao接口的实现类对象
private BaseDao baseDao;
// 将被代理者的实例传进动态代理类的构造函数中
public StaticDaoProxy(BaseDao baseDao) {
this.baseDao = baseDao;
}
//代理他们实现功能,可以在调用前,调用后额外添加功能.
@Override
public void save() {
System.out.println("before");//额外的扩展功能
baseDao.save();//调用的是真实目标中的方法
System.out.println("after");//额外的扩展功能
}
}
动态代理
动态代理中,代理类并不是在 Java 代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法 调用次数、添加日志功能等等,动态代理分为 jdk 动态代理和 cglib 动态代理 。
jdk代理
是通过反射实现的,借助java自带的java.lang.reflect.Proxy,通过固定的规则生成
-
编写一个委托类的接口,即静态代理的
/* Dao接口,定义保存功能 */ public interface BaseDao { void save(); }
-
实现一个真正的委托类,即静态代理的
/* 实际功能实现类 */ public class UserDaoImpl implements BaseDao { @Override public void save() { System.out.println("UserDaoImpl:save()"); } }
-
创建一个动态代理类,实现 InvocationHandler 接口,并重写该 invoke 方法
/* * 动态代理类 * jdk代理 底层实现使用反射机制 */ public class DynamicDaoProxy implements InvocationHandler { // 被代理类的实例 目标 private Object object;// BaseDao ---> Object(任意的) // 将被代理者的实例传进动态代理类的构造函数中 public DynamicDaoProxy(Object object) { this.object = object; } /* * 覆盖InvocationHandler接口中的invoke()方法 * Object proxy 表示代理对象 * Method method 代理对象中的方法 * Object[] args 表示代理方法中的参数 * 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构 * 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到 * 控制被代理对象的行为,下面的before、after就是我们可以进行特殊 * 代码切入的扩展点了。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before"); Object result = method.invoke(object, args);//用反射机制获取到目标对象中的方法 System.out.println("after"); return result; } }
-
在测试类中,生成动态代理的对象。
public class Test { public static void main(String[] args) { //我们要代理的真实对象 Service层是需要添加事务 UserDaoImpl userDaoImpl = new UserDaoImpl(); //我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的 InvocationHandler dynamicProxy = new DynamicDaoProxy(userDaoImpl); /* * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数 * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象 * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了 * 第三个参数dynamicProxy, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上 */ //动态生成的代理对象 BaseDao baseDao = (BaseDao) Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader(), userDaoImpl.getClass().getInterfaces(), dynamicProxy); // mybatis 接口代理 访问 接口中的方法 baseDao.save(); } }
**动态代理总结:**虽然相对于静态代理,动态代理大大减少了我们的开发任务, 同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注 定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。
Cglib 代理
对于没有接口的类,不能使用jdk代理来实现,此时就需要cglib来了,采用非常底层的字节码技术,通过字节码技术创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,加入逻辑。因为采用的是继承,所以不能对final修饰的类进行代理。
jdk代理和cglib代理都是实现spring aop 的基础。
步骤
1.需要引入 cglib 的 jar 文件,但是 Spring 的核心包中已经包括了 Cglib 功能,所以直接引入 spring-core-xxx.jar 即可.
2.引入功能包后,就可以在内存中动态构建子类
//具体主题
public class UserDaoImpl {
public void save() {
System.out.println("UserDaoImpl:save()");
}
}
3.代理的类不能为 final,否则报错
4.目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
/*
* 动态代理类
*/
public class CGLibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class<?> clazz){
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
/*
* 拦截所有目标类方法的调用
* 参数:
* obj 目标实例对象
* method 目标方法的反射对象
* args 方法的参数
* proxy 代理类的实例
*/
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
//代理类调用父类的方法
System.out.println("开始事务");
Object obj1 = proxy.invokeSuper(obj, args);
System.out.println("关闭事务");
return obj1;
}
}
CGLIB 与JDK代理的区别
CGLIB 创建的动态代理对象比JDK 创建的动态代理对象的性能更高,但是 CGLIB创建代理对象时所花费的时间却比 JDK 多得多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适,反之使用 JDK 方式要更为合适一些。同时由于 CGLib 由于是采用动态创建子类的方法,对于 final 修饰的方法无法进行代理