设计模式

软件设计原则&设计模式

设计原则和设计模式的初衷都是为了更好的维护代码,可重用,可扩展,好维护,更稳定,提高可读性,降低变更引起的风险,代码解耦。

七大原则

  • 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

image-20220316011309595

Thread-1

image-20220316011322431

上图为容器式并发导致实际上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动态代理特点
  1. final修饰类、方法
  2. 目标一定实现接口
  3. implement InvocationHandler,newProxyInstance(1.同一个类加载器,2. 目标类的接口,3. 实现了InvocationHandler的类)
  4. jdk只生成一个代理类
  5. 通过反射调用的
cglib动态代理
  1. 生成的代理类会继承目标类,目标类不需要实现接口
  2. 通过传入的o,找到目标方法的索引,通过index索引找到目标类,不需要反射
  3. implements MethodInterceptor,enhancer设置目标类的类型、实现了MethodInterceptor的类,enhancer.create()
  4. 不能代理final修饰的方法
  5. 生成三个代理类

生成代理类时jdk更快,cglib慢,文件少

调用时jdk用的反射,cglib用的fastclass机制通过index去定位方法,速度更快。

我们的代理类还能再被代理吗?
  1. jdk代理后再次被jdk代理

    同一个handler不可以。invoke调用invoke,死循环。不同的handler可以再次被代理(mybatis中的多重插件就是这么实现的)。

  2. jdk代理后再次被cglib代理

    不能,jdk生成的代理类是final修饰的,cglib生成代理类继承目标类,不能被代理。

  3. cglib代理后的类再次被jdk代理

    不能,实现的Factory中,没有被代理的方法了。

  4. cglib代理后的类再次被cglib代理

    不能,重复的newInstance()。

推到mybatis,事务失效的原理就是代理失效

什么样的类不能被代理?
  1. 对于jdk,必须有接口实现
  2. final修饰的方法不能被cglib代理
  3. 方法不是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 +
'}';
}
}

深克隆:

每个属性单独设置都互不影响

  1. 递归克隆
  2. 序列化克隆
  3. 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种设计模式之一。属于行为型模式。

适用场景:

  1. 委派对象本身不知道如何处理一个任务的,把请求交给其他对象来处理。
  2. 实现程序的解耦。
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());
}
}

委派模式在源码中的应用:

  1. DispatherServlet
  2. ClassLoader
  3. Method invoke()
  4. BeanDefinitionParseDelegate

委派模式的优缺点:

优点:

  1. 通过任务委派能够将一个大型的任务细化,通过统一管理这些子类的完成情况实现任务的跟进,能够加快任务执行的效率。

缺点:

  1. 任务委派方式需要根据任务的复杂程度进行不同的改变,在任务比较复杂的情况下,可能需要进行多重委派,容易造成紊乱。

委派模式与代理模式的区别

  1. 委派模式是行为模式,代理模式是结构型模式
  2. 委派模式注重的是任务派遣,注重结果;代理模式注重的是代码增强,注重过程;
  3. 委派模式是一种特殊的静态代理,相当于全权代理。

六、策略模式

定义:它是将定义的算法家族分别封装起来,让它们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户。可以避免多重分支的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));
}
}

策略模式在源码中的应用:

  1. DispatherServlet结合委派模式
  2. Comparator
  3. Resource
  4. InstantiationStrategy: CglibSubclassingInstantiationStrategy、SimpleInstantiationStrategy

策略模式的使用场景:

  1. 假如系统中有很多类,而它们的区别仅仅在于他们的行为不同。
  2. 一个系统需要动态地在几种算法中选择一种。
  3. 需要屏蔽算法规则。

策略模式的优缺点:

优点:

  1. 策略模式符合开闭原则。
  2. 避免使用多重条件转移语句,如if...else、swith
  3. 适用策略模式可以提高算法的保密性和安全性。

缺点:

  1. 客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
  2. 代码中会产生非常多策略类,增加维护难度。

七、装饰器模式(包装模式)

定义:是指在不改变原有对象的基础上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能)。属于结构型模式

适用场景:

  1. 用于扩展一个类的功能或给一个类添加附加职责
  2. 动态给一个对象添加功能,这些功能可以动态的撤销

image-20220319171243775

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等

装饰器模式和代理模式对比

  1. 装饰器模式就是一种特殊的代理模式。
  2. 装饰器模式强调自身的功能扩展,而且扩展是透明的、可动态定制的。
  3. 代理模式强调代理过程的控制。

装饰器模式的优缺点

优点:

  1. 装饰器是继承的有力补充,比继承灵活,不改变原有对象的情况下动态的给一个对象扩展功能,即插即用。
  2. 通过使用不同装饰类以及这些装饰类的排列组合,可实现不同效果。
  3. 装饰器完全遵守开闭原则。

缺点:

  1. 会出现更多的代码,更多的类,增加程序复杂性。
  2. 动态装饰时,多层装饰会更复杂。

八、模板方法模式

定义:模板方法模式通常又叫模板模式,是指定义一个算法的骨架,并允许子类为其中的一个或者多个步骤提供实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。属于行为型设计模式。

适用场景:

  1. 一次性实现一个算法不变的部分,并将可变的行为留给子类来实现。
  2. 各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。
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();
}
}

模板模式在源码中的应用

  1. JdbcTemplate
  2. AbstractList get()钩子方法
  3. HttpServlet get()、post()钩子方法

模板模式的优缺点

优点:

  1. 利用模板方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性
  2. 将不同代码放在不同的子类中,通过对子类的扩展增加新的行为,提高代码的扩展性
  3. 把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。

缺点:

  1. 类数目的增加,每一个抽象类都需要一个子类来实现,这样导致类的个数增加。
  2. 类数量的增加,间接地增加了系统实现的复杂度。
  3. 继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍。

九、建造者模式

定义:将一个复杂对象的构建与他的表示分离,使同样的构建过程可以创建不同的表示。属于创建型模式。

特征:用户只需指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。

适用场景:

  1. 创建对象需要很多步骤,但是步骤的顺序不一定固定。
  2. 如果一个对象有非常复杂的内部结构(很多属性)。
  3. 把复杂对象的创建和适用分离。

面试题

什么是依赖倒置原则?

高层次的模块不依赖于低层次的模块,二者都应该依赖其抽象
简单的说就是面向抽象编程

双重检查锁单例模式为什么需要双重检查

第一个检查:检查是否要阻塞,如果已经创建过,就不需要再进入加锁代码块拉低性能,大大的提升性能,如果没有
该检查,每次都会去竞争锁
第二个检查: 检查是否要重新创建实例。如果没有该判断,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步做完,就是到了具体的实现了。怎么更好的达到目的,那么为了行为更清晰,效率更高就是行为型模式,
比如我们的委派,后面的策略,责任链、迭代器等等

posted @   coderElian  阅读(71)  评论(0编辑  收藏  举报
编辑推荐:
· 从 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)
点击右上角即可分享
微信分享提示