设计模式
软件设计原则&设计模式
设计原则和设计模式的初衷都是为了更好的维护代码,可重用,可扩展,好维护,更稳定,提高可读性,降低变更引起的风险,代码解耦。
七大原则
-
Open-Closed 开闭原则
-
定义:一个软件实体如类、模块、函数等应该对扩展开放,对修改关闭,用抽象构建框架,用实现扩展细节
-
优点:提高软件系统的可复用性以及可维护性。
-
-
Dependence Inversion 依赖倒置
-
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象;针对接口编程,不要针对实现编程
-
优点:可以减少类间的耦合性、提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险。
-
-
Simple Responsibility 单一职责
-
定义:不要存在多于一个导致类变更的原因,一个类、接口、方法只负责单一职责
-
优点:降低类的复杂度;提高类的可读性;提高系统的可维护性;降低变更引起的风险。
-
-
Interface Seqreqation 接口隔离
-
定义:用多个专门的接口,而不是使用单一的总接口,客户端不应该依赖他不需要的接口
-
注意:一个类对应一个类的依赖应该建立在最小的接口上;建立单一接口,不要建立庞大臃肿的解耦;尽量细化接口,接口中的方法尽量少。注意适度
-
优点:符合我们常说的高内聚、低耦合的设计思想;从而使得类具有很好的可读性,可扩展性和可维护性。
-
-
Law of Demeter 迪米特法则
-
定义:一个对象应该对其它对象保持最少的了解,又叫最少知道原则,尽量降低类与类之间的耦合
-
优点:降低类之间的耦合
-
强调:只和朋友交流,不和陌生人说话
-
朋友:出现在成员变量、方法的输入、输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。
-
-
Liskov Subsitution 里氏替换
-
定义:如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
-
定义扩展:一个软件的实体如果适用一个父类的话,那一定适用于其子类,所有引用父类的地方必须能透明的使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
-
引申意义:
- 子类可以扩展父类的功能,但不能改变父类原有的功能
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
- 子类中可以增加自己特有的方法
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入、入参)要比父类方法的输入参数更宽松(如果父类参数是HashMap,子类可以用Map)
- 当子类的方法实现父类的方法时(重写、重载、实现抽象方法),方法的后置条件(即方法的输出、返回值)要比父类更严格或相等(如果父类是Map,子类可以是HashMap)
-
优点:约束继承泛滥,开闭原则的一种体现;加强程序的健壮性,同时变更时也可以做到非常好的兼容性,提高程序的维护性,扩展性。降低需求变更时引入的风险。
-
-
Composite&Aggregate Reuse 合成复用
- 定义:尽量使用对象组合、聚合,而不是继承关系达到软件复用的目的
- 聚合 has-a(电脑和U盘,两者分开来独立能自我完成工作)、组合 contains-a(人体和四肢)、继承 is-a(狗和动物)
- 优点:可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其它类造成的影响相对较少。
- 定义:尽量使用对象组合、聚合,而不是继承关系达到软件复用的目的
设计模式
设计思路
Spring IOC: 工厂、单例、装饰器
Spring AOP: 代理、观察者
Spring MVC: 委派、适配器
Spring JDBC:模板方法
一、工厂模式
简单工厂 -> 工厂方法 -> 抽象工厂
简单工厂模式:
- 简单工厂模式是指有一个工厂对象决定创建出哪一种产品类的实例。
- 属于创建型模式,但不属于GOF23种设计模式。
简单工厂创建逻辑复杂,但是对用户而言就简化了创建逻辑。所以把非常复杂的创建逻辑放进公共类中,作为一个工厂类,那么我们去调用的时候就只需要调用工厂的一个创建方法,传入正确的参数即可。如果在创建之前还有其他业务逻辑处理,简单工厂的使用场景就非常有限了,只能适合于产品逻辑创建逻辑比较稳定,而且产品比较少的情况。增加新产品的时候需要修改工厂类的判断逻辑,违背开闭原则,不易于扩展过于复杂的产品结构。
public class CourseFactory { public ICourse getInstance(Class clazz) { try { if (clazz != null ) if (clazz.equals(JavaCourse.class)) return (JavaCourse) clazz.newInstance(); else if (clazz.equals(PythonCourse.class)) return (PythonCourse) clazz.newInstance(); else return null; } catch (Exception e) { e.printStackTrace(); } return null; } } public class SimpleFactoryTest { public static void main(String[] args) { CourseFactory factory = new CourseFactory(); ICourse iCourse = factory.getInstance(JavaCourse.class); iCourse.getPrice(); iCourse = factory.getInstance(PythonCourse.class); iCourse.getPrice(); } }
工厂方法模式:
工厂方法模式是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。属于创建型设计模式。
工厂方法模式是简单工厂模式的扩展,解决产品链不断丰富,变得越来越多,他的职责也变得越来越复杂的这种问题。首先要准备一个SuperFactory,然后为了满足单一职责,通过不同的子类实例化获取到生产对应产品的子工厂,通过子工厂进行对应产品的实例化。如果后期有新的产品和产品工厂,增加产品链,只需要维护新的工厂继承SuperFactory就好了,不需要改动原来的代码,遵循开闭原则,提高了系统的可扩展性。如果在创建对象需要大量重复代码,还可以放在父类公共处理,子类中调用。但是个数容易过多,增加了代码结构的复杂度,增加了系统抽象性和理解难度。
public abstract class ICourseFactory { public void preCreate() { System.out.println("before getInstance(), do sth."); } abstract ICourse getInstance(); } public class JavaCourseFactory extends ICourseFactory { @Override public ICourse getInstance() { super.preCreate(); return new JavaCourse(); } } public class PythonCourseFactory extends ICourseFactory { @Override public ICourse getInstance() { super.preCreate(); return new PythonCourse(); } } public class MethodFactoryTest { public static void main(String[] args) { ICourseFactory factory = new JavaCourseFactory(); ICourse iCourse = factory.getInstance(); iCourse.getPrice(); } }
抽象工厂:
抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的接口,无需指定他们具体的类。属于创建型设计模式。
抽象工厂适用于产品链,产品族非常复杂的情况,而且产品创建逻辑差异程度非常高,这个时候就可以利用抽象工厂来进行全局的定义和把关,要求每个产品等级、产品族都必须包含抽象接口工厂所拥有的产品。但其实抽象工厂不符合开闭原则,每次抽象接口工厂增加一个产品,所有的子类都需要重新实现一个方法。如果产品经常修改,就不适合抽象工厂模式。优点:具体产品在应用层代码隔离,将一个系列的产品族统一到一起创建。缺点:规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。
/** * Describe:抽象工厂<br> * 要求所有子工厂都实现这个工厂 * * @author Elian * @since 2022/3/14 23:58 */ public interface ICourseFacotry { ICourse createCourse(); INote createNote(); IVideo createVideo(); } public class JavaCourseFactory implements ICourseFacotry { @Override public ICourse createCourse() { return new JavaCourse(); } @Override public INote createNote() { return new JavaNote(); } @Override public IVideo createVideo() { return new JavaVideo(); } } public class PythonCourseFactory implements ICourseFacotry { @Override public ICourse createCourse() { return new PythonCourse(); } @Override public INote createNote() { return null; } @Override public IVideo createVideo() { return null; } } public class AbstractFactoryTest { public static void main(String[] args) { ICourseFacotry facotry = new JavaCourseFactory(); ICourse iCourse = facotry.createCourse(); INote iNote = facotry.createNote(); IVideo iVideo = facotry.createVideo(); iCourse.getPrice(); facotry = new PythonCourseFactory(); iCourse = facotry.createCourse(); iCourse.getPrice(); } }
二、单例模式
单例模式属于创建型模式。
饿汉式:
优点:执行效率高,性能高,没有锁
缺点:某些情况下可能造成内存浪费
// 普通饿汉式 public class HungrySingleton { private static final HungrySingleton instance = new HungrySingleton(); private HungrySingleton() { if (instance != null) throw new RuntimeException("cannot create a instance by reflex, because a existing instance already be created"); } public static HungrySingleton getInstance() { return instance; } } // 静态饿汉式 public class HungryStaticSingleton { private static final HungryStaticSingleton instance; static { instance = new HungryStaticSingleton(); } private HungryStaticSingleton() { if (instance!=null) throw new RuntimeException("cannot create a instance by reflex, because a existing instance already be created"); } public static HungryStaticSingleton getInstance() { return instance; } }
懒汉式:
- 普通懒汉式:方法上加锁,性能差
- 双重检查:性能高,线程安全,代码不优雅
- 静态内部类:代码优雅,利用了Java本身语法特点,性能高,没有锁
// 普通懒汉式 public class LazySimpleSingleton { private static LazySimpleSingleton instance; private LazySimpleSingleton() { if (instance == null) throw new RuntimeException("cannot create a instance by reflex, because a existing instance already be created"); } // 防止多线程创建多个对象,效率低,所有getInstance()方法都在等待释放锁,还要竞争再释放 public static synchronized LazySimpleSingleton getInstance() { if (instance == null) instance = new LazySimpleSingleton(); return instance; } } // 双重检查 public class LazyDoubleCheckSingleton { private static volatile LazyDoubleCheckSingleton instance; // volatile防止指令重排序 private LazyDoubleCheckSingleton() { if (instance != null) throw new RuntimeException("cannot create a instance by reflex, because a existing instance already be created"); } public static LazyDoubleCheckSingleton getInstance() { if (instance == null) { // 第一次检查是否为null,指令重排序阶段重要判断 synchronized (LazyDoubleCheckSingleton.class) { if (instance == null) // 第二次检查,竞得锁后进行判断 instance = new LazyDoubleCheckSingleton(); } } return instance; } } // 静态内部类 public class LazyStaticInnerClassSingleton { private LazyStaticInnerClassSingleton() { if (InnerClass.INSTANCE != null) throw new RuntimeException("cannot create a instance by reflex, because a existing instance already be created"); } public static LazyStaticInnerClassSingleton getInstance() { return InnerClass.INSTANCE; } private static class InnerClass { private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton(); } }
注册式:
- Enum:官方推荐最优雅单例写法,但是不适合大批量创建对象使用
- 容器式:模拟Enum创建的单例模式,适合大批量创建,缺点线程不安全
// Enum式,饿汉式 public enum EnumSingleton { INSTANCE; public static EnumSingleton getInstance() {return INSTANCE;} } //在jdk中 Constructor.newInstance()方法中 if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); // 所以Enum是不能通过反射创建对象的 // Class中,规定:private volatile transient Map<String, T> enumConstantDirectory = null; // 所有枚举对象必须是单例,通过name方式获取实例,所以不存在反序列化时创建不同对象的问题 // 容器式 public class ContainerSingleton { private ContainerSingleton(){} private static Map<String, Object> ioc = new ConcurrentHashMap<>(); public static Object getInstance(String className) { if (!ioc.containsKey(className)) { try { Object instance = Class.forName(className).newInstance(); ioc.put(className, instance); } catch (Exception e) { e.printStackTrace(); } } return ioc.get(className); } }
Thread-0
Thread-1
上图为容器式并发导致实际上new了不同对象。
解决方案:
-
像Spring一样,在初始化时,就将所有单例需要加载到容器中
-
public class ContainerSingleton { private ContainerSingleton(){} private static final Map<String, Object> ioc = new ConcurrentHashMap<>(); public static Object getInstance(String className) { if (!ioc.containsKey(className)) { synchronized (ContainerSingleton.class) { if (!ioc.containsKey(className)) { try { Object instance = Class.forName(className).newInstance(); ioc.put(className, instance); } catch (Exception e) { e.printStackTrace(); } } } } return ioc.get(className); } }
总结:
-
饿汉式执行效率高,性能高,没有任何锁,但是某些情况下,比如从来未使用却有大量饿汉式单例创建会造成空间浪费。
-
懒汉式在方法加锁时,效率低,性能差,双重检查锁又会导致代码不易读,代码量大,静态内部类属于比较优雅的写法,同时不会出现线程问题。
-
注册式中枚举是被官方推荐的写法,但是不适合大批量创建,容器式是为了大批量创建模拟枚举写的方法。
-
哪些会被线程安全破坏单例?
懒汉式在没有加锁的情况下会出现线程安全问题,容器式也会出现线程安全问题,为了解决这一问题,可以模拟Spring中IOC模式,把所有要创建的单例对象,在开始时就加载到容器中。
-
哪些会被反射破坏?
以上除了枚举之外都会被反射破坏,但是可以在构造器中通过手动判断是否存在实例,容器式通过构造器中获取容器get出来实例等方式,拒绝反射创建。
-
那些会被序列化破坏?
除了枚举之外都会被序列化破坏,如果在类中一,添加readResolve方法并返回唯一实例,就可以防止反序列化的破坏。枚举因为其被创建之后就唯一的存在Map集合中所以可以避免反序列化时被破坏。
三、代理模式
定义:代理模式(proxy pattern)是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用。属于结构型设计模式。
适用场景:保护目标对象,增强目标对象
// 找对象 public interface IPerson { void findLove(); } // 我找对象 public class Elian implements IPerson{ @Override public void findLove() { System.out.println("我要求:肤白貌美大长腿"); } } // Tom找对象 public class Tom implements IPerson { @Override public void findLove() { System.out.println("是女的就行"); } }
静态代理(显示声明被代理对象):
// 红娘帮我找对象 public class RedWomen implements IPerson { private IPerson iPerson; public RedWomen(IPerson iPerson) { this.iPerson = iPerson; } @Override public void findLove() { System.out.println("开始物色小姑娘"); iPerson.findLove(); System.out.println("开始交往"); } } public class Test { public static void main(String[] args) { RedWomen redWomen = new RedWomen(new Elian()); redWomen.findLove(); redWomen = new RedWomen(new Tom()); redWomen.findLove(); } }
静态代理的缺点:硬编码,如果继承了其他的接口,会导致代理类中的对象类型不兼容。
只要有找对象的人,红娘就会为它服务,随着找对象的人越来越多,红娘的职责也越来越专业,越来越通用,于是就有了动态代理
动态代理
把某个服务做到更好,更加专注。这也符合单一职责和开闭原则。
基本原理:利用底层逻辑通过字节码重组动态生成一个新的类,这个类就是代理类,这个类只在内存中存在,这个类去继承目标类。在调用代理类这个方法的时候,通过反射机制找到调用目标类的方法。
jdk动态代理:
/** * <h3>Describe:动态代理实现<br><h3> * * @author Elian * @since 2022/3/16 18:08 */ public class JdkRedWomen<T> implements InvocationHandler { private T target; public T getInstance(T target) { this.target = target; Class<?> clazz = target.getClass(); // 创建目标对象的代理对象 // 1. 同一个ClassLoader // 2. 目标对象实现的接口 // 3. 该类本身(该类要实现IvocationHandler接口,并重写invoke方法) return (T) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); } /** * <h3>InvocationHandler的invoke反射方法</h3> * @param proxy 生成的虚拟代理对象 * @param method 调用的方法 * @param args 调用的参数 * @return invoke 方法返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); // 增强方法的实现,以及参数 Object invoke = method.invoke(this.target, args); after(); return invoke; } private void before() { System.out.println("红娘开始物色"); } private void after() { System.out.println("确认关系,开始交往"); } }
cglib动态代理
/** * <h3>Describe:动态代理实现<br><h3> * * @author Elian * @since 2022/3/16 18:08 */ public class CglibRedWomen<T> implements MethodInterceptor { private T target; public T getInstance(T target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); enhancer.setCallback(this); return (T) enhancer.create(); } private void before() { System.out.println("红娘开始物色"); } private void after() { System.out.println("确认关系,开始交往"); } /** * <h3>MethodInterceptor的intercept方法</h3> * @param o 代理对象本身 * @param method 被代理对象的方法 * @param objects 参数 * @param methodProxy 方法的代理 * @return 原方法的返回值 * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object invokeSuper = methodProxy.invokeSuper(o, objects); after(); return invokeSuper; } }
总结:
- jdk动态代理与cglib动态代理的区别在于,jdk需要目标类继承接口,通过这些接口才能创建一个虚拟的类,以及才能知道要去代理哪个方法,而cglib创建了一个目标类的子类,通过对目标方法的前置后置处理实现增强。cglib不能代理final修饰的类,jdk可以。
jdk动态代理特点
- final修饰类、方法
- 目标一定实现接口
- implement InvocationHandler,newProxyInstance(1.同一个类加载器,2. 目标类的接口,3. 实现了InvocationHandler的类)
- jdk只生成一个代理类
- 通过反射调用的
cglib动态代理
- 生成的代理类会继承目标类,目标类不需要实现接口
- 通过传入的o,找到目标方法的索引,通过index索引找到目标类,不需要反射
- implements MethodInterceptor,enhancer设置目标类的类型、实现了MethodInterceptor的类,enhancer.create()
- 不能代理final修饰的方法
- 生成三个代理类
生成代理类时jdk更快,cglib慢,文件少
调用时jdk用的反射,cglib用的fastclass机制通过index去定位方法,速度更快。
我们的代理类还能再被代理吗?
-
jdk代理后再次被jdk代理
同一个handler不可以。invoke调用invoke,死循环。不同的handler可以再次被代理(mybatis中的多重插件就是这么实现的)。
-
jdk代理后再次被cglib代理
不能,jdk生成的代理类是final修饰的,cglib生成代理类继承目标类,不能被代理。
-
cglib代理后的类再次被jdk代理
不能,实现的Factory中,没有被代理的方法了。
-
cglib代理后的类再次被cglib代理
不能,重复的newInstance()。
推到mybatis,事务失效的原理就是代理失效
什么样的类不能被代理?
- 对于jdk,必须有接口实现
- final修饰的方法不能被cglib代理
- 方法不是public的
接口能被代理吗?
接口能被代理,比如MyBatis中的mapper就是这样。
工作中有没有用过代理?
源码里面有哪些使用代理的?
四、原型模式(创建型模式)克隆模式
浅克隆:
不复制对象的引用,对于引用属性,使用同一地址
public class ConcretePrototype implements Cloneable { private int age; private String name; private List<String> hobbies; @Override public ConcretePrototype clone() { try { return (ConcretePrototype)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + ", hobbies=" + hobbies + '}'; } }
深克隆:
每个属性单独设置都互不影响
- 递归克隆
- 序列化克隆
- JSON流克隆
public class ConcretePrototype implements Cloneable,Serializable { private int age; private String name; private List<String> hobbies; private List<String> hobbies1; private List<String> hobbie2; @Override public ConcretePrototype clone() { try { return (ConcretePrototype)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } /** * 递归克隆 * @return */ public ConcretePrototype deepCloneHobbies(){ try { ConcretePrototype result = (ConcretePrototype)super.clone(); result.hobbies = (List)((ArrayList)result.hobbies).clone(); return result; } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } /** * 序列化的方式 * @return */ public ConcretePrototype deepClone(){ try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (ConcretePrototype)ois.readObject(); }catch (Exception e){ e.printStackTrace(); return null; } } /** * json流的方式 * @return */ public ConcretePrototype deepCloneJson(){ String json = JSON.toJSONString(this); return JSON.parseObject(json, ConcretePrototype.class); } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + ", hobbies=" + hobbies + '}'; } }
五、委派模式
定义:他的基本作用就是负责任务的调度和分配,将任务的分配和执行分离开来。可以看作是一种特殊情况下的静态代理全权代理。不属于GOF23种设计模式之一。属于行为型模式。
适用场景:
- 委派对象本身不知道如何处理一个任务的,把请求交给其他对象来处理。
- 实现程序的解耦。
public class Boss { public void command(String task,Leader leader){ leader.doing(task); } } public interface IEmployee { void doing(String task); } public class Leader implements IEmployee { private Map<String,IEmployee> employee = new HashMap<String,IEmployee>(); public Leader(){ employee.put("爬虫",new EmployeeA()); employee.put("海报图",new EmployeeB()); } public void doing(String task) { if(!employee.containsKey(task)){ System.out.println("这个任务" +task + "超出我的能力范围"); return; } employee.get(task).doing(task); } } public class EmployeeA implements IEmployee { protected String goodAt = "编程"; public void doing(String task) { System.out.println("我是员工A,我擅长" + goodAt + ",现在开始做" +task + "工作"); } } public class EmployeeB implements IEmployee { protected String goodAt = "平面设计"; public void doing(String task) { System.out.println("我是员工B,我擅长" + goodAt + ",现在开始做" +task + "工作"); } } public class Test { public static void main(String[] args) { new Boss().command("海报图",new Leader()); new Boss().command("爬虫",new Leader()); new Boss().command("卖手机",new Leader()); } }
委派模式在源码中的应用:
- DispatherServlet
- ClassLoader
- Method invoke()
- BeanDefinitionParseDelegate
委派模式的优缺点:
优点:
- 通过任务委派能够将一个大型的任务细化,通过统一管理这些子类的完成情况实现任务的跟进,能够加快任务执行的效率。
缺点:
- 任务委派方式需要根据任务的复杂程度进行不同的改变,在任务比较复杂的情况下,可能需要进行多重委派,容易造成紊乱。
委派模式与代理模式的区别
- 委派模式是行为模式,代理模式是结构型模式
- 委派模式注重的是任务派遣,注重结果;代理模式注重的是代码增强,注重过程;
- 委派模式是一种特殊的静态代理,相当于全权代理。
六、策略模式
定义:它是将定义的算法家族分别封装起来,让它们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户。可以避免多重分支的if else等语句。属于行为型模式。
// 工具类 public class MsgResult { private int code; private Object data; private String msg; public MsgResult(int code, String msg, Object data) { this.code = code; this.data = data; this.msg = msg; } @Override public String toString() { return "MsgResult{" + "code=" + code + ", data=" + data + ", msg='" + msg + '\'' + '}'; } } public abstract class Payment { // 支付接口 public abstract String getName(); //通用逻辑放到抽象类里面实现 public final MsgResult pay(String uid, double amount){ //余额是否足够 if(queryBalance(uid) < amount){ return new MsgResult(500,"支付失败","余额不足"); } return new MsgResult(200,"支付成功","支付金额" + amount); } protected abstract double queryBalance(String uid); } public class AliPay extends Payment { // 支付宝支付 public String getName() { return "支付宝"; } protected double queryBalance(String uid) { return 900; } } public class JDPay extends Payment { // 京东白条 public String getName() { return "京东白条"; } protected double queryBalance(String uid) { return 500; } } public class UnionPay extends Payment { // 银联 public String getName() { return "银联支付"; } protected double queryBalance(String uid) { return 120; } } public class WechatPay extends Payment { //微信 public String getName() { return "微信支付"; } protected double queryBalance(String uid) { return 263; } } // 支付策略 public class PayStrategy { public static final String ALI_PAY = "AliPay"; public static final String JD_PAY = "JdPay"; public static final String WECHAT_PAY = "WechatPay"; public static final String UNION_PAY = "UnionPay"; public static final String DEFAULT_PAY = ALI_PAY; private static Map<String,Payment> strategy = new HashMap<String,Payment>(); static { strategy.put(ALI_PAY,new AliPay()); strategy.put(JD_PAY,new JDPay()); strategy.put(WECHAT_PAY,new WechatPay()); strategy.put(UNION_PAY,new UnionPay()); } public static Payment get(String payKey){ if(!strategy.containsKey(payKey)){ return strategy.get(DEFAULT_PAY); } return strategy.get(payKey); } } // 订单 通过传入的支付方式获取不同的支付策略,进行支付 public class Order { private String uid; private String orderId; private double amount; public Order(String uid, String orderId, double amount) { this.uid = uid; this.orderId = orderId; this.amount = amount; } public MsgResult pay(){ return pay(PayStrategy.DEFAULT_PAY); } public MsgResult pay(String payKey){ Payment payment = PayStrategy.get(payKey); System.out.println("欢迎使用" + payment.getName()); System.out.println("本次交易金额为" + amount + ",开始扣款"); return payment.pay(uid,amount); } } public class Test { public static void main(String[] args) { Order order = new Order("1","2020031401000323",324.5); System.out.println(order.pay(PayStrategy.UNION_PAY)); } }
策略模式在源码中的应用:
- DispatherServlet结合委派模式
- Comparator
- Resource
- InstantiationStrategy: CglibSubclassingInstantiationStrategy、SimpleInstantiationStrategy
策略模式的使用场景:
- 假如系统中有很多类,而它们的区别仅仅在于他们的行为不同。
- 一个系统需要动态地在几种算法中选择一种。
- 需要屏蔽算法规则。
策略模式的优缺点:
优点:
- 策略模式符合开闭原则。
- 避免使用多重条件转移语句,如if...else、swith
- 适用策略模式可以提高算法的保密性和安全性。
缺点:
- 客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
- 代码中会产生非常多策略类,增加维护难度。
七、装饰器模式(包装模式)
定义:是指在不改变原有对象的基础上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能)。属于结构型模式
适用场景:
- 用于扩展一个类的功能或给一个类添加附加职责
- 动态给一个对象添加功能,这些功能可以动态的撤销
public abstract class Component { // 基类 /** * 实例方法 */ public abstract void operation(); } public class ConcreteComponent extends Component { // 提供处理的基本方法 @Override public void operation() { // 相应的功能处理 System.out.println("处理业务逻辑"); } } /** * <h3>Describe: 装饰器<br><h3> * <p>继承组件,重写operation方法</p> * <p>将组件传入,在执行组件方法前后可以执行一些其他事情</p> * * @author Elian * @since 2022/3/19 16:44 */ public abstract class Decorator extends Component { // 包装器基类 /** * 持有组件对象 */ protected Component component; /** * 构造方法,传入组件对象 * @param component 组件对象 */ public Decorator(Component component) { this.component = component; } @Override public void operation() { // 转发请求给组件对象,可以在转发前后执行一些附加动作 component.operation(); } } public class ConcreteDecoratorA extends Decorator { // 包装器A /** * 构造方法,传入组件对象 * * @param component 组件对象 */ public ConcreteDecoratorA(Component component) { // 包装器B super(component); } // 调用父类operation()方法之前需要执行的操作 private void operationFirst() { System.out.println("A装饰器pre处理"); } // 调用父类operation()方法之后需要执行的操作 private void operationLast() { System.out.println("A装饰器after处理"); } @Override public void operation() { operationFirst(); super.operation(); // 选择行调用父类方法,如不调用相当于 operationLast(); } } public class ConcreteDecoratorB extends Decorator { /** * 构造方法,传入组件对象 * * @param component 组件对象 */ public ConcreteDecoratorB(Component component) { super(component); } // 调用父类operation()方法之前需要执行的操作 private void operationFirst() { System.out.println("B装饰器pre处理"); } // 调用父类operation()方法之后需要执行的操作 private void operationLast() { System.out.println("B装饰器after处理"); } @Override public void operation() { operationFirst(); super.operation(); // 选择行调用父类方法,如不调用相当于 operationLast(); } } public class Client { public static void main(String[] args) { Component component = new ConcreteComponent(); Decorator decoratorA = new ConcreteDecoratorA(component); decoratorA.operation(); Decorator decoratorB = new ConcreteDecoratorB(component); decoratorB.operation(); Decorator decoratorBandA = new ConcreteDecoratorB(new ConcreteDecoratorA(new ConcreteComponent())); decoratorBandA.operation(); } }
在jdk中的应用:BufferedInputStream等
装饰器模式和代理模式对比
- 装饰器模式就是一种特殊的代理模式。
- 装饰器模式强调自身的功能扩展,而且扩展是透明的、可动态定制的。
- 代理模式强调代理过程的控制。
装饰器模式的优缺点
优点:
- 装饰器是继承的有力补充,比继承灵活,不改变原有对象的情况下动态的给一个对象扩展功能,即插即用。
- 通过使用不同装饰类以及这些装饰类的排列组合,可实现不同效果。
- 装饰器完全遵守开闭原则。
缺点:
- 会出现更多的代码,更多的类,增加程序复杂性。
- 动态装饰时,多层装饰会更复杂。
八、模板方法模式
定义:模板方法模式通常又叫模板模式,是指定义一个算法的骨架,并允许子类为其中的一个或者多个步骤提供实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。属于行为型设计模式。
适用场景:
- 一次性实现一个算法不变的部分,并将可变的行为留给子类来实现。
- 各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。
public abstract class AbstractCourse { public final void createCourse() { // 1. 发布预习资料 postPreResources(); // 2. 制作课件 createPPT(); // 3. 直播授课 liveVideo(); // 4. 上传课后资料 postResource(); // 5. 布置作业 postHomeWork(); // 动态执行是否检查作业 if (needCheckHomeWork()) { checkHomeWork(); } } protected abstract void checkHomeWork(); // 钩子方法 protected boolean needCheckHomeWork() { return false; } private void postHomeWork() { System.out.println("布置作业"); } private void postResource() { System.out.println("上传课后资料"); } private void liveVideo() { System.out.println("直播授课"); } private void createPPT() { System.out.println("制作课件"); } private void postPreResources() { System.out.println("发布预习资料"); } } public class JavaCourse extends AbstractCourse { @Override protected void checkHomeWork() { System.out.println("检查Java作业"); } @Override protected boolean needCheckHomeWork() { return true; } } public class PythonCourse extends AbstractCourse { @Override protected void checkHomeWork() { System.out.println("检查Python作业"); } } public class Test { public static void main(String[] args) { JavaCourse javaCourse = new JavaCourse(); javaCourse.createCourse(); PythonCourse pythonCourse = new PythonCourse(); pythonCourse.createCourse(); } }
模板模式在源码中的应用
- JdbcTemplate
- AbstractList get()钩子方法
- HttpServlet get()、post()钩子方法
模板模式的优缺点
优点:
- 利用模板方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性
- 将不同代码放在不同的子类中,通过对子类的扩展增加新的行为,提高代码的扩展性
- 把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。
缺点:
- 类数目的增加,每一个抽象类都需要一个子类来实现,这样导致类的个数增加。
- 类数量的增加,间接地增加了系统实现的复杂度。
- 继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍。
九、建造者模式
定义:将一个复杂对象的构建与他的表示分离,使同样的构建过程可以创建不同的表示。属于创建型模式。
特征:用户只需指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。
适用场景:
- 创建对象需要很多步骤,但是步骤的顺序不一定固定。
- 如果一个对象有非常复杂的内部结构(很多属性)。
- 把复杂对象的创建和适用分离。
面试题
什么是依赖倒置原则?
高层次的模块不依赖于低层次的模块,二者都应该依赖其抽象
简单的说就是面向抽象编程
双重检查锁单例模式为什么需要双重检查
第一个检查:检查是否要阻塞,如果已经创建过,就不需要再进入加锁代码块拉低性能,大大的提升性能,如果没有
该检查,每次都会去竞争锁
第二个检查: 检查是否要重新创建实例。如果没有该判断,2个线程在第一个if都判断为null,那么就会创建2个实例
什么是简单工厂模式、工厂方法模式、抽象工厂模式?
- 简单工厂模式
主要有3个重要的角色:工厂(CourseFactory)、抽象类产品(ICourse)、具体产品(JavaCourse)
在客户端根据不同的参数来生成不同的具体产品,比如,客户端我传1,就生成java课程,传0就生成Python课程,
但是主要的逻辑全部在主工厂类,后续加其他课程都需要修改工厂类,不便于维护- 工厂方法模式
主要有4个重要的角色:父工厂(ICourseFactory)、具体工厂(JavaCourseFactory)、抽象类产品
(ICourse)、具体产品(JavaCourse)
工厂方法与简单工厂的区别在于把创建产品的过程下移,创建产品不再由父工厂负责,而是由下面的具体工厂负
责,客户端调用的时候,不再由参数决定,而是你需要什么产品,就创建什么样的工厂,比如,你需要创建
Python课程,客户端为 ICourseFactory factory = new PythonCourseFactory();- 抽象工厂模式
主要有4个重要的角色:抽象工厂(ICourseFactory)、具体工厂(JavaCourseFactory)、抽象类产品
(ICourse)、具体产品(JavaCourse)- 抽象工厂跟工厂方法的最大区别在于,我这个工厂不再是单一的创建课程,可能还会去做笔记,后续每个具体的工
厂也会去做笔记!!不同的工厂课程做不同的笔记
什么是单例模式,并说出2到3中不同的实现方式以及他们的优缺点?
单例模式,指在任何情况下,一个类绝对只有一个实例,并提供一个全局访问点,单例模式属于创建型模式!!
实现方式:
饿汉式单例:在类加载的时候就初始化,绝对的线程安全,但是不确定类是否需要使用,会造成内存空间浪费
懒汉式单例:在使用的时候才会初始化,所以会带来线程安全问题,用synchronized或者双重检查锁解决性能问题
枚举单例:解决了反射与序列化破坏单例,反射newInstance有枚举类型判断,枚举无法通过反射创建实例!枚举
的序列化则是通过类名和类对象类找到一个唯一的枚举对象,所以,也不可能被类加载加载多次!!
有哪些方式可以破坏单例模式,怎么解决单例破坏?
反射、序列化
使用枚举单例模式,不会有反射跟序列化破坏
枚举的实例的底层编译实现是一个final static class,这样的实现类似于单例中静态常量模式的实现(饿汉式),无法
实现lazy-loading但保证了单例。类在被加载时是线程安全的,所以解决了线程问题。各种序列化方法,如
writeObject、readObject、readObjectNoData、writeReplace和readResolve等会造成单例破坏的方法都是被禁
用的,所以在JVM中,枚举类和枚举量都是唯一的,这就实现了自由序列化
反射:无参构造函数添加非空判断
序列化:增加readResolve()方法
原型模式中的clone()
三大设计模式类型
设计模式的的3大类型其实是一个递进的过程,主要是基于它这个设计模式的目的
首先,你要实现功能,肯定要创建对象是不是,所以像单例、工厂、建造者、原型都属于怎么去很好的创建对象
创建完对象后是不是就应该考虑对象之间怎么合理的存在,如何更好的继承、依赖、组合,那么就衍生了很多结构
型的模式、比如门面、适配器、代理、装饰、组合、享元
前面2步做完,就是到了具体的实现了。怎么更好的达到目的,那么为了行为更清晰,效率更高就是行为型模式,
比如我们的委派,后面的策略,责任链、迭代器等等
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)