常用设计模式

1.设计模式原则

设计原则解释
开闭原则对扩展开放,对修改关闭。
依赖倒置原则通过抽象使各个类或者模块不相互影响,实现松耦合。
单一职责原则一个类、接口、方法只做一件事。
接口隔离原则尽量保证接口的纯洁性,客户端不应该依赖不需要的接口。
迪米特法则又叫最少知道原则,一个类对其所依赖的类知道得越少越好。
里氏替换原则子类可以扩展父类的功能但不能改变父类原有的功能。
合成复用原则尽量使用对象组合、聚合,而不使用继承关系达到代码复用的目的。

2.简单工厂模式

【定义】
简单工厂模式(Simple Factory Pattern)是指由一个工厂对象决定创建出哪一种产品类 的实例,但它不属于 GOF,23 种设计模式
【应用场景】
简单工厂适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创 建对象的逻辑不需要关心。

【总结代码实现】

1、产品接口、产品类:实现自己的业务逻辑,如 JavaCourse的record()

2、工厂类:判断String:className非空,className:包名全路径。直接反射,初始化对象

2.1-v1

public class CourseFactory {
    public ICourse create(String className) {
        try {
            if (!((null == className) || "".equals(className))) {
                return (ICourse) Class.forName(className).newInstance();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

image

2.2-v2

【总结代码实现】

1、产品接口、产品类:实现自己的业务逻辑,如 JavaCourse的record()

2、工厂类:判断Class类对象:clazz非空。直接Class类对象,初始化对象。

public ICourse create(Class<? extends ICourse> clazz){
        try {
            if (null != clazz) {
                return clazz.newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
}
		 		![image-20191026151350946](https://img-blog.csdnimg.cn/img_convert/b6a0e8d9f2d6c67fd32767bee6b1de91.png)

3.工厂方法模式

【定义
工厂方法模式(Fatory Method Pattern)是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行.
【应用场景】
优点:
1、创建对象需要大量重复的代码。
2、客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。
3、一个类通过其子类来指定创建哪个对象。
缺点:
1、类的个数容易过多,增加复杂度。
2、增加了系统的抽象性和理解难度。
【区别】
简单工厂和工厂方法的区别
工厂方法中用户只需要关心所需产品对应的工厂,无须关心创建细节,而且加入新的产品符 合开闭原则。 工厂方法模式主要解决产品扩展的问题。

简单工厂中,随着产品链的丰富,如果每个 课程的创建逻辑有区别的话,工厂的职责会变得越来越多,有点像万能工厂,并不便于维护。

根据单一职责原则我们将职能继续拆分,专人干专事。Java 课程由 Java 工厂创建, Python 课程由 Python 工厂创建,对工厂本身也做一个抽象。

public class JavaCourseFactory implements ICourseFactory {
        public ICourse create() {
            return new JavaCourse();
        }
}

public class PythonCourseFactory implements ICourseFactory {
        public ICourse create() {
            return new PythonCourse();
        }
}

【总结代码实现】

1、产品接口、产品类:实现自己的业务逻辑,如 JavaCourse的record()

2、工厂接口、工厂类:直接new产品对象。

image-20191026152246745

4.抽象工厂模式

【定义】
抽象工厂模式(Abastract Factory Pattern)是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们具体的类。

【总结代码实现】

1、产品接口、产品类:实现自己的业务逻辑,如 JavaNote的note(),JavaVideo的video()

2、工厂接口、工厂类:不同的工厂实现工厂接口,如JavaFactory、PythonFactory

跟工厂方法没啥区别,只不过抽象工厂分的更细。

【缺点】
如果再加一个产品源码 Source 也加入到课程中,那么我们的代码从抽象工厂,到具体工厂要全部调整,很显然不符合开闭原则。
因此抽象工厂也是有缺点的:
1、规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。
2、增加了系统的抽象性和理解难度。
但在实际应用中,我们千万不能犯强迫症甚至有洁癖。在实际需求中产品等级结构升级
是非常正常的一件事情。我们可以根据实际情况,只要不是频繁升级,可以不遵循开闭 原则。代码每半年升级一次或者每年升级一次又有何不可呢?

image-20191026153359807

比如有两个产品族:Java产品族、Python产品族,每个产品族有两个产品:video、note
1、创建两个产品
IVideo 接口:
public interface IVideo { void record();}
INote 接口:
public interface INote { void edit();}                      
2、创建抽象工厂
public interface CourseFactory { 
  INote createNote();
	IVideo createVideo();
}
3、创建Java产品族、Python产品族
Java
    public class JavaVideo implements IVideo {
        public void record() {
            System.out.println("录制 Java 视频");
        }
    }

    public class JavaNote implements INote {
        public void edit() {
            System.out.println("编写 Java 笔记");
        }
    }
Python
    public class PythonVideo implements IVideo {
        public void record() {
            System.out.println("录制 Python 视频");
        }
    }

    public class PythonNote implements INote {
        public void edit() {
            System.out.println("编写 Python 笔记");
        }
    }
4、创建JavaPython产品族的具体工厂
Java
public class JavaCourseFactory implements CourseFactory {
        public INote createNote() {
            return new JavaNote();
        }

        public IVideo createVideo() {
            return new JavaVideo();
        }
}
Python
public class PythonCourseFactory implements CourseFactory {
        public INote createNote() {
            return new PythonNote();
        }

        public IVideo createVideo() {
            return new PythonVideo();
        }
}

5.单例模式

【统一的逻辑】

1、私有化构造器

2、提供私有静态的变量 / 静态内部类(提供字段)

3、提供公开静态的方法 getInstance(只是这里的实现不同)

【注册式单例】

1、定义枚举 INSTANCE

2、定义Object、get/set

3、getInstance()

5.1 饿汉式单例

【定义】

饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题 。

【优点】

没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。

【缺点】

类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅 坑不拉屎 。

public class HungrySingleton { 
    //先静态、后动态 //先属性、后方法 // 先上后下
    private static final HungrySingleton hungrySingleton = new HungrySingleton();
    private HungrySingleton() {}
    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }
}

5.2 懒汉式单例

5.2.1-v1 线程不安全
public class LazySimpleSingleton {
    private LazySimpleSingleton() {} //静态块,公共内存区域
    private static LazySimpleSingleton lazy = null;
    public static LazySimpleSingleton getInstance() {
        if (lazy == null) {
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}
5.2.2-v2 线程安全-synchronized-方法
public class LazySimpleSingleton {
    private LazySimpleSingleton() {}
    private static LazySimpleSingleton lazy = null;     //静态块,公共内存区域
    public synchronized static LazySimpleSingleton getInstance() {
        if (lazy == null) {
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

解决了线程安全的问题,但是出现了性能问题
用 synchronized 加锁,在线程数量比较多情况下,如果 CPU 分配压力上升,会导致大批 量线程出现阻塞,从而导致程序运行性能大幅下降。

5.2.3-v3 线程安全-synchronized-代码块-双重检查锁
public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton lazy = null;
    private LazyDoubleCheckSingleton() {}
    public static LazyDoubleCheckSingleton getInstance() {
        if (lazy == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (lazy == null) {
                  // 1.分配内存给这个对象 // 2.初始化对象 // 3.设置 lazy 指向刚分配的内存地址
                    lazy = new LazyDoubleCheckSingleton(); 
                }
            }
        }
        return lazy;
    }
}

当第一个线程调用 getInstance()方法时,第二个线程也可以调用 getInstance()。当第一 个线程执行到 synchronized 时会上锁,第二个线程就会变成 MONITOR 状态,出现阻 塞。**此时,阻塞并不是基于整个 LazySimpleSingleton 类的阻塞,而是在 getInstance() 方法内部阻塞,只要逻辑不是太复杂,对于调用者而言感知不到。**但是,用到 synchronized 关键字,总归是要上锁,对程序性能还是存在一定影响的。

5.2.4-v4 线程安全-静态内部类
public class LazyInnerClassSingleton {
    private LazyInnerClassSingleton(){}
    public static final LazyInnerClassSingleton getInstance() {
				//在返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
    }
    // 默认不加载
    private static class LazyHolder {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

这种形式,即兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题,完美地屏蔽了这两个缺点。

内部类一定是要在方 法调用之前初始化,巧妙地避免了线程安全问题。

1.默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类
2.如果没使用的话,内部类是不加载的
3.每一个关键字都不是多余的
4.static 是为了使单例的空间共享
5.保证这个方法不会被重写,重载

5.2.5 反射破坏单例
public class LazyInnerClassSingletonTest {
    public static void main(String[] args) {
        try {
            Class<?> clazz = LazyInnerClassSingleton.class;
            Constructor c = clazz.getDeclaredConstructor(null);// 通过反射拿到私有的构造方法
            c.setAccessible(true);// 强制访问,强吻,不愿意也要吻
            Object o1 = c.newInstance(); // 暴力初始化,通过私有构造方法
            Object o2 = c.newInstance(); // 调用了两次私有构造方法,相当于new了两次,犯了原则性问题
            System.out.println(o1 == o2); // false
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

优化:在私有构造器加入以下代码

private LazyInnerClassSingleton() {
  if (LazyHolder.LAZY != null) {
  	throw new RuntimeException("不允许创建多个实例");
  }
}
5.2.6 序列化破坏单例

当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时 再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存, 即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当 于破坏了单例。

public class SeriableSingleton implements Serializable {
    public final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton() {}
    public static SeriableSingleton getInstance() {
        return INSTANCE;
    }
}
public class SeriableSingletonTest {
    public static void main(String[] args) {
        SeriableSingleton s1 = null;
        SeriableSingleton s2 = SeriableSingleton.getInstance();
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("SeriableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();
            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SeriableSingleton) ois.readObject();
            ois.close();
            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2); // false
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

优化:添加readResolve()方法即可

public class SeriableSingleton {
    public final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton() {}
    public static SeriableSingleton getInstance() {
        return INSTANCE;
    }
    private Object readResolve() {
        return INSTANCE;
    }
}

通过 JDK 源码分析我们可以看出,虽然,增加 readResolve()方法返回实例,解决了单 例被破坏的问题。但是,我们通过分析源码以及调试,我们可以看到实际上实例化了两 次,只不过新创建的对象没有被返回而已。那如果,创建对象的动作发生频率增大,就 意味着内存分配开销也就随之增大,难道真的就没办法从根本上解决问题吗?下面我们 来注册式单例也许能帮助到你。

5.2.7 注册式单例-枚举登记

【定义】

注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例。注册式单例有两种写法:一种为容器缓存,一种为枚举登记。

public enum EnumSingleton {
    INSTANCE;
    private Object data;
    public Object getData() {return data;}
    public void setData(Object data) { this.data = data;}
    public static EnumSingleton getInstance() {
        return INSTANCE;
    }
}
或者
public enum EnumSingleton {
    INSTANCE;
    private Object instance;
    // 枚举的特性,在JVM中只会被实例化一次
    EnumSingleton() {
        instance = new EnumResource();
    }
    public Object getInstance() {
        return instance;
    }
}
使用
EnumSingleton instance = EnumSingleton.INSTANCE;
System.out.println(instance.getInstance());

Java反编译工具:https://varaneckas.com/jad/

以下是反编译代码,原来,枚举式单例在静态代码块中就给 INSTANCE 进行了赋值,是饿汉式单例的实现。

static {
	INSTANCE = new EnumSingleton("INSTANCE", 0); 
	$VALUES = (new EnumSingleton[] {
		INSTANCE
   });
}

序列化我们能否破坏枚举式单例呢?

public static void main(String[] args) {
            try {
                Class clazz = EnumSingleton.class;
                Constructor c = clazz.getDeclaredConstructor();
                c.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

报的是 java.lang.NoSuchMethodException 异常,意思是没找到无参的构造方法。这时候,我们打开 java.lang.Enum 的源码代码,查看它的构造方法,只有一个 protected 的构造方法,代码如下 :

protected Enum(String name, int ordinal) { 
	this.name = name;
	this.ordinal = ordinal;
}

那在通过这个两个参数的构造器进行反射获取

public static void main(String[] args) {
            try {
                Class clazz = EnumSingleton.class;
                Constructor c = clazz.getDeclaredConstructor(String.class, int.class);
                c.setAccessible(true);
                EnumSingleton enumSingleton = (EnumSingleton) c.newInstance("Tom", 666);
            } catch (Exception e) {
                e.printStackTrace();
            }
}

这时错误已经非常明显了,告诉我们 Cannot reflectively create enum objects,不能 用反射来创建枚举类型。

还是习惯性地想来看看 JDK 源码,进入 Constructor 的 newInstance()方法 :

public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor; // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

在 newInstance()方法中做了强制性的判断,如果修饰符是 Modifier.ENUM 枚举类型, 直接抛出异常。到这为止,我们是不是已经非常清晰明了呢?枚举式单例也是《Effective Java》书中推荐的一种单例实现写法。在 JDK 枚举的语法特殊性,以及反射也为枚举保驾护航,让枚举式单例成为一种比较优雅的实现 。

5.2.8 注册式单例-容器缓存
public class ContainerSingleton {
    private ContainerSingleton() {}
    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
    public static Object getBean(String className) {
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object obj = null;
                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            } else {
                return ioc.get(className);
            }
        }
    }
}

容器式写法适用于创建实例非常多的情况,便于管理。但是,是非线程安全的。

5.2.9 ThreadLocal线程单例
public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
            new ThreadLocal<ThreadLocalSingleton>() {
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };
    private ThreadLocalSingleton() {}
    public static ThreadLocalSingleton getInstance() {
        return threadLocalInstance.get();
    }
}

我们发现,在主线程 main 中无论调用多少次,获取到的实例都是同一个,都在两个子线 程中分别获取到了不同的实例。那么 ThreadLocal 是如果实现这样的效果的呢?我们知 道上面的单例模式为了达到线程安全的目的,给方法上锁,以时间换空间。ThreadLocal 将所有的对象全部放在 ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以 空间换时间来实现线程间隔离的。

6.原型模式

【定义】

原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这些 原型创建新的对象。

【使用场景】

1、类初始化消耗资源较多。
2、new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
3、构造函数比较复杂。
4、循环体中生产大量对象时。

浅克隆:克隆地址。意味着修改一个值,另一个对象的值也改变。

深克隆:克隆值。如果克隆的对象是单利对象,那就会破坏单利。

7.代理模式

【定义】

代理模式(Proxy Pattern)是指为其他对象提供一种代理, 以控制对这个对象的访问。

【作用】

代理对象在客户端和目标对象之间起到中介作用。

【目的】

1、保护目标对象 2、增强目标对象

【总结代码实现】

1、被代理的接口和实现类

2、代理类

7.1 静态代理

显示声明被代理对象,静态代理的体现:三层结构:controller、service、dao

代理类显示增强目标对象

7.2 JDK动态代理

【JDK Proxy生产代理步骤】

1、拿到被代理对象的引用,并且获取到它的所有的接口,反射获取。
2、JDK Proxy 类重新生成一个新的类、同时新的类要实现被代理类所有实现的所有的接口。
3、动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体 现)。
4、编译新生成的 Java 代码.class。
5、再重新加载到 JVM 中运行。

以上这个过程就叫字节码重组。JDK 中有一个规范,在 ClassPath 下只要是$开头的 class 文件一般都是自动生成的。

7.3 CGLib动态代理

不需要实现接口

Customer$$EnhancerByCGLIB$$3feeb52a$$FastClassByCGLIB$$6aad62f1.class 就是代理类的 FastClass,
Customer$$FastClassByCGLIB$$2669574a.class 就是被代理类的 FastClass。
CGLib 动态代理执行代理方法效率之所以比 JDK 的高是因为 Cglib 采用了 FastClass 机制。
它的原理简单来说就是:为代理类和被代理类各生成一个 Class,这个 Class 会为代 理类或被代理类的方法分配一个 index(int 类型)。这个 index 当做一个入参,FastClass 就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比 JDK动态代理通过反射调用高。

7.4 区别-优缺点

静态代理和动态代理区别

1、静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步 新增,违背开闭原则。
2、动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
3、若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成, 无需修改代理类的代码。

CGLib和JDK区别

1.JDK 动态代理是实现了被代理对象的接口,CGLib 是继承了被代理对象。
2.JDK 和 CGLib 都是在运行期生成字节码,JDK 是直接写 Class 字节码,CGLib 使用 ASM 框架写 Class 字节码,Cglib 代理实现更复杂,生成代理类比 JDK 效率低。
3.JDK 调用代理方法,是通过反射机制调用,CGLib 是通过 FastClass 机制直接调用方法, CGLib 执行效率更高。

代理模式优缺点

优点:

1、代理模式能将代理对象与真实被调用的目标对象分离。
2、一定程度上降低了系统的耦合度,扩展性好。
3、可以起到保护目标对象的作用。
4、可以对目标对象的功能增强。

缺点:

1、代理模式会造成系统设计中类的数量增加。
2、在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
3、增加了系统的复杂度。

7.5 代理模式与Spring

ProxyFactoryBean 核心的方法就是 getObject()方法

public Object getObject() throws BeansException {
        initializeAdvisorChain();
        if (isSingleton()) {
            return getSingletonInstance();
        } else {
            if (this.targetName == null) {
                logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " + "Enable prototype proxies by setting the 'targetName' property.");
            }
            return newPrototypeInstance();
        }
    }

Spring 利用动态代理实现 AOP 有两个非常重要的类,一个是 JdkDynamicAopProxy 类 和 CglibAopProxy 类

【Spring中的代理选择原则】

1、当 Bean 有实现接口时,Spring 就会用 JDK 的动态代理
2、当 Bean 没有实现接口时,Spring 选择 CGLib。
3、Spring 可以通过配置强制使用 CGLib,只需在 Spring 的配置文件中加入如下代码
<aop:aspectj-autoproxy proxy-target-class=“true”/>

8. 委派模式

【定义】

委派模式不属于 GOF23 种设计模式中。
委派模式(Delegate Pattern)的基本作用就是 负责任务的调用和分配任务,跟代理模式很像,可以看做是一种特殊情况下的静态代理 的全权代理,但是代理模式注重过程,而委派模式注重结果。
委派模式在 Spring 中应用 非常多,大家常用的 DispatcherServlet 其实就是用到了委派模式。

【总结代码实现】

1、一个IEmployee接口,所有员工、领导、老板都实现该接口

2、Leader类拥有所有员工,Boss拥有该Leader

3、Main创建Boss传入命令

9. 策略模式

【定义】

策略模式(Strategy Pattern)是指定义了算法家族、分别封装起来,让它们之间可以互 相替换,此模式让算法的变化不会影响到使用算法的用户。

【应用场景】

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zofSzkYZ-1653900067128)(https://img-zz.zzshows.com/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F-%E6%94%AF%E4%BB%98%E6%96%B9%E5%BC%8F.png)]

【JDK中体现】

比较常用的比较器 Comparator 接口,我们看到的一个大家常用的 compare()方法

【策略模式优缺点】

优点:

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

缺点:

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

【总结代码实现】

1、定义一个策略接口或者抽象类

2、不同的实现类实现策略接口

3、上下文类拥有该接口,并有execute()

4、Main根据不同的参数,调用不同的策略(推荐变量存储策略,然后for)

【SpringMVC中的体现】

public class DispatcherServlet extends HttpServlet {
        private List<Handler> handlerMapping = new ArrayList<Handler>(); // 省略add,省略init
        private void doDispatch(HttpServletRequest request, HttpServletResponse response) {
            String uri = request.getRequestURI();
            Handler handle = null;
            for (Handler h : handlerMapping) {
                if (uri.equals(h.getUrl())) {
                    handle = h;
                    break;
                }
            }
        }
    }

10.模板模式

【定义】

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

【应用场景】

1、一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
2、各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。

【总结代码实现】

1、抽象父类:定义抽象类、final的模板方法、具体方法(可以有钩子方法)

2、具体子类:具体方法可以由子类实现

【优缺点】

优点:

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

缺点:

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

11.适配器模式

【定义】

适配器模式(Adapter Pattern)是指将一个类的接口转换成客户期望的另一个接口,使原本的接口不兼容的类可以一起工作,属于结构型设计模式。

【应用场景】

1、已经存在的类,它的方法和需求不匹配(方法结果相同或相似)的情况。
2、适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不 同厂家造成功能类似而接口不相同情况下的解决方案。有点亡羊补牢的感觉。

【总结代码实现】

1、旧类:SiginService ,以前通过账号密码登录

2、适配器接口:LoginAdapter

3、具体适配器:LoginForQQAdapter 、LoginForWechatAdapter 等

4、第三方登录兼容接口:IPassportForThird,loginForQQ()、loginForWechat()

5、第三方适配器实现第三方登录兼容接口:PassportForThirdAdapter,processLogin()

【优缺点】

优点:

1、能提高类的透明性和复用,现有的类复用但不需要改变。
2、目标类和适配器类解耦,提高程序的扩展性。
3、在很多业务场景中符合开闭原则。

缺点:

1、适配器编写过程需要全面考虑,可能会增加系统的复杂性。
2、增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。

12.装饰者模式

【定义】

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

【场景】

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

【装饰者|适配器=区别】

装饰者适配器
形式是一种非常特别的适配器模式没有层级关系,装饰器模式有层级关系
定义装饰者和被装饰者都实现同一个接口,主要目的是为了扩展之后依旧保留OOP关系适配器和被适配者没有必然的联系,通常是采用继承或代理的形式进行包装
关系满足is-a关系满足has-a关系
功能注重覆盖、扩展注重兼容、转换
设计前置考虑后置考虑

【总结代码实现】

1、建立抽象类

2、建立装饰者父类和Base类继承抽象类

3、建立装饰者子类继承父类

【优缺点】

优点:

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

缺点:

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

13. 观察者模式

【定义】

观察者模式(Observer Pattern)定义了对象之间的一对多依赖,让多个观察者对象同 时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通 知并更新,属于行为型模式。观察者模式有时也叫做发布订阅模式。

【Guava-API-实现】

public class GuavaEvent { 
  @Subscribe
	public void subscribe(String str){ 
	// 业务逻辑
	System.out.println("执行 subscribe 方法,传入的参数是:" + str); }
}

	public static void main(String[] args) {
		EventBus eventbus = new EventBus();
  	GuavaEvent guavaEvent = new GuavaEvent(); 
    eventbus.register(guavaEvent); 				
    eventbus.post("Tom");
	}

【总结代码实现】

1、定义主题/消息总线:JDK-Observable, Guava-EventBus,发布事件

2、定义观察者:JDK-Observer,自定义-GuavaEvent-@Subscribe

【优缺点】

优点:

1、观察者和被观察者之间建立了一个抽象的耦合。
2、观察者模式支持广播通信。

缺点:

1、观察者之间有过多的细节依赖、提高时间消耗及程序的复杂度。
2、使用要得当,要避免循环调用。

14. 设计模式对比

【分类】

【关系】

【Spring中常用的设计模式】

【Spring中编程思想总结】

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值