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](https://wx2.sinaimg.cn/mw690/007x3FEZly1g8ber62lzgj30ok0qe17l.jpg)
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产品对象。
4.抽象工厂模式
【定义】
抽象工厂模式(Abastract Factory Pattern)是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们具体的类。
【总结代码实现】
1、产品接口、产品类:实现自己的业务逻辑,如 JavaNote的note(),JavaVideo的video()
2、工厂接口、工厂类:不同的工厂实现工厂接口,如JavaFactory、PythonFactory
跟工厂方法没啥区别,只不过抽象工厂分的更细。
【缺点】
如果再加一个产品源码 Source 也加入到课程中,那么我们的代码从抽象工厂,到具体工厂要全部调整,很显然不符合开闭原则。
因此抽象工厂也是有缺点的:
1、规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。
2、增加了系统的抽象性和理解难度。
但在实际应用中,我们千万不能犯强迫症甚至有洁癖。在实际需求中产品等级结构升级
是非常正常的一件事情。我们可以根据实际情况,只要不是频繁升级,可以不遵循开闭 原则。代码每半年升级一次或者每年升级一次又有何不可呢?
比如有两个产品族: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、创建Java、Python产品族的具体工厂
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中编程思想总结】