oop设计模式
设计模式
软件设计模式是一套反复使用,经验性的总结,具有一定普遍性,可以反复使用
1.软件设计原则
1.1开闭原则
对拓展开放,对修改关闭。在程序需要进行拓展时,不去修改原有的代码,实现一个热插拔的效果,简而言之,是为了使程序的拓展性好,易于维护和升级。
想要达到这样的效果,我们需要使用接口和抽象类。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
1.2 里氏代换原则
里氏代换原则是面向对象设计的基本原则之一。
里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
1.3 依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
2.创建型模式
-
单例模式
-
工厂方法模式
-
抽象工厂模式
-
原型模式
-
建造者模式
2.1 单例设计模式
单例模式是Java中最简单的设计模式之一,
2.1.1 单例模式的结构
- 单例类。只能创建一个实例的类
- 访问类。使用单例类
2.1.2 单例模式的实现
单例模式分两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,首次使用该对象被创建
-
饿汉式
方式一(静态变量方式)
/** * @author ==> 许帅帅 * 2022/12/16 16:25 *单例模式 ==> 饿汉式:静态成员变量方式 */ public class Singleton { //创建私有构造方法 private Singleton(){} //创建该类的对象,私有对象 private static Singleton singleton = new Singleton(); //设置一个暴露的方法,供外界使用这个单例对象 public static Singleton getSingleton(){ return singleton; } }
说明:
该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
方式二-静态代码块方式
/**
* @author ==> 许帅帅
*单例模式的第二种方式,使用静态代码块进行创建对象
*/
public class Singleton {
//私有构造方法,为了使用单例,不提供公开方法
private Singleton(){}
//创建私有对象
private static Singleton singleton;
//使用静态代码块为私有对象赋值
static {
singleton = new Singleton();
}
//将私有单例对象暴露出去,给外界使用
public static Singleton getSingleton(){
return singleton;
}
}
说明:
该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本上一样,当然该方式也存在内存浪费问题。
-
懒汉式
方式一:线程锁方式
说明:
该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。
/**
* @author ==> 许帅帅
* 线程安全的懒汉式----单例实现
* 在暴露为外界使用的方法上加上一把线程锁
* 只有在A线程执行完毕之后,才会执行b线程
*加上锁之后,会导致性能衰减
*/
public class Singleton_lazy {
private Singleton_lazy(){}
private static Singleton_lazy instance;
private static synchronized Singleton_lazy getInstance(){
//如果instance为空,就会创建一个,不为空,返回当前已经创建的对象,保证是单例的
if (instance == null){
instance = new Singleton_lazy();
}
return instance;
}
}
方式二(双重检查锁方式)
再来讨论一下懒汉模式中加锁的问题,对于 getInstance()
方法来说,绝大部分的操作都是读操作, 读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时 机。由此也产生了一种新的实现模式:双重检查锁模式
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测 锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的 原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile
关键字, volatile
关键字可以保 证可见性和有序性。
/**
* @author ==> 许帅帅
*懒汉式----双重检查锁方式
*/
public class Singleton_lazy {
private Singleton_lazy() {}
//volatile 关键字可以保证可见性和有序性
private static volatile Singleton_lazy instance;
public static Singleton_lazy getInstance() {
//双重检查锁
if (instance == null) {
synchronized (Singleton_lazy.class) {
if (instance == null) {
instance = new Singleton_lazy();
}
}
}
return instance;
}
}
-
静态内部类方式
/** * @author ==> 许帅帅 * 单例模式----静态内部类方式 */ public class Singleton { private Singleton(){} //定义一个静态内部类,该类随着jvm只会加载一次 private static class SingletonHolder{ private static final Singleton singleton = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.singleton; } }
说明:
第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder
并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
小结:
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
3.1 原型模式
3.1.1 原型模式
浅克隆:在进行拷贝时,拷贝对象引用地址
深克隆:拷贝对象,重新生成一个新的对象引用地址,不使用new,可以使用对象输入输出流
浅克隆
package com.xu.yuanxing.demo02;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/14 22:30
*/
public class Condition implements Cloneable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show() {
System.out.println(name + "获得奖状!");
}
@Override
public Condition clone() throws CloneNotSupportedException {
return (Condition) super.clone();
}
}
package com.xu.yuanxing.demo02;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/14 22:34
*/
public class Cline {
public static void main(String[] args) throws CloneNotSupportedException {
Condition condition = new Condition();
condition.setName("张三");
Condition clone = condition.clone();
clone.setName("李四");
clone.show();
}
}
深克隆
package com.xu.yuanxing.demo03;
import java.io.Serializable;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/15 8:13
*/
public class Condition implements Cloneable , Serializable {
private Student stu;
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
public void show(){
System.out.println(stu.getName()+"获得奖状!");
}
@Override
public Condition clone() throws CloneNotSupportedException {
return (Condition) super.clone();
}
}
package com.xu.yuanxing.demo03;
import java.io.*;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/15 8:15
*/
public class Cline {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
Condition condition = new Condition();
Student student = new Student();
student.setName("张三");
condition.setStu(student);
// Condition clone = condition.clone();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\txt01.txt"));
objectOutputStream.writeObject(condition);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\txt01.txt"));
Student student1 =(Student) objectInputStream.readObject();
condition.setStu(student1);
Student stu = condition.getStu();
System.out.println(stu == student);
condition.show();
}
}
2.3 创建者模式
- 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于: 某个对象的构建过程复杂的情况
- 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象,相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
- 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须道其内部的具体构造细节。
类图
package com.xu.constructor;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/15 19:16
*/
public abstract class Builder {
protected Bike bike = new Bike();
public abstract void createFrame();
public abstract void createName();
public abstract Bike createBike();
}
package com.xu.constructor;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/15 19:20
*/
public class OFOBuilder extends Builder{
@Override
public void createFrame() {
bike.setFrame("钛合金车架");
}
@Override
public void createName() {
bike.setName("OFO");
}
@Override
public Bike createBike() {
return bike;
}
}
package com.xu.constructor;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/15 19:24
*/
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
//组装组件
public Bike construct() {
builder.createName();
builder.createFrame();
return builder.createBike();
}
}
使用场景
- 建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所通常在以下场合使用。
- 创建的对象较复杂,由匆个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
创建者模式升级
使用链式调用,创建对象,给产品一个私有构造方法
package com.xu.constructor.demo02;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/15 19:53
*/
public class Phone {
private String cpu;
private String screen;
private String camera;
private Phone(Builder builder){
this.cpu = builder.cpu;
this.camera = builder.camera;
this.screen = builder.screen;
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", camera='" + camera + '\'' +
'}';
}
public final static class Builder {
private String cpu;
private String screen;
private String camera;
public Builder cpu(String cpu) {
this.cpu = cpu;
return this;
}
public Builder screen(String screen) {
this.screen = screen;
return this;
}
public Builder camera(String camera) {
this.camera = camera;
return this;
}
//创建phone对象
public Phone build(){
return new Phone(this);
}
}
}
3.结构型模式
3.1代理模式
- 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介
- Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
结构
代理 (Proxy) 模式分为三种角色:
- 抽象主题 (Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法
- 真实主题(Real Subjet)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象.
- 代理(Proxy)类提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能
动态代理
JDK动态代理
package com.xu.proxy.JDKProxy;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/16 18:42
*/
public interface Tickets {
void sell();
}
package com.xu.proxy.JDKProxy;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/16 18:42
*/
public class TrainStation implements Tickets{
@Override
public void sell() {
System.out.println("火车站卖票!");
}
}
package com.xu.proxy.JDKProxy;
import java.lang.reflect.Proxy;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/16 18:45
*/
public class ProxyTicket {
private TrainStation trainStation = new TrainStation();
//获取代理对象
public Tickets getPoxy() {
//JDK提供的代理
Tickets tickets = (Tickets) Proxy.newProxyInstance(
//获取类加载器
trainStation.getClass().getClassLoader(),
//反射获取接口
trainStation.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理点收费==>JDK动态代理!");
//反射传入方法
Object invoke = method.invoke(trainStation, args);
return invoke;
}
}
);
return tickets;
}
}
CGlib动态代理
package com.xu.proxy.CGlibProxy;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/16 19:42
*/
public class TrainStation {
public void sell(){
System.out.println("火车站卖票!");
}
}
package com.xu.proxy.CGlibProxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author ==> 许帅帅
* @version ==> 1.0
* 2023/3/16 19:43
*/
public class ProxyFactory implements MethodInterceptor {
private TrainStation station = new TrainStation();
public TrainStation getProxy(){
//创建enhance对象,相当于jdk代理中的Proxy类
Enhancer enhancer = new Enhancer();
//设置父类字节码对象
enhancer.setSuperclass(TrainStation.class);
//设置回调函数
enhancer.setCallback(this);
//创建代理类对象
TrainStation trainStation = (TrainStation) enhancer.create();
return trainStation;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理收费");
Object invoke = method.invoke(station, objects);
return invoke;
}
}
- jdk 代理和 CGLIB 代理
使用 CGLib 实现动态代理, CGLib 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用 Java 反射效率要高。唯一需要注意的是, CGLib 不能对声明为 final 的类或者方法进行代理,因为 CGlib 原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对 JDK 动态代理优化之后,在调用次数较少的情况下, JDK 代理效率高于 CGLib 代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比 CGLib 代理效率低一点,但是到JDK1.8的时候, JDK 代理效率高于 CGLib 代理。所以如果有接口使用 JDK 动态代理,如果没有接口使用CGLib代理。 - 动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理
( InvocationHandler . invoke )。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~