全文2.6W余字,读完需要20分钟,介绍23种设计模式,每个模式都有案例与真实使用场景,能够帮助初学者快速了解设计模式,建立起对代码中设计模式的初步理解,要明确:设计模式只是帮助我们更好的设计代码架构,低耦合高内聚是我们不变的目标。
参考文献:《Head First 设计模式》【美国】弗里曼
文章目录
JAVA DesignMode
在学习设计模式之前,建议首先学习UML类图。
统一建模语言(Unided Modeling Language)是用来设计软件的可视化建模语言,UML从目标系统的不同角度出发,定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、部署图等9种图。
一,UML类图
类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其它类的关系等。
1.1 类图的表现方式
在UML类图中,类使用包含类名、属性(field)和方法(method)且带有分割线的矩阵来表示,比如下图就表示一个Employee类:
1.2 类与类之间的关系表示方式
关联关系时对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、上级与下级等。关联关系时类与类之间最常用的一种关系,分为一般关联关系、聚合关联关系和组合关系。
-
(一般)关联关系
关联关系又可以分为单向关联、双向关联、自关联。
-
单向关联
在UML类图中,单向关联用一个带三角箭头的实线表示。上图中每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现。
-
双向关联
所谓双向关联,就是双方各自持有对方类型的成员变量,在UML类图中,双向关联用一个不带箭头的实线表示。上图中在Customer类中维护了一个List,表示一个顾客可以购买多个商品;在Product类中维护一个Customer类型的成员变量则表示这个产品被哪个顾客所购买。
-
自关联
自关联在UML类图中,用一个带三角箭头且指向自身的线表示。上图表示Node类包含类型为Node的成员变量,也就是自己包含自己。
-
-
聚合关系
聚合关系是关联关系中的一种,是强关联关系,表示整体与部分之间的关系。
其也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如学校与老师的关系,学校是整体包含老师这个成员对象,如果学校休学了那老师依然存在。在UML类图中,聚合关系可以用空心的菱形实现来表示,菱形指向整体部分。
-
组合关系
组合关系表示类之间整体与部分的关系,但它是一种更强烈的聚合关系。
在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在了,那么部分对象也将不复存在,部分对象不能脱离整体对象而存在,例如头和嘴巴的关系,没有了头嘴巴也就不存在了。在UML类图中,组合关系使用带实心菱形的实线来表示,实心菱形指向整体部分。
-
依赖关系
依赖关系时一种使用关系,它是对象之间耦合度最弱的一种关系,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖的类)中的某些方法来完成一些职责。
在UML类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示司机与汽车的关系图,司机驾驶汽车:
-
继承关系
继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。
在UML类图中,泛化(继承)关系用带空心的三角箭头的实线来表示,箭头从子类指向父类。例如Student类和Teacher类都是Person类的子类:
-
实现关系
实现关系时接口与实现类的关系,在这种关系中,类实现了目标接口,类中的操作实现了接口中所声明的所有抽象操作。
在UML类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如汽车和船都实现了交通工具接口:
二,设计原则
模式:就是在某种情景下,针对某问题的某种解决方案
- 抽象原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
- 组合原则:多用组合,少用继承(Java中是单继承)。
- 面向接口编程原则:针对接口编程,而不是针对实现编程(Spring中的ApplicationContext高级展现形式就体现了该原则)。
- 松耦合编程原则:为了交互对象之间的松耦合设计而努力。
- 开闭原则:类应该对扩展开放,对修改关闭。
- 依赖倒置原则:依赖抽象,不要依赖具体类。
- 最少知识原则:只和你的密友谈话。
- 好莱坞原则:别调用(打电话)我们,我们会调用(打电话)给你。
- 单一责任原则:一个类应该只有一个引起变化的原因(高内聚:用来度量一个类或模块紧密地达到单一目的或责任)。类的每个责任都有改变的潜在区域,超过一个责任,则意味着超过一个改变的区域。当一个模块或一个类被设计成只支持一组相关的功能时,我们称之为该类是高内聚的。
三,创建型模式:
1)工厂方法模式
工厂方法模式是简单工厂模式的进一步抽象和推广,它让类的实例化推迟到子类中进行。工厂方法模式定义了一个创建对象的接口,但是要由子类来决定实例化的类是哪一个,把创建对象的操作下发到对象工厂中去完成。
工厂方法模式常用于解决对象多态的问题,对应对象的创建应当把创建方法与实例化方法解耦,类似现在要做一个披萨,但是这个披萨有很多种类型,在实例化时想根据用户的需求动态的创建披萨,这时候就可以用到工厂方法模式,让工厂来帮助我们创建对象。
注意:正如前面所说,工厂方法让子类来决定要实例化的类是哪一个,这里所谓的“决定”,并不是指模式运行子类本身在运行时做决定,而是指在编写创建类时,不需要知道实际要创建的产品是哪一个,选择了使用哪个子类自然就决定了实际创建的产品是什么。
工厂方法模式类图:
在Spring中Bean的创建就使用了工厂方法模式,FactoryBean是Spring中的一个接口,常用来获取单例Bean实例。当一个类实现了FactoryBean接口,那么当我们从Spring IOC容器中获取该类的实例时,Spring会调用
getObject()
方法(如果子类重写了该方法),把方法的返回结果给我们。
// 定义一个Bean, 实现FactoryBean(工厂模式)接口
@Component
public class MyFactoryBean implements FactoryBean<MyFactoryBean> {
private String name;
@Override
public MyFactoryBean getObject() {
MyFactoryBean myFactoryBean = new MyFactoryBean();
myFactoryBean.setName("通过getObject方法初始化实例=========");
System.out.println("(1) MyFactoryBean.getObject()");
return myFactoryBean;
}
@Override
public Class<?> getObjectType() {
System.out.println("(2) MyFactoryBean.getObjectType()");
return MyFactoryBean.class;
}
// 省略构造函数、get、set、toString方法
}
我们先看容器启动之后,三个bean的情况,打上断点查看((AnnotationConfigApplicationContext) context).beanFactory.singletonObjects.get("myFactoryBean")
可以清楚的看到此时的myFactoryBean实例是Spring通过无参构造函数来创建的,name还是null值:
当程序执行context.getBean(MyFactoryBean.class)
代码时:
- Spring准备从ioc容器中获取该bean;
- 发现该bean实现了FactoryBean接口,说明这是一个工厂bean,那么就不从ioc容器中获取该bean的实例了,去调用
FactoryBean.getObject()
方法获取其bean的实例; - ioc容器虽然已经为该bean做了实例化的操作,但是由于你实现了FactoryBean接口,你是一个工厂Bean,所以你的实例还是自己去创建吧!将Bean的实例化推迟到子类中进行。
(Spring)工厂方法模式定义了一个创建对象的接口(FactoryBean),但是要由子类(MyFactoryBean)来决定实例化的类是哪一个,把创建对象的操作下发到对象工厂(实现了FactoryBean接口的类)中去完成。
2)抽象工厂模式
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么,这样一来,客户就可以从他们的具体产品中解耦。抽象工厂中的每个方法实际上都是工厂方法。抽象工厂的任务是定义一个负责创建一组产品的接口,这个接口内的每个方法都负责创建一个具体的产品,同时我们利用实现抽象工厂的子类来提供这些具体的做法。
抽象工厂与工厂方法的区别:
- 整个工厂方法模式,不过是通过子类来创建对象(比如创建一个披萨),用这种方法,客户只需要知道他们所使用的抽象类型就可以了,而由子类来负责决定具体的类型。所以工厂方法模式只负责将客户从具体类型中解耦;
- 抽象工厂模式提供了一个用来创建一个产品家族的抽象类型(比如创建一个披萨,但是还要包含披萨的原材料),这个类型的子类定义了产品被产生的方法;
- 工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个;
- 工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。
抽象工厂模式类图:
Spring中的BeanFactory就是采用了抽象工厂模式的设计思路,当你的bean实现BeanFactory接口时,实例化bean会推迟到具体子类中进行,而在Spring的内部,BeanFactory接口又被其他抽象工厂互相实现,Spring org.springframework.beans.factory.BeanFactory
源码:
/**
* BeanFactory 接口中的方法, 其中都是有关于Bean的操作, 要么通过name, 类型的字节码去获取Bean, 要么就是根据名称或者ResolvableType来判断
* Bean是singleton还是prototype的, 但是这里面没有关于Bean注入的内容, Spring底层提供了Bean注册中心, 而BeanFactory接口主要是声明获取Bean信息的相关方法.
*/
public interface BeanFactory {
// 1. beanFactory 自带的bean 前缀
String FACTORY_BEAN_PREFIX = "&";
// 2. 根据bean的name获取Bean, Bean的默认name为首字母小写的类名
Object getBean(String name) throws BeansException;
// 3. 根据bean的name获取Bean, 并转化为指定的对象
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
// 4. 根据bean的name获取Bean, 并传入构造该bean的参数, 用于有参构造
Object getBean(String name, Object... args) throws BeansException;
// 5. 根据字节码获取bean
<T> T getBean(Class<T> requiredType) throws BeansException;
// 6. 根据字节码获取bean, 并传入构造该bean的参数, 用于有参构造
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
// 7. 根据字节码获取指定bean 的提供者
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
// 8. 根据指定的解析类型获取bean 的提供者
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
// 9. 判断工厂中是否包含指定bean
boolean containsBean(String name);
// 10. 根据beanName来判断该bean是否是单例的
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
// 11. 根据beanName来判断该bean是否是原型对象
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
// 12. 根据名称判断Bean 是否能被指定的可解析类型解析
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
// 13. 根据名称判断Bean 是否被指定的字节码对应的类解析
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
// 14. 根据名称获取bean的类型对应的字节码
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
// 15. 根据名称获取Bean 的类型对应的字节码, 并设置是否允许Bean初始化
@Nullable
Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
// 16. 获取Bean 的别名
String[] getAliases(String name);
}
在Spring中,自带的BeanFactory接口实现类就有大几十个,BeanFactory顶层接口中声明了关于获取Bean信息的方法,其他所有的工厂都是BeanFactory的子类,虽然在BeanFactory中并没有显示的声明具体有哪些工厂,但这些工厂都会来实现BeanFactory接口。这就是抽象工厂模式的设计方法,BeanFactory就相当于是抽象工厂,而其继承子类就相当于产品,不同的工厂类就相当于不同的产品。抽象工厂模式提供一个接口(BeanFactory),用于创建相关或依赖对象的家族,而不需要明确指定具体类。
3)建造者模式
建造者模式又称之为生成器模式,是将一个复杂对象的构建与它的表示进行分离,使得同样的构建过程可以创建不同的表示。用户只需要指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。
建造者模式与工厂模式最大的区别在于,建造者模式更关注产品的组合方式与装配顺序,而工厂模式专注的是产品本身(换句话说:建造者模式更注重过程,而工厂模式只关注结果)。
建造者模式类图:
JDK中的建造者模式: