一、六大设计原则
1、单一职责原则
定义:一个类或者一个模块只负责一个功能。
就比如当我们再写插入、删除、查询功能时,我们最好将三个功能用不同的类去实现。
例子:电脑就相当于一个单一职责原则,显卡、固态、显示器等等,加入某个硬件坏了,我们立马就可以针对该硬件去购买或者维修,不用去换一个新电脑。
2、里氏替换原则
定义:里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。
简单来说,就是子类可以扩展父类的方法,但不能改变父类原有的方法。另外,里氏替换原则,最重要的就是多用组合,少用继承。
多用组合的意思,简单来说就是在代码中我们要多用 private A a;
3、依赖倒置原则
定义:高层模块依赖于接口,而不是具体的实现,底层模块去实现接口,所以当需要修改时,我们只针对具体的实现去修改。简单来说就是面向接口编程。
例子:假设有一个高层模块A,依赖于接口B,底层模块实现C和D实现B的抽象方法,假设抽象方法主要的作用就是存储,C的作用存储在本地中,D是存储在数据库中,当新需求是要求我们存储在redis中(只是一个例子,也可以从数据库中读取),我们就可以针对实现来增加一个模块E来实现存储到redis中。
4、接口隔离原则
定义:建立单一的接口,不要建立臃肿庞大的接口,接口尽量细化。
接口隔离原则就好理解了,就是符合单一职责原则。
5、迪米特法则
定义:一个类要让其调用的类知道的最少,无论内部如何复杂只是自己的事,与其他类无关。
6、开闭原则
定义:对修改关闭,对扩展开放
对某个类我们可以新增某些东西,但是不能修改他原有的东西。
二、23种设计模式
设计模式主要分为三大种类创建型设计模式、结构性设计模式和行为型设计模式。
创建型设计模式
1、单例模式
定义:一个类只能有一个实例,并且只有一个全局访问点。
① 饿汉式
② 懒汉式
③ 双重校验
④静态内部类
我们只接拿代码。
饿汉式:
public class SingletonDemo1 {
private static SingletonDemo1 instance = new SingletonDemo1();
private SingletonDemo1(){}
public static SingletonDemo1 getInstance(){
return instance;
}
}
饿汉式单例模式就是在类加载的时候就立即初始化,并且创建单例对象。不管你有没有用到,都先建好了再说。它绝对线程安全,在线程还没出现以前就实例化了,不可能存在访问安全问题。最大的缺点,就是不管用没用,都会占用内存空间。
懒汉式:
public class SingletonDemo2 {
//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
private static SingletonDemo2 instance;
//构造器私有化
private SingletonDemo2(){}
//方法同步,调用效率低
public static synchronized SingletonDemo2 getInstance(){
if(instance == null) {
instance = new SingletonDemo2();
}
return instance;
}
}
懒汉式就是只有当我们在使用的时候才会去检查有没有加载类,加载了直接返回,没有加载则加载,线程安全的。
双重校验:
public class SingletonDemo3 {
private volatile static SingletonDemo3 singletonDemo3;
private SingletonDemo3() {}
public static SingletonDemo3 newInstance() {
if (singletonDemo3 == null) {
synchronized (SingletonDemo3.class) {
if (singletonDemo3 == null) {
singletonDemo3 = new SingletonDemo3();
}
}
}
return singletonDemo3;
}
}
双重校验是为防止在多线程情况下,线程阻塞,导致性能下降。当第一个线程调用 newInstance()方法时,第二个线程也可以调用。当第一个线程执行到 synchronized 时会上锁,第二个线程就会变成 MONITOR 状态,出现阻塞。此时,阻塞并不是基于整 个 SingletonDemo3 类的阻塞,而是在 newInstance()方法内部的阻塞,只要逻辑不太复杂,对于 调用者而言感知不到。
静态内部类:
public class SingletonDemo4 {
private SingletonDemo4(){
System.out.println("SingletonDemo4");
}
/** 静态内部类 */
private static class SingletonClassInstance {
private static final SingletonDemo4 instance = new SingletonDemo4();
}
/** 只有在第一次调用时,才会被创建,可以认为是懒加载的升级版本 */
public static SingletonDemo4 getInstance(){
return SingletonClassInstance.instance;
}
public static void main(String[] args) {
System.out.println("args = " + Arrays.deepToString(args));
SingletonDemo4.getInstance();
SingletonDemo4.getInstance();
}
}
最后只输出一遍,为什么这种静态内部类是线程安全的呢?这就要从类加载最后一个过程类的初始化说起(类加载过程加载-验证-准备-解析-初始化),类的初始化就是执行类构造器的clinit方法,什么是clinit方法呢?他是由静态成员的赋值语句和静态代码块组成的,而JVM会保证一个类下的clinit正确加锁同步,只会有一个线程去执行clinit方法,其他的线程就会阻塞,等待clinit方法执行完毕,其他的线程不会再进入clinit方法中。
2、原型模式
定义:简单来说就是不通过new来创建对象了,而通过clone的方式来创建对象。
原型模式最重要的就是深拷贝与浅拷贝,
什么是浅拷贝呢?
简单来说就是当我们对某个对象进行clone时,这个对象内引用的其他对象不会被复制,在虚拟机中只会存在一份。
什么是深拷贝?
与浅拷贝相反,它会把引用的其他对象复制一份。
3、工厂方法模式
定义:定义了一个创建对象的接口(类或接口中的方法),但由子类决定要实例化的类是哪一个。工厂方法把实例化推迟到子类。
例子:有球鞋这个接口,有子类adidas和子类nike,就由子类确定要实例化的是那种球鞋。
4、抽象工厂
定义:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
简单来说,就是其功能就是当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。
5、建造者模式
定义:使用生成器模式,来封装一个产品的构造过程。将一个复杂对象的创建过程封装起来。允许对象通过多个步骤来创建,并且可以改变过程。向客户隐藏产品内部的表现。产品的实现可以被替换,因为客户只看到一个抽象接口。
例子:当我们买车的时候我们可能需要一些定制化的东西,当车交到我们手里的时候,这些定制化的东西的安装过程对我们来说是没有任何感觉的。
结构性设计模式
1、代理模式
定义:代理模式为另一个对象提供一个替身或者占位符,以此来控制对这个对象的访问。
SpringAOP中采用的JDK动态代理,就是最典型的例子。
public interface Subject {
void request();
}
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("-----RealSubject 开始执行业务操作-----");
}
}
public class Proxy implements Subject {
// 被代理的对象
private Subject subject;
public Proxy(Subject subject) {
this.subject = subject;
}
@Override
public void request() {
beforeProcessor();
subject.request();
afterProcessor();
}
private void beforeProcessor() {
System.out.println("-----Proxy before processor-----");
}
private void afterProcessor() {
System.out.println("-----Proxy after processor-----");
}
}
public class ProxyTest {
public static void main(String[] args) {
// Subject subject = new RealSubject();
Subject subject = new Proxy(new RealSubject());
subject.request();
}
}
解决问题:当我们想要对一个业务类进行某些横切性的增强时,例如:增加请求与响应的日志、增加权限校验、增加远程请求对象封装等等。我们可以采用代理模式去实现,而不需要修改原有的类。
2、适配器模式
定义:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
3、桥接模式
定义:将抽象部分和实现部分,分离解耦,使得两者可以独立地变化。桥接模式通过将实现和抽象放在两个不同的类层次中而使它们可以独立变化。将实现予以解耦,抽象和实现可以独立扩展,不会影响到对方。对于“具体的抽象类”所做的改变,不会影响到客户。这个就不用图或者代码来给大家展示,因为我自己越看越复杂(主要是不好讲),所以口述一下。
就比如说一辆摩托车,它主要有颜色排量两种,颜色是一个接口,排量是一个抽象类,我们在抽象类将接口引入,这就相当于桥接。
4、装饰者模式
定义:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更弹性的替代方案。
例子:购买烤冷面时,我们选择加肠,加蛋,之后可能会有鸡柳、辣条等等辅料,我们只需要把之前加的所有东西的价格传入到新加入的辅料里,这就是装饰者模式。
5、门面模式
定义:提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
6、享元/蝇量模式
定义:享元模式是池技术的重要实现方式,其定义如下:使用共享对象可以有效地支持大量的细粒度的对象。了解一下就可以
7、组合模式
定义:允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
行为型设计模式
1、模板方法模式
定义:在一个方法中定义一个算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
例子:比如我们导航去某个地方时,我们需要经过几个步骤,第一个步骤打开导航,第二个步骤搜索目的地,然后开始导航,我们就会发现有好几种路线可以让我们选择,有可能有一条路修完了又增加一条路线,我们可以将路线放到子类中去加载,即使后面增加无数种路线,我们只需修改子类就好。
2、策略模式
定义:定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
通过策略模式,我们可以根据不同的行为选择不同的策略。这种设计使得算法的变化不会影响到客户端代码,提供了更好的灵活性和可扩展性。同时,策略模式也可以使代码更加可读和易于维护,因为每个具体的策略类都有明确的职责和行为。
3、命令模式
定义:将“请求”封装成命令对象,以便使用不同的请求、队列或日志来参数化其他对象。
命令模式的核心角色:
-
命令(Command):
- 定义了执行操作的接口,通常包含一个
execute
方法,用于调用具体的操作。
- 定义了执行操作的接口,通常包含一个
-
具体命令(ConcreteCommand):
- 实现了命令接口,负责执行具体的操作。它通常包含了对接收者的引用,通过调用接收者的方法来完成请求的处理。
-
接收者(Receiver):
- 知道如何执行与请求相关的操作,实际执行命令的对象。
-
调用者/请求者(Invoker):
- 发送命令的对象,它包含了一个命令对象并能触发命令的执行。调用者并不直接处理请求,而是通过将请求传递给命令对象来实现。
-
客户端(Client):
- 创建具体命令对象并设置其接收者,将命令对象交给调用者执行。
4、责任链模式
定义:当你想要让一个以上的对象有机会能够处理某个请求的时候,就使用责任链模式。使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
例子:公司领导一般都是项目经理、副总经理、总经理、财务、财务主管、董事长、技术主管,这里每个人都是一个能处理请求的对象,但是有些请求就只能一个对象来处理,就比如说当有一个面试者来面试的时候,项目经理先接受面试的请求,但是发现自己没办法面试,所以交给技术主管,技术主管发现自己能面试,所以这个请求指挥到技术主管这里,而这个链就是责任链。
5、状态模式
定义:允许对象在内部状态改变时改变它的行为,在外部看来像是这个类发生改变了。就是当我们在创建一个A类之后,又创建了不同状态类B、C、D都实现接口E,当我们在A类中引入接口E,当我们在传入BCD三个不同的类时,A类的行为是不一样的这就是状态模式。
6、观察者模式
定义:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
例子:班级群就是一个观察者模式,每个学生就相当于观察者,当班级新转入一名学生时就相当于注册一个新的观察者,转校走后的学生就相当于删除一个观察者。
7、中介者模式
定义;用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。
例子:在上图中可以看出来采购、销售和库存相互依赖,每当我们想修改某个模块时,其他的模块也必须修改,所以中介者就出现了,其他的三个模块只需要依赖于中介者模块,当我们想要修改某个模块时,只需要把中介者模块修改就好了,这就是中介者模式。
优点:1、降低了类的复杂度,将一对多转化成了一对一。 2、各个类之间的解耦。 3、符合迪米特原则。
8、迭代器模式
定义:提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
例子:JAVA 中的 iterator。
9、访问者模式
定义:表示一个作用于某个对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
根据不同的客户,来定制不同的价格。
10、备忘录模式
定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。当你需要让对象返回之前的状态时(例如:你的用户请求“撤销”),就使用备忘录模式。
例子:excel撤销就相当于一个备忘录模式。
11、解释器模式
定义:定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
后面会持续更新(关注我私信领取12万字面试总结文档)
觉得还不错 ,关注点赞收藏❤️❤️❤️❤️