设计模式六大原则
●开闭原则
对扩展开放,对修改关闭,在代码层面而言就是在你有新的需求的时候,你应当增加新的对象来实现,而不是修改原来的对象
继承
●里氏替换原则
基类可以出现,那么子类一定也能行
- 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法
- 子类中可以增加自己特有的方法
- 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格
接口隔离原则
用多个专门的接口比使用单一的总接口要好。一个类对另外一个类的依赖性应当是建立在最小的接口上的;
一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。
举个栗子:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系
●依赖倒转原则
针对接口编程
高层模块不应该依赖低层模块,两者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象
也就是说,尽量依赖接口,不要依赖细节
例如:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
●迪米特法则,最少知道原则
减少耦合
又叫做最少知道原则,就是说一个对象应当对其它对象有尽可能少的了解,类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大,所以一个对象应该对其他对象保持最少的了解,也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息,迪米特法则初衷在于降低类之间的耦合。由于每个类尽量减少对其它类的依赖,因此。很容易使得系统的功能模块独立,相互之间不存在(或很少代码体现有)依赖关系。
举个栗子:Leader想从Teacher那里知道现有Student的的总数,它们之间的调用关系应该为Leader—>Teacher—>Student,Leader与Student并无直接联系,所以在Leader类的方法中不应该出现Student类
●合成复用原则
尽量合成或者聚合而不是继承
模板方法模式:
Spring中jdbcTemplate、hibernateTemplate 等以Template结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式:
我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
工厂模式
工厂设计模式: Spring使用工厂模式通过BeanFactory、ApplicationContext 创建bean对象。
简单工厂:一个工厂类根据传入的参数决定创建出那一种产品类的实例。
优点:创建对象分离开,解耦,工厂负责创建对象,程序员只需要去调用就行。
创建一个汽车的接口,定义一个汽车名字的方法,然后定义几个品牌类去实现这个接口,
然后去写一个工厂
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bUnncBVE-1670295589080)(…/img/image-20220831145555660.png)]
当时当想给添加别的汽车品牌的时候,就需要添加代码,违反开闭原则,需要修改工厂的代码
缺点:当我们新增类的时候,需要去修改工厂配置,增加创建对象的分支,每次都要去修改
抽象工厂模式:
工厂的工厂:一个大工厂,里面有具体的不同产品的小工厂,每个生成的工厂都能按照简单工厂模式提供对象,他们自己决定生产哪一些产品
比如苹果有创建苹果的工厂,各自有各自的工厂,免除了简单工厂模式的每次新增就会修改工厂代码,各自负责自己的工厂创建,每个工厂决定到底生产哪些产品
单例模式
单例设计模式:Spring中的Bean默认都是单例的。
概念
全世界就只要一个—在整个java程序中,只有这个类的一个实例
比如Student a = new Student(); 就是Student类只创建这一个实例,只能有这一个对象存在
主要解决:一个全局使用的类频繁地创建与销毁。在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)
何时使用:当您想控制实例数目,节省系统资源的时候。
关键代码:构造函数是私有的。
1、为了保证只有一个对象,不能new对象,所以设置构造方法私有。
2、只能通过方法或者属性获取对象,如果通过属性获取,这个属性是可以修改的,所以属性只能
是私有的。所以只能通过方法获取。
3、由于我们不能new对象,所以获取对象的方法定是静态的。属性也得是静态的,因为不是静态的,静态的方法访问不到
**缺点:**没有接口,不能继承,与单一 职责原则冲突,一 个类应该只关心内部逻辑, 而不关心外面怎么样来实例化。
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
**注意事项:**getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
**枚举:**对象固定,私有构造器
饿汉
类加载后一开始就会创建对象,
缺点就是占内存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ZQ7CksV-1670295589081)(…/img/image-20220831165809527.png)]
懒汉模式
需要的时候才会去创建对象
好处:节省内存 坏处用的时候才创建稍微有点慢
线程不安全,当创建了100个线程,调用getInstance方法时,可能会有多个线程同时看到没有new,就会执行多次new,调用多次构造方法(也就是会进行多次初始化)
线程安全 给getInstance方法加synchronize锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kf8Pfus0-1670295589082)(…/img/image-20220831165549693.png)]
双重校验锁–安全的懒汉式
我们可以选择不给getInstance方法加synchronize锁,而是在这个方法里面去进行加synchronize锁,因为方法锁的范围太广,其他线程阻塞的范围就大,时间就长。
双锁是懒汉为了解决线程安全演变来的
当多线程调用getInstance,不知道是不是第一次调用,有没有创建对象,所以要判断一下属性是否为空
为空需要创建对象,但是在多线程的情况下,访问这个方法的情况下,多个线程可能会创建多个对象,就不是单例了
所以在判断是不是为空后,需要给这个对象加synchronize锁。
假设第一个线程执行完,接下来的线程就会来进行竞争锁,它还是要继续判断是否为空,不为空,直接return
synchroniz不能防止指令重排序,instance = new Instance () ;可能会发生指令重排序,就是另一个线程会访问到Instance 仍然为空,再次进入到第一个if循环
类属性加volatile的原因:第一个线程加锁之后创建了对象,能够让其他线程及时发现 instance 不是空了,这样其他线程就不用进入第一层判断了,加快了程序速度;如果没有加volatile,不能防止指令重排序,可能对象创建了,但是句柄的地址没有传给属性,判断时还是空,这样就需要继续走第一层if。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F2yfo0tB-1670295589082)(…/img/image-20220905152440380.png)]
建造者模式
它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成.
建造者模式包括的角色:
(1)Builder:给出一个抽象接口或抽象类,以规范产品的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建,一般由子类具体实现。
(2)ConcreteBuilder:Builder接口的实现类,并返回组建好对象实例。
(3**)Director(指挥者):调用具体建造者来创建复杂对象的各个部分**,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
(4)Product:要创建的复杂对象,产品类。
建造者模式的使用场景:
(1)当产品有复杂的内部构造时(参数很多)。
(2)需要生产的产品的属性相互依赖,这些属性的赋值顺序比较重要时(因为在调用ConcreteBuilder的赋值方法时是有先后顺序的)。
建造者模式优缺点
建造者模式的优点:
(1)建造模式是将复杂的内部创建封装在内部,对于外部调用的人来说,只需要传入建造者和建造工具,对于内部是如何建造成成品的,调用者无需关心,良好的封装性是建造者模式的优点之一。
(2)建造者类逻辑独立,易拓·1`展。
建造者模式的缺点:
很明显产生了多余的Build对象以及Dirextor对象,消耗了内存。
要组装一台电脑(Computer类),我们假设它有三个部件:CPU 、主板以及内存。
在Computer类中提供三个set方法分别设置这三个属性。
public class Computer {
private String mCpu;
private String mMainboard;
private String mRam;
public void setmCpu(String mCpu) {
this.mCpu = mCpu;
}
public void setmMainboard(String mMainboard) {
this.mMainboard = mMainboard;
}
public void setmRam(String mRam) {
this.mRam = mRam;
}
}
Builder类
里面提供了安装CPU、主板和内存的抽象方法,以及组装成电脑的create方法
public abstract class Builder {
public abstract void buildCpu(String cpu);
public abstract void buildMainboard(String mainboard);
public abstract void buildRam(String ram);
public abstract Computer create();
}
Builder实现类
里面不仅新建了Computer的实例,还提供了安装CPU、主板和内存的具体实现方法,并且在组装成电脑的create方法中将该Computer对象实例返回
public class MyComputerBuilder extends Builder {
private Computer mComputer = new Computer();
@Override
public void buildCpu(String cpu) {
mComputer.setmCpu(cpu);
}
@Override
public void buildMainboard(String mainboard) {
mComputer.setmMainboard(mainboard);
}
@Override
public void buildRam(String ram) {
mComputer.setmRam(ram);
}
@Override
public Computer create() {
return mComputer;
}
}
指挥者(Director)类用来规范组装电脑的流程顺序,先安装主板,再安装CPU,最后安装内存并组装成电脑。
public class Direcror {
Builder mBuild=null;
public Direcror(Builder build){
this.mBuild=build;
}
public Computer CreateComputer(String cpu,String mainboard,String ram){
//规范建造流程,这个顺序是由它定的
this.mBuild.buildMainboard(mainboard);
this.mBuild.buildCpu(cpu);
this.mBuild.buildRam(ram);
return mBuild.create();
}
}
Builder mBuilder = new MyComputerBuilder();
Direcror mDirecror=new Direcror(mBuilder);
mDirecror.CreateComputer("i7","Intel主板","mRam");//返回Computer实例对象
观察者模式
定义对象间的一种一对多的关系,当一个对象状态发生改变时,所有依赖这个对象的都会得到通知,自动更新,比如微信的订阅,当作者发文则通知订阅者。
观察者模式: Spring事件驱动模型就是观察者模式很经典的一-个应用。
代理模式
代理设计模式**:SpringAOP功能的实现。**动态代理
适配器模式
适配器模式:SpringAOP的增强或通知(Advice)使用到了适配器模式、springMVC中也是用到了适配器模式适配Controller。
责任链模式
项目中责任链模式也比较常用,从其概念可以看出其适合的应用场景:链!
避免将一个请求的发送者和接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。
解耦,不仅是该模式的作用和特点,更是软件设计的原则之一。如何来判定某个对象是否有机会处理链上的请求,这个判断的过程是不是像极了过滤器?所以过滤器的设计可以说是该模式的一个特点。
此外,采用职责链模式不仅可以方便扩展(当增加一个接受者时,只需要在链上的适当位置插入对应的处理方法即可),而且可以替换掉代码中可能存在的switch-case或者if-else,从代码简洁的考量也是一个不错的回答角度。(当然,替换掉switch-case或if-else不仅仅只有这种设计模式可以达到)
请求的发送者和接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。
解耦,不仅是该模式的作用和特点,更是软件设计的原则之一。如何来判定某个对象是否有机会处理链上的请求,这个判断的过程是不是像极了过滤器?所以过滤器的设计可以说是该模式的一个特点。
此外,采用职责链模式不仅可以方便扩展(当增加一个接受者时,只需要在链上的适当位置插入对应的处理方法即可),而且可以替换掉代码中可能存在的switch-case或者if-else,从代码简洁的考量也是一个不错的回答角度。(当然,替换掉switch-case或if-else不仅仅只有这种设计模式可以达到)