设计模式

软件设计原则&设计模式

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

七大原则

  • 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 @ 2022-03-20 22:07  coderElian  阅读(81)  评论(0)    收藏  举报