一、设计模式的七大原则
1.单一职责原则
- 所谓的单一职责原则就是只负责一个职责
- 示例:有车和船,分别有陆地上跑和水里游的特点,如果只写一个run的方法,在里面判断如果是车就输出在陆地上跑,如果是船在水里游。这时很明显就违反了单一职责原则,
解决方法:建立一个在路上跑的类和在水里游的类,new对象的时候根据各个种类传进去,这样一个类只负责一个职责!
缺点:类爆炸,造成的类过多
2.接口隔离原则
就是一个类实现一个接口,但是吧,里面的方法不一定都用得到,违反了该原则,需要把接口拆开。
示例:如果有个接口A,里面有抽象方法a1,a2,a3,a4。类B实现了该接口,但是类B只想使用里面的a1,a2。但又不得不实现a3,a4。这是就违反了该原则,应该讲接口A拆开。客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
3.依赖倒转原则
就是面向接口编程
4.里式替换原则
一句话总结:不要重写父类中的有特定功能的方法
5.开闭原则
对扩展进行开,对修改关。变化时是扩展而不是修改
尊崇的是可以括扩展,但不修改,对修改是关闭的,对扩展是开放的
6.迪米特法则
对扩展进行开,对修改关。变化时是扩展而不是修改
尊崇的是可以括扩展,但不修改,对修改是关闭的,对扩展是开放的
这个法则理解的前提是我们要知道在一个类中使用另一个类的对象的方式
1,在类中定一个全局变量直接new
2,通过方法传参进行传进来
3,来方法内直接new
直接朋友:通过参数传进来或者一个get方法返回一个对象
不直接朋友:比如在一个方法中new一个对象
如果遇到不直接朋友,将相应的代码写到传过来的那个类里面比较好
7.合成复用原则
基本介绍:原则是尽量使用合成/聚合的方式,而不是使用继承
依赖关系:在b类中写个方法,传参进去
聚合关系:一个A类对象的成员变量,通过set方法设置进来
组合关系:b类中A是成员变量,直接new
二、常见设计模式
1.单例模式
所谓类的单例设计模式,就是采取一定的方法保证对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
1.1基本实现方式
实现单例模式有一下几种方法:
1、饿汉式
直接先创建起,不管用不用;用时直接返回给你即可
**
* 饿汉式单例模式
* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943
*/
public class ClassA {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassA(){ }
//2.在类的内部创建一个类的实例
//类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!
private static final ClassA instance = new ClassA();
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
//方法没有同步,调用效率高!
public static ClassA getInstance(){
return instance;
}
//测试
public static void main(String[] args) {
ClassA a = ClassA.getInstance();
ClassA b = ClassA.getInstance();
System.out.println(a==b);
}
}
2、普通懒汉式
用的时候才创建,线程不安全,加锁会影响效率。资源利用率高了,但是,每次调用getInstance()方法都要同步,并发效率较低。
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3、线程同步懒汉式
同步方法:效率太低了,每个线程在想获得类的实例时候,执行 getInstance(O方法都要进行同步。而其实这个方法只执行
次实例化代码就够了,后面的想获得该类实例,直接 return就行了。方法进行同步效率太低
同步代码块(必须双层检查),也叫双重校验锁式:加volatile的作用是禁止指令重排
/**
* 使用双重校验锁来实现单例模式
* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943
*/
public class ClassE {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassE(){ }
//2.在类的内部创建一个类的实例
private volatile static ClassE instance; //volatile作用:保证多线程可以正确处理instance
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassE getInstance(){
if(instance == null){ //检查实例,如果为空,就进入同步代码块
synchronized (ClassE.class){
if(instance == null){ //再检查一次,仍未空才创建实例
instance = new ClassE();
}
}
}
return instance;
}
//测试
public static void main(String[] args) {
ClassE a = ClassE.getInstance();
ClassE b = ClassE.getInstance();
System.out.println(a==b);
}
}
4、静态内部类方式
也即饿汉式和懒汉式的组合,类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的
/**
* 使用静态内部类方式实现单例模式
* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943
*/
public class ClassC {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassC(){ }
//2.在类的内部创建一个类的实例
private static class Holder{
private static ClassC instance = new ClassC();
}
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassC getInstance(){
return Holder.instance;
}
//测试
public static void main(String[] args) {
ClassC a = ClassC.getInstance();
ClassC b = ClassC.getInstance();
System.out.println(a==b);
}
}
5、枚举方法
线程安全,实现简单,调用效率高,不能延时加载。枚举本身就是单例模式,由JVM从根本上提供保障并且可以天然的防止反射和反序列化漏洞!需要继承的场景它就不适用了。枚举方式是Effective Java作者提倡的方式。
/**
* 使用枚举方法实现单例模式
* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943
*/
public enum ClassD {
//定义一个枚举的元素,它就代表了Singleton的一个实例。
INSTANCE;
//对外部提供调用方法:将创建的对象返回,只能通过类来调用
public void otherMethod(){
//功能处理
}
//测试
public static void main(String[] args) {
ClassD a = ClassD.INSTANCE;
ClassD b = ClassD.INSTANCE;
System.out.println(a==b);
}
}
1.2实际应用
在mybatis官方文档中对SqlSessionFactory 的描述:
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
所以在学习mybatis时写了一个工具类专门用来得到sqlSession对象的,其中需要用到SqlSessionFactory ,而SqlSessionFactory 就可以依靠单例模式来实现:
/**
*双重校验锁实现
*/
public class utils {
private volatile static SqlSessionFactory sqlSessionFactory;
public static SqlSession getSqlSession(){
if(sqlSessionFactory == null){
synchronized (utils.class){
//双重检验实现单例模式,再检查一次是为了防止另外一个线程得到锁后
//再执行下面语句创建对象。
if(sqlSessionFactory == null){
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return sqlSessionFactory.openSession();
}
}
工厂模式
2.1简单工厂模式
定义
简单工厂模式属于创建型模式又叫做静态工厂方法模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。
简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
简单实现:
定义一个电脑父类和几个具体的不同品牌的电脑类,使用一个工厂类用于根据传入参数判断创建具体哪一个品牌的电脑类。
public abstract class Computer {
/**
* 产品的抽象方法,由具体的产品类去实现
*/
public abstract void start();
}
联想电脑
public class LenovoComputer extends Computer{
@Override
public void start() {
System.out.println("联想电脑启动");
}
华硕电脑
public class AsusComputer extends Computer {
@Override
public void start() {
System.out.println("华硕电脑启动");
}
}
简单工厂类
public class ComputerFactory {
public static Computer createComputer(String type){
Computer mComputer=null;
switch (type) {
case "lenovo":
mComputer=new LenovoComputer();
break;
case "hp":
mComputer=new HpComputer();
break;
case "asus":
mComputer=new AsusComputer();
break;
}
return mComputer;
}
}
还可以使用静态代码块提前创建好对象节省创建时间
public class RuleConfigParserFactory {
private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>();
static {
cachedParsers.put("json", new JsonRuleConfigParser());
cachedParsers.put("xml", new XmlRuleConfigParser());
cachedParsers.put("yaml", new YamlRuleConfigParser());
cachedParsers.put("properties", new PropertiesRuleConfigParser());
}
public static IRuleConfigParser createParser(String configFormat) {
if (configFormat == null || configFormat.isEmpty()) {
return null;//返回null还是IllegalArgumentException全凭你自己说了算
}
IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
return parser;
}
}
- 工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响;
- 违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂。
- 简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。
2.2工厂方法模式
定义
工厂方法模式,又称工厂模式、多态工厂模式和虚拟构造器模式,通过定义工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。
将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化(创建)哪一个类。
简单实现
步骤1: 创建抽象工厂类,定义具体工厂的公共接口;
步骤2: 创建抽象产品类 ,定义具体产品的公共接口;
步骤3: 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
步骤4:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
步骤5:外界创建具体工厂类对象调用对应方法,从而创建不同具体产品类的实例
抽象工厂
abstract class Factory{
public abstract Product Manufacture();
}
抽象产品
abstract class Product{
public abstract void Show();
}
具体产品
//具体产品A类
class ProductA extends Product{
@Override
public void Show() {
System.out.println("生产出了产品A");
}
}
//具体产品B类
class ProductB extends Product{
@Override
public void Show() {
System.out.println("生产出了产品B");
}
}
具体工厂
//工厂A类 - 生产A类产品
class FactoryA extends Factory{
@Override
public Product Manufacture() {
return new ProductA();
}
}
//工厂B类 - 生产B类产品
class FactoryB extends Factory{
@Override
public Product Manufacture() {
return new ProductB();
}
}
使用
//生产工作流程
public class FactoryPattern {
public static void main(String[] args){
//客户要产品A
FactoryA mFactoryA = new FactoryA();
mFactoryA.Manufacture().Show();
//客户要产品B
FactoryB mFactoryB = new FactoryB();
mFactoryB.Manufacture().Show();
}
}
- 添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度;同时,有更多的类需要编译和运行,会给系统带来一些额外的开销;
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
- 虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类;
- 一个具体工厂只能创建一种具体产品
2.3抽象工厂模式
定义
工厂方法模式引入工厂等级结构,解决了简单工厂模式中工厂类职责过重的问题,但由于工厂方法模式中每个工厂只创建一类具体类的对象,这将会导致系统当中的工厂类过多,这势必会增加系统的开销。此时,我们可以考虑将一些相关的具体类组成一个“具体类族”,由同一个工厂来统一生产,这就是我们本文要说的“抽象工厂模式”的基本思想。
简单实现
1、实例介绍:
课程工厂—>各个语言的课程,每个语言课程包括视频实践课程与文章理论课程
如果按照工厂方法模式我们就需要为每一个语言的视频实践课程与文章理论课程都各自创建一个工厂对象,如果有java和python两个语言,我们就需要有四个课程工厂,分别是java的视频和文章课程工厂和Python的。
这将会导致系统当中的工厂类过多,这势必会增加系统的开销。此时,我们可以考虑将一些相关的具体类组成一个“具体类族”,由同一个工厂来统一生产:
- java课程工厂负责生产java的频实践课程与文章理论课程
- Python课程工厂负责生产Python的视频实践课程与文章理论课程
步骤1: 创建抽象课程工厂类,,定义具体工厂的公共接口;
步骤2: 创建抽象产品类 ,定义具体产品的公共接口;
步骤3: 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
视频实践课程产品、文章理论课程产品
步骤4:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
java课程工厂、 Python课程工厂
步骤5:外界创建具体工厂类对象调用对应方法,从而创建不同具体产品类的实例
抽象课程工厂类
public abstract class CourseFactory(){
void getVideoCourse();//视频课程
void getArticleCourse();//文章课程
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XzHuBWlw-1617940036357)(https:upload-images.jianshu.io/upload_images/2353568-e9c7d72a4476cbb1.png?imageMogr2/auto-orient/strip|imageView2/2/w/618/format/webp)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ne6hkD1r-1617940036359)(https:upload-images.jianshu.io/upload_images/2353568-f08160db9da2bcdb.png?imageMogr2/auto-orient/strip|imageView2/2/w/642/format/webp)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B4E41rqp-1617940036363)(https:upload-images.jianshu.io/upload_images/2353568-f27651bb03ffab24.png?imageMogr2/auto-orient/strip|imageView2/2/w/644/format/webp)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vWqXCxYB-1617940036369)(https:upload-images.jianshu.io/upload_images/2353568-d3d0293d0efed84c.png?imageMogr2/auto-orient/strip|imageView2/2/w/635/format/webp)]
然后创建python产品族的视频和手记,还有手记工厂,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rv0WbcVp-1617940036371)(https:upload-images.jianshu.io/upload_images/2353568-78e687fb0f718f71.png?imageMogr2/auto-orient/strip|imageView2/2/w/630/format/webp)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YxvbgKAb-1617940036375)(https:upload-images.jianshu.io/upload_images/2353568-505699285d101f5a.png?imageMogr2/auto-orient/strip|imageView2/2/w/669/format/webp)]
再然后创建工厂,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WkBJGsu2-1617940036376)(https:upload-images.jianshu.io/upload_images/2353568-2107a38018360b1b.png?imageMogr2/auto-orient/strip|imageView2/2/w/666/format/webp)]
测试,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R67VR3fE-1617940036378)(https:upload-images.jianshu.io/upload_images/2353568-2c7f1998be4b1315.png?imageMogr2/auto-orient/strip|imageView2/2/w/561/format/webp)]
3.代理模式
定义
为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处 是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK 代理、接口代理)和 Cglib 代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
3.1静态代理
定义
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继 承相同父类
简单实例:目标类与接口类略
//代理对象,静态代理
public class TeacherDaoProxy implements ITeacherDao{
private ITeacherDao target; // 目标对象,通过接口来聚合
//构造器
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;//将代理的目标对象实例传进代理对象
}
@Override
public void teach() {
// TODO Auto-generated method stub
System.out.println("开始代理 完成某些操作。。。。。 ");//方法
target.teach();
System.out.println("提交。。。。。");//方法
}
}
3.2动态代理(JDK代理)
定义
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK 代理、接口代理(本质代理的是这个接口!)
- 通过JDK java.lang.reflect.Proxy类的static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h )方法生成代理对象
简单实例
目标对象接口与实现接口的目标对象
//接口
public interface ITeacherDao {
void teach(); // 授课方法
void sayHello(String name);
}
//实现接口的目标类
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
// TODO Auto-generated method stub
System.out.println(" 老师授课中.... ");
}
@Override
public void sayHello(String name) {
// TODO Auto-generated method stub
System.out.println("hello " + name);
}
}
这里再结合使用下简单工厂模式来生成代理对象,只要实现了这个接口的对象都可以使用这个工厂生产对应的代理对象。
public class ProxyFactory {
//维护一个目标对象 , Object
private Object target;
//构造器 , 对 target 进行初始化
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象 生成一个代理对象
public Object getProxyInstance() {
//说明
/*
* public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
//1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
//2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
//3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行
的目标对象方法作为参数传入
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("JDK 代理开始~~");
//反射机制调用目标对象的方法
Object returnVal = method.invoke(target, args);
System.out.println("JDK 代理提交");
return returnVal;
}
});
}
}
测试
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建目标对象
ITeacherDao target = new TeacherDao();
//给目标对象,创建代理对象, 可以转成 ITeacherDa
ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
// proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
System.out.println("proxyInstance=" + proxyInstance.getClass());
//通过代理对象,调用目标对象的方法
proxyInstance.teach();
proxyInstance.sayHello(" tom ");
}
}
3.3Cglib代理
定义
-
静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实 现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理
-
Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代 理归属到动态代理。
-
Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的 框架使用,例如 Spring AOP,实现方法拦截
-
在 AOP 编程中如何选择代理模式:
1、目标对象需要实现接口,用 JDK 代理
2、目标对象不需要实现接口,用 Cglib 代理
-
Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
-
使用需要引入 cglib 的 jar 文件
注意
因为是动态构建子类,所以会有以下情况:
- 在内存中动态构建子类,注意代理的类不能为 final,否则报错 java.lang.IllegalArgumentException:
- 目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
使用
public class ProxyFactory implements MethodInterceptor {
//维护一个目标对象
private Object target;
//构造器,传入一个被代理的对象
public ProxyFactory(Object target) {
this.target = target;
}
//返回一个代理对象: 是 target 对象的代理对象
public Object getProxyInstance() {
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
//重写 intercept 方法,会调用目标对象的方法
@Override
public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
// TODO Auto-generated method stub
System.out.println("Cglib 代理模式 ~~ 开始");
Object returnVal = method.invoke(target, args);
System.out.println("Cglib 代理模式 ~~ 提交");
return returnVal;
}
}