狂神课程链接:【狂神说Java】通俗易懂的23种设计模式教学(停更)_哔哩哔哩_bilibili
尚硅谷课程链接:尚硅谷Java设计模式(图解+框架源码剖析)_哔哩哔哩_bilibili
参考博客地址:
设计模式面试题(总结最全面的面试题!!!)_小杰爱吃蛋的博客-CSDN博客_设计模式面试题
(6条消息) (狂神)初识设计模式笔记_愿你拥有大风与烈酒,也能享受孤独与自由-CSDN博客
设计模式概述
设计模式:是一套用来提高代码可复用性,可维护性、可读性、稳健型以及安全性的解决方案
设计模式的本质:是面向对象设计原则的实际运用,是对类的封装、继承、多态以及类的关联关系和组合关系的充分理解。
设计模式的的基本要素:模式名称、问题、解决方案、效果
分类
创建型模式:(描述怎样去创建一个对象,创建和使用分离)
- 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构型模式:(描述如何将类或对象安装某种类型组成更大的结构)
- 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为型模式:(描述类和对象如何可以相互协作)
- 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
OOP七大原则
开闭原则:对扩展开发,对修改关闭(当需求需要改变的时候,尽量去扩展)
里氏替换原则: 继承必须确保超类所拥有的性质在子类中仍然成立(尽量不重写父类的方法)
依赖倒置原则: 要面向接口编程,不要面向实现编程。(低级模块不依赖高级模块,而是共同依赖抽象模块、抽象不依赖细节,细节依赖抽象)
单一职责原则: 控制类的粒度大小,将对象解耦,提高其内聚性(一个对象不应该担任太多的职责,原子性,单一的方法做单一的事情)
接口隔离原则: 要为各个类建立他们需要的专用接口
迪米特法则: 只与你的直接朋友交谈,不跟“陌生人”说话,降低代码之间的耦合度(比如A->B->C,而不能直接A->C,,controller,service,dao,不能是直接controller->dao,缺点是增加了一个中介)
合成复用原则: 尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现(如内部类组合外部类,属于has-a关系,但是子类继承则是靠继承关系来实现的属于is-a关系)
单例模式
核心作用:保证一个类只有一个实例,并且提供一个访问该实例的 全局访问点
饿汉式单例模式:
// 饿汉式单例 public class Hungry { // 可能会浪费空间 private byte[] data1 = new byte[1024*1024]; private byte[] data2 = new byte[1024*1024]; private byte[] data3 = new byte[1024*1024]; private byte[] data4 = new byte[1024*1024]; // 单例模式核心思想:构造器私有 private Hungry(){ } private final static Hungry HUNGRY = new Hungry(); public static Hungry getInstance(){ return HUNGRY; } }
DCL懒汉式单例模式:
// 懒汉式单例 public class LazyMan { private LazyMan() { System.out.println(Thread.currentThread().getName() + "ok"); } private volatile static LazyMan lazyMan; // volatile 为了避免指令重排 // 双重检测锁模式的懒汉式单例 DCL 懒汉式 public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan();// 不是一个原子性操作 /* 1、分配内存空间 2、执行构造方法,初始化对象 3、把这个对象指向这个空间 123 132 A B // 此时B线程进来会认为lazyman不为null // 直接返回 此时lazyman 还没有完成构造 // 为了避免指令重排 */ } } } return lazyMan; } // // public static LazyMan getInstance() { // if (lazyMan == null) { // lazyMan = new LazyMan(); // } // return lazyMan; // } // 单线程下确实单例ok,但是多线程并发 public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { LazyMan.getInstance(); }).start(); } } }
反射 可以破环这种单例
// 懒汉式单例 // 道高一尺,魔高一丈 public class LazyMan { private static boolean qinjiang = false; private LazyMan() { if(qinjiang == false){ qinjiang=true; }else{ throw new RuntimeException("不要试图使用反射破坏异常"); } // synchronized (LazyMan.class){ // if (lazyMan!=null){ // throw new RuntimeException("不要试图使用反射破坏异常"); // } // } // System.out.println(Thread.currentThread().getName() + "ok"); } private volatile static LazyMan lazyMan; // volatile 为了避免指令重排 // 双重检测锁模式的懒汉式单例 DCL 懒汉式 public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan();// 不是一个原子性操作 /* 1、分配内存空间 2、执行构造方法,初始化对象 3、把这个对象指向这个空间 123 132 A B // 此时B线程进来会认为lazyman不为null // 直接返回 此时lazyman 还没有完成构造 // 为了避免指令重排 */ } } } return lazyMan; } // 单线程下确实单例ok,但是多线程并发 public static void main(String[] args) throws Exception { // 反射 可以破环这种单例 // LazyMan instance = LazyMan.getInstance(); Field qinjiang = LazyMan.class.getDeclaredField("qinjiang"); qinjiang.setAccessible(true); Constructor<LazyMan> declaredConstructor =LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true);//无视私有构造器 LazyMan instance=declaredConstructor.newInstance(); qinjiang.set(instance,false); LazyMan instance2=declaredConstructor.newInstance(); System.out.println(instance); System.out.println(instance2); } }
静态内部类
// 静态内部类实现单例模式 不安全 public class Holder { private Holder(){ } public static Holder getInstance(){ return InnerClass.HOLDER; } public static class InnerClass{ private static final Holder HOLDER = new Holder(); } }
单例不安全,因为有反射
枚举
// enum 本身也是一个 class 类 public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingle instance1 = EnumSingle.INSTANCE; //Constructor<EnumSingle> declaredConstructor=EnumSingle.class.getDeclaredConstructor(null); // 枚举没有无参构造,只有有参构造 Constructor<EnumSingle> declaredConstructor=EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); // java.lang.NoSuchMethodException: com.kuang.single.EnumSingle.<init> 没有空参的构造方法 // java.lang.IllegalArgumentException: Cannot reflectively create enum objects 反射不能破坏枚举的单例 System.out.println(instance1); System.out.println(instance2); } }
枚举没有无参构造,只有有参构造
工厂设计模式
作用:
- 实现了创建者和调用者的分离
- 详细分类:
- 简单工厂模式:用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
- 工厂方法模式:用来生产同一等级结构中的固定产品(支持增加任意产品)
- 抽象工厂模式:围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
工厂设计模式的原则(OOP七大原则):
- 开闭原则:一个软件的实体应当对扩展开放,对修改关闭
- 依赖倒转原则:要针对接口编程,不要针对实现编程
- 迪米特法则:只与你直接的朋友通信,而避免和陌生人通信
核心本质:
- 实例化对象不使用new,用工厂方法代替 factory
- 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦
简单工厂模式(静态工厂模式)
用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
public interface Car { void name(); } public class WuLing implements Car{ @Override public void name() { System.out.println("五菱宏光"); } } public class Tesla implements Car{ @Override public void name() { System.out.println("特斯拉"); } } // 静态工厂模式 // 开闭原则 public class CarFactory { // 方法一: 不满足开闭原则 public static Car getCar(String car){ if(car.equals("wuling")){ return new WuLing(); }else if(car.equals("tesila")){ return new Tesla(); }else { return null; } } // 方法二: public static Car geyWuling(){ return new WuLing(); } public static Car geyTesla(){ return new Tesla(); } } public class Consumer { public static void main(String[] args) { // 接口,所有的实现类 // Car car = new WuLing(); // Car car1 = new Tesla(); // 2、使用工厂创建 Car car = CarFactory.getCar("wuling"); Car car1 = CarFactory.getCar("tesila"); car.name(); car1.name(); } }
弊端:
增加一个新的产品,做不到不修改代码。
工厂方法模式:
用来生产同一等级结构中的固定产品(支持增加任意产品)
public interface Car { void name(); } public class WuLing implements Car { @Override public void name() { System.out.println("五菱宏光"); } } public class Tesla implements Car { @Override public void name() { System.out.println("特斯拉"); } } // 工厂方法模式 public interface CarFactory { Car getCar(); } public class WulingFactory implements CarFactory{ @Override public Car getCar() { return new WuLing(); } } public class TeslaFactory implements CarFactory{ @Override public Car getCar() { return new Tesla(); } } public class Consumer { public static void main(String[] args) { Car car = new WulingFactory().getCar(); Car car1 = new TeslaFactory().getCar(); car.name(); car1.name(); Car car2 = new MoBaiFactory().getCar(); car2.name(); } }
对比简单工厂模式
1、结构复杂度:simple>method
2、代码复杂度:simple>method
3、编程复杂度:simple>method
4、管理上的复杂度:simple>method
根据设计原则,使用工厂方法模式;根据实际业务,使用简单工厂模式
抽象工厂模式:
围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂
定义:
抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定他们的具体的类(针对整个产品族,产品等级数量相对固定的产品族)
适用场景:
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
- 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
- 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体实现
// 手机产品接口 public interface IphoneProduct { void start(); void shutdown(); void callup(); void sendSMS(); } // 小米手机 public class XiaomiPhone implements IphoneProduct{ @Override public void start() { System.out.println("开启小米手机"); } @Override public void shutdown() { System.out.println("关闭小米手机"); } @Override public void callup() { System.out.println("小米手机打电话"); } @Override public void sendSMS() { System.out.println("小米手机发短信"); } } // 华为手机 public class HuaweiPhone implements IphoneProduct{ @Override public void start() { System.out.println("开启华为手机"); } @Override public void shutdown() { System.out.println("关闭华为手机"); } @Override public void callup() { System.out.println("华为手机打电话"); } @Override public void sendSMS() { System.out.println("华为手机发短信"); } } // 路由器产品接口 public interface IRouterProduct { void start(); void shutdown(); void openWifi(); void setting(); } // 小米路由器 public class XiaomiRouter implements IRouterProduct{ @Override public void start() { System.out.println("启动小米路由器"); } @Override public void shutdown() { System.out.println("关闭小米路由器"); } @Override public void openWifi() { System.out.println("打开小米Wi-Fi"); } @Override public void setting() { System.out.println("小米设置"); } } // 华为路由器 public class HuaweiRouter implements IRouterProduct{ @Override public void start() { System.out.println("启动华为路由器"); } @Override public void shutdown() { System.out.println("关闭华为路由器"); } @Override public void openWifi() { System.out.println("打开华为Wi-Fi"); } @Override public void setting() { System.out.println("华为设置"); } } // 抽象产品工厂 public interface IProductFactory { // 生产手机 IphoneProduct iphoneProduct(); // 生产路由器 IRouterProduct irouterProduct(); } public class XiaomiFactory implements IProductFactory{ @Override public IphoneProduct iphoneProduct() { return new XiaomiPhone(); } @Override public IRouterProduct irouterProduct() { return new XiaomiRouter(); } } public class HuaweiFactory implements IProductFactory{ @Override public IphoneProduct iphoneProduct() { return new HuaweiPhone(); } @Override public IRouterProduct irouterProduct() { return new HuaweiRouter(); } } public class Client { public static void main(String[] args) { System.out.println("小米系列产品--------------------"); // 小米工厂 XiaomiFactory xiaomiFactory = new XiaomiFactory(); IphoneProduct iphoneProduct = xiaomiFactory.iphoneProduct(); iphoneProduct.callup(); iphoneProduct.sendSMS(); IRouterProduct iRouterProduct = xiaomiFactory.irouterProduct(); iRouterProduct.openWifi(); System.out.println("华为系列产品--------------------"); // 小米工厂 HuaweiFactory huaweiFactory = new HuaweiFactory(); iphoneProduct = huaweiFactory.iphoneProduct(); iphoneProduct.callup(); iphoneProduct.sendSMS(); iRouterProduct = huaweiFactory.irouterProduct(); iRouterProduct.openWifi(); } }
多了一个抽象工厂,抽象工厂把每个工厂创建的产品已经声明,实现该工厂的工厂生产自己的产品,因此该模式适合创建一个产品簇。
优点
具体产品在应用层的代码隔离,无需关心创建的细节
将一个系列的产品统一到一起创建
缺点:
规定了所有可能被创建的产品集合,产品族中扩展新的产品困难;
增加了系统的抽象性和理解难度。
工厂模式小结:
简单工厂模式:虽然某种程度上不符合设计原则,但实际使用最多
工厂方法模式:不修改已有类的前提下,通过增加新的工厂类实现扩展
抽象工厂模式:不可以增加产品,可以增加产品族
应用场景:
- jdk中calendar的getInstance方法
- JDBC中的Connection对象的获取
- Spring中的IOC容器创建管理bean对象
- 反射中Class对象的newInstance方法
建造者模式
角色分析
// 抽象的建造者:只是定义一些方法和接口 public abstract class Builder { abstract void buildA(); // 地基 abstract void buildB(); // 钢筋工程 abstract void buildC(); // 铺电线 abstract void buildD(); // 粉刷 // 完工:得到产品 abstract Product getProduct(); } // 产品:房子 public class Product { private String buildA; private String buildB; private String buildC; private String buildD; public String getBuildA() { return buildA; } public void setBuildA(String buildA) { this.buildA = buildA; } public String getBuildB() { return buildB; } public void setBuildB(String buildB) { this.buildB = buildB; } public String getBuildC() { return buildC; } public void setBuildC(String buildC) { this.buildC = buildC; } public String getBuildD() { return buildD; } public void setBuildD(String buildD) { this.buildD = buildD; } @Override public String toString() { return "Product{" + "buildA='" + buildA + '\'' + ", buildB='" + buildB + '\'' + ", buildC='" + buildC + '\'' + ", buildD='" + buildD + '\'' + '}'; } } // 具体的建造者:工人 public class Worker extends Builder { private Product product; public Worker() { product = new Product();// 工人负责创建产品 } @Override void buildA() { product.setBuildA("地基"); System.out.println("地基"); } @Override void buildB() { product.setBuildB("钢筋工程"); System.out.println("钢筋工程"); } @Override void buildC() { product.setBuildC("铺电线"); System.out.println("铺电线"); } @Override void buildD() { product.setBuildD("粉刷"); System.out.println("粉刷"); } @Override Product getProduct() { return product; } } // 指挥:核心 负责指挥构建一个工程,工程如何构建,由他决定 public class Director { // 指挥工人按照顺序建房子 public Product build(Builder builder){ builder.buildA(); builder.buildB(); builder.buildC(); builder.buildD(); return builder.getProduct(); } } public class Test { public static void main(String[] args) { // 指挥 Director director = new Director(); // 指挥 具体的工人 完成产品 Product build = director.build(new Worker()); System.out.println(build.toString()); } }
上面示例是Builder模式的常规用法,Director在builder模式中具有重要的作用,他用于指导具体构建者如何构建产品,**控制调用先后次序,并向调用者返回完整的产品类,**但有些情况下需要简化系统结构,可以把Director和抽象建造者进行结合
通过静态内部类方式实现零件无序装配构造,这种方式使用更加灵活,更符合定义。
内部有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容,并且无需改变具体的构造方式。就可以生产出不同复杂产品
例如:麦当劳的套餐,服务员(具体建造者)可以随机搭配任意几种产品(零件)组成一款套餐(产品),然后出售给客户。比第一种方式少了指挥者,主要是因为第二种方式把指挥者交给用户来操作,使得产品创建更加简单灵活
// 抽象的建造者 public abstract class Builder { public abstract Builder builderA(String msg);// 汉堡 public abstract Builder builderB(String msg);// 可乐 public abstract Builder builderC(String msg);// 薯条 public abstract Builder builderD(String msg);// 甜点 abstract Product getProduct(); } // 产品 :套餐 public class Product { private String BuildA = "汉堡" ; private String BuildB = "可乐" ; private String BuildC = "薯条" ; private String BuildD = "甜点" ; public String getBuildA() { return BuildA; } public void setBuildA(String buildA) { BuildA = buildA; } public String getBuildB() { return BuildB; } public void setBuildB(String buildB) { BuildB = buildB; } public String getBuildC() { return BuildC; } public void setBuildC(String buildC) { BuildC = buildC; } public String getBuildD() { return BuildD; } public void setBuildD(String buildD) { BuildD = buildD; } @Override public String toString() { return "Product{" + "BuildA='" + BuildA + '\'' + ", BuildB='" + BuildB + '\'' + ", BuildC='" + BuildC + '\'' + ", BuildD='" + BuildD + '\'' + '}'; } } // 具体的建造者 public class Woker extends Builder{ private Product product; public Woker() { product = new Product(); } @Override public Builder builderA(String msg) { product.setBuildA(msg); return this; } @Override public Builder builderB(String msg) { product.setBuildB(msg); return this; } @Override public Builder builderC(String msg) { product.setBuildC(msg); return this; } @Override public Builder builderD(String msg) { product.setBuildD(msg); return this; } @Override Product getProduct() { return product; } } public class Test { public static void main(String[] args) { // 服务员 Woker woker = new Woker(); // 链式编程 :在原来的基础上,可以自由的自合,如果不组合也有固定的套餐 Product product = woker.builderA("全家桶").builderB("雪碧") .getProduct(); System.out.println(product.toString()); } }
优点:
- 产品的建造和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节
- 将复杂产品的创建步骤分解在不同的方法中,是得创建过程更加清晰
- 具体的建造者之间是相互独立的,这有利于系统的扩展。增加新的具体建造者无需修改原有类库代码,符合“开闭原则”
缺点:
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变的很庞大
建造者模式适合创建需要多个步骤组装的产品对象,而抽象工厂模式适合的是一系列相关的产品,这些产品构成一个产品族 。
原型模式
克隆
Prototype
Cloneable接口
clone()方法
浅克隆是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。
深克隆不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。
代码实现
Video
package com.kuang.prototype.demo01; import java.util.Date; /* 1..实现一个接口Cloneable 2.重写一个方法clone() */ //Video public class Video implements Cloneable { //无良up 主,克隆别人的视频! . private String name; private Date createTime; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public Video() { } public Video(String name, Date createTime) { this.name = name; this.createTime = createTime; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } @Override public String toString() { return "Video{" + "name='" + name + '\'' + ", createTime=" + createTime + '}'; } }
Bilibili
package com.kuang.prototype.demo01; import java.util.Date; /* 客户端:克隆 */ public class Bilibili { public static void main(String[] args) throws CloneNotSupportedException { //原型对象v1 Date date = new Date(); Video v1 = new Video("狂神说Java", date); //v1克隆v2 //Video v2 = new Video( "狂神说Java", date); Video v2 = (Video) v1.clone(); System.out.println("v1=>" + v1); System.out.println("v2=>" + v2); date.setTime(22131231); System.out.println("++++++++++++++++++++++++++++++++++++++"); System.out.println("v1=>" + v1); System.out.println("v2=>" + v2); //没有克隆引用类型,两个对象使用的还是同一个日期引用对象 } }
原理理解
代码再实现
Video
package com.kuang.prototype.demo02; import java.util.Date; /* 1..实现一个接口Cloneable 2.重写一个方法clone() */ //Video public class Video implements Cloneable { //无良up 主,克隆别人的视频! . private String name; private Date createTime; @Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone(); //实现深克隆~序列化, 反序列化 Video v = (Video) obj; v.createTime = (Date) this.createTime.clone();//将这个对象的属性也进行克隆~ return v; } public Video() { } public Video(String name, Date createTime) { this.name = name; this.createTime = createTime; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } @Override public String toString() { return "Video{" + "name='" + name + '\'' + ", createTime=" + createTime + '}'; } }
Bilibili
package com.kuang.prototype.demo02; import java.util.Date; /* 客户端:克隆 */ public class Bilibili { public static void main(String[] alrgs) throws CloneNotSupportedException { //原型对象v1 Date date = new Date(); Video v1 = new Video("狂神说Java", date); //v1克隆v2 //Video v2 = new Video( "狂神说Java", date); Video v2 = (Video) v1.clone(); System.out.println("v1=>" + v1); System.out.println("v2=>" + v2); date.setTime(22131231); System.out.println("+++++++++++++++++++++++++++++++++++"); System.out.println("v1=>" + v1); System.out.println("v2=>" + v2); } }
结构性模式:
-
作用
从程序的结构上实现松耦合, 从而可以扩大整体的类结构,用来解决更大的问题
适配器模式就像USB网线转换器
代码实现
Adaptee
package com.kuang.adapter; //要被适配的类:网线 public class Adaptee { public void request() { System.out.println("连接网线上网"); } }
Adapter
package com.kuang.adapter; //真正的适配器,需要连接USB,连接网线~ public class Adapter extends Adaptee implements NetToUsb { @Override public void handleRequest() { //上网的具体实现,找一个转接头 super.request(); } }
NetToUsb
package com.kuang.adapter; //按1口转换器的抽象实现~ public interface NetToUsb { //作用:处理请求,网线=>usb public void handleRequest(); }
Adapter2
package com.kuang.adapter; //真正的适配器,需 要连接USB,连接网线~ public class Adapter2 implements NetToUsb { //组合模式 private Adaptee adaptee; public Adapter2(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void handleRequest() { //上网的具体实现,找一个转接头 adaptee.request();//可以上网 } }
Computer
package com.kuang.adapter; //客户端关: 想上:网,插不L:网线~ public class Computer { //我们的电脑需要连接:转换器才可以:网 public void net(NetToUsb adapter) { //.上网的具体实现, 找个转换器 adapter.handleRequest(); } public static void main(String[] args) { /* //电脑,适配器,网线~ Computer computer = new Computer(); //电脑 Adaptee adaptee = new Adaptee(); //网线 Adapter adapter = new Adapter(); //转按器 computer.net(adapter);*/ System.out.println("++++++++++++++++++++++++++++++"); //电脑,适配器,网线~ Computer computer = new Computer(); //电脑 Adaptee adaptee = new Adaptee(); //网线 Adapter2 adapter = new Adapter2(adaptee); //转换器 computer.net(adapter); } }
角色分析
- 将一个类的接口转换成客户希望的另外一个接口. Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作
角色分析
- 目标接口: 客户所期待的接口, 目标可以是具体的或抽象的类, 可以是接口
- 需要适配的类: 需要适配的类或适配者类
- 适配器: 通过包装一个需要适配的对象, 把原接口转换成目标对象
原理理解
对象适配器优点
- 一个对象适配器可以把多个不同的适配者适配到同一个目标
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据"里氏代换原则", 适配者的子类也可通过该适配器进行适配
类适配器缺点
- 对于Java, C#等不支持多重类继承的语言, 一次最多只能适配一个适配者类,不能同时适配多个适配者,
- 在Java, C#等语言中, 类适配器模式中的目标抽象类只能为接口, 不能为类, 其使用有一定的局限性
适用场景
- 系统需要使用一些现有的类, 而这些类的接口(如方法名)不符合系统的需求,甚至没有这些类的源代码
- 想创建一个可以重复使用的类, 用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作
-
桥接模式
-
桥接模式是将抽象部分与它的实现部分分离, 使他们都可以独立地变化, 它是一种对象结构型模式, 又称为柄体(Handle and Body)模式或接口(Interface)模式
-
-
分析:
这个场景中有两个变化的维度: 品牌, 类型
代码实现大脑所想
代码实现
Brand
package com.kuang.bridge; //品牌 public interface Brand { void info(); }
Lenovo
package com.kuang.bridge; //联想品牌 public class Lenovo implements Brand { @Override public void info() { System.out.print("联想"); } }
Apple
package com.kuang.bridge; //联想品牌 public class Apple implements Brand { @Override public void info() { System.out.print("苹果"); } }
Computer
package com.kuang.bridge; //抽象的电脑类型类 public abstract class Computer { //组合,品牌~ protected Brand brand; public Computer(Brand brand) { this.brand = brand; } public void info() { brand.info();//自带品牌 } } class Desktop extends Computer { public Desktop(Brand brand) { super(brand); } @Override public void info() { super.info(); System.out.print("台式机"); } } class Laptop extends Computer { public Laptop(Brand brand) { super(brand); } @Override public void info() { super.info(); System.out.print("笔记本"); } }
Test
package com.kuang.bridge; public class Test { public static void main(String[] args) { //苹果管记本 Computer computer = new Laptop(new Apple()); computer.info(); System.out.println(); //联想台式机 Computer computer2 = new Desktop(new Lenovo()); computer2.info(); } }
运行结果
苹果笔记本
联想台式机
优劣势分析
好处分析:
- 桥接模式偶尔类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案更好的解决方法。极大的减少了子类的个数,从而降低管理和维护的成本
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则,就像一座桥,可以把两个变化的维度连接起来!
劣势分析:
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性.
最佳实践:
- 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
- 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
场景:
- Java语言通过Java虚拟机实现了平台的无关性。
- AWT中的Peer架构
- JDBC驱动程序也是桥接模式的应用之一。
代理模式
是SpringAOP的底层 [SpringAOP和SpringMVC]
代理模式的分类
- 静态代理
- 动态代理
10.1 静态代理
角色分析:
- 抽象角色: 一般会使用接口或抽象类来解决
- 真实角色: 被代理的角色
- 代理角色: 代理真实角色,代理真实角色后,我们一般会做一些附属操作
- 客户: 访问代理对象的人
代码步骤:
-
接口
public interface Rent { public void rent(); }
真实角色
//房东 public class Host implements Rent{ @Override public void rent() { System.out.println("房东要出租房子"); } }
代理角色
//代理 public class Proxy implements Rent{ private Host host; public Proxy(){ } public Proxy(Host host) { this.host = host; } @Override public void rent() { host.rent(); seeHouse(); hetong(); fare(); } //看房 public void seeHouse(){ System.out.println("中介带你看房"); } //收中介费 public void fare(){ System.out.println("收中介费"); } //签合同 public void hetong(){ System.out.println("签合同"); } }
客户端访问代理角色
public static void main(String[] args) { //房东要租房子 Host host = new Host(); //代理,中介帮房东租房子,但是 代理一般会有一些附属操作 Proxy proxy = new Proxy(host); //不用面对房东,直接找中介租房即可 proxy.rent(); }
代理模式的好处:
可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
公共也就交给代理角色,实现了业务的分工
公共业务发生扩展的时候,方便集中管理
缺点:
一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低
10.2 加深理解
我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想
聊聊AOP:纵向开发,横向开发。
-
创建一个抽象角色
//抽象角色:增删改查业务 public interface UserService { void add(); void delete(); void update(); void query(); }
我们需要一个真实对象来完成这些增删改查操作
//真实对象,完成增删改查操作的人 public class UserServiceImpl implements UserService { public void add() { System.out.println("增加了一个用户"); } public void delete() { System.out.println("删除了一个用户"); } public void update() { System.out.println("更新了一个用户"); } public void query() { System.out.println("查询了一个用户"); } }
需求来了,现在我们需要增加一个日志功能,怎么实现!
-
- 思路1 :在实现类上增加代码 【麻烦!】
- 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!
设置一个代理类来处理日志, 代理角色
//代理角色,在这里面增加日志的实现 public class UserServiceProxy implements UserService { private UserServiceImpl userService; //代理的一个具体的真实角色。 public void setUserService(UserServiceImpl userService) { this.userService = userService; } public void add() { log("add"); userService.add(); } public void delete() { log("delete"); userService.delete(); } public void update() { log("update"); userService.update(); } public void query() { log("query"); userService.query(); } public void log(String msg){ System.out.println("执行了"+msg+"方法"); } }
测试访问类
public class Client { public static void main(String[] args) { //真实业务 UserServiceImpl userService = new UserServiceImpl(); //代理类 UserServiceProxy proxy = new UserServiceProxy(); //使用代理类实现日志功能! proxy.setUserService(userService); proxy.add(); } }
10.3 动态代理
动态代理和静态代理角色一样
动态代理的代理类是动态生成的,不是我们直接写好的
动态代理分为两大类 : 基于接口的动态代理,基于类的动态代理
基于接口-JDK动态代理
基于类: cglib
java字节码实现 : javassist
需要了解的两个类: Proxy: 代理, InvocationHandler: 调用处理程序
InvocationHandler(调用处理程序)
//生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); }
代码实现
抽象角色和真实角色和之前的一样!
Rent . java 即抽象角色
//抽象角色:租房 public interface Rent { public void rent(); }
Host . java 即真实角色
//真实角色: 房东,房东要出租房子 public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); } }
ProxyInvocationHandler. java 即代理角色
public class ProxyInvocationHandler implements InvocationHandler { private Rent rent; //抽象类,代理的是一类对象 public void setRent(Rent rent) { this.rent = rent; } //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); } // proxy : 代理类 method : 代理类的调用处理程序的方法对象. // 处理代理实例上的方法调用并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throwsThrowable { seeHouse(); //核心:本质利用反射实现! Object result = method.invoke(rent, args); fare(); return result; } //看房 public void seeHouse(){ System.out.println("带房客看房"); } //收中介费 public void fare(){ System.out.println("收中介费"); } }
Client . java
//租客 public class Client { public static void main(String[] args) { //真实角色 Host host = new Host(); //代理实例的调用处理程序 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setRent(host); //将真实角色放置进去! Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类! proxy.rent(); } }
核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!
动态代理在理解
我们来使用动态代理实现代理我们后面写的UserService!
我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!
ProxyInvocationHandler. java 即代理角色
public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 private Object target; public void setTarget(Object target) { this.target = target; } 生成得到代理对象 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } // proxy : 代理类 // method : 代理类的调用处理程序的方法对象. public Object invoke(Object proxy, Method method, Object[] args) throwsThrowable { log(method.getName()); //动态代理的本质,就是使用反射机制实现 Object result = method.invoke(target, args); return result; } public void log(String methodName){ System.out.println("执行了"+methodName+"方法"); } }
测试
public class Client { public static void main(String[] args) { //真实角色 UserServiceImpl userService = new UserServiceImpl(); //代理角色,不存在 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setTarget(userService); //设置要代理的对象 //动态生成代理类 UserService proxy = (UserService) pih.getProxy(); proxy.add(); } }
动态代理的好处
静态代理有的它都有,静态代理没有的,它也有!
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
- 一个动态代理 , 一般代理某一类业务,不像一个静态代理只能代理一个真实角色,减少代码的开发量。
- 一个动态代理可以代理多个类,代理的是接口!
模版模式
一、豆浆制作问题
编写制作豆浆的程序,说明如下:
1)制作豆浆的流程 选材—>添加配料—>浸泡—>放到豆浆机打碎
2)通过添加不同的配料,可以制作出不同口味的豆浆
3)选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
4)请使用模板方法模式完成(说明:因为模板方法模式,比较简单,很容易就想到这个方案,因此就直接使用, 不再使用传统的方案来引出模板方法模式 )
二、模板方法模式基本介绍
基本介绍
1)模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),
在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
2)简单说,模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
3)这种类型的设计模式属于行为型模式。
三、模板方法模式原理类图
1、 模板方法模式的原理类图
对原理类图的说明-即(模板方法模式的角色及职责)
AbstractClass 抽象类:
类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现 其它的抽象方法 operationr2,3,4
ConcreteClass 实现抽象方法 operationr2,3,4, 以完成算法中特点子类的步骤
四、模板方法模式解决豆浆制作问题
1)应用实例要求
编写制作豆浆的程序,说明如下:
制作豆浆的流程 选材—>添加配料(每个种类的配料材料不一样)—>浸泡—>放到豆浆机打碎通过添加不同的配料,可以制作出不同口味的豆浆
选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的(红豆、花生豆浆…)
2)思路分析和图解(类图)
3)代码实现
主函数
public class Client { public static void main(String[] args) { //制作红豆豆浆 System.out.println("----制作红豆豆浆----"); SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk(); redBeanSoyaMilk.make(); System.out.println("---- 制 作 花 生 豆 浆 ----"); SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk(); peanutSoyaMilk.make(); } }
PeanutSoyaMilk
public class PeanutSoyaMilk extends SoyaMilk { @Override void addCondiments() { System.out.println(" 加入上好的花生 "); } }
RedBeanSoyaMilk
public class RedBeanSoyaMilk extends SoyaMilk { @Override void addCondiments() { System.out.println(" 加入上好的红豆 "); } }
SoyaMilk抽象类
//抽象类,表示豆浆
public abstract class SoyaMilk { //模板方法, make , //模板方法可以做成 final , 不让子类去覆盖重写 final void make() { select(); addCondiments(); soak(); beat(); } //选材料 void select() { System.out.println("第一步:选择好的新鲜黄豆 "); } //添加不同的配料, 抽象方法, 子类具体实现 abstract void addCondiments(); //浸泡 void soak() { System.out.println("第三步, 黄豆和配料开始浸泡, 需要 3 小时 "); } void beat() { System.out.println("第四步:黄豆和配料放到豆浆机去打碎 "); } }
五、模板方法模式的钩子方法
在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
2)还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造
代码
//抽象类,表示豆浆 public abstract class SoyaMilk { //模板方法, make //模板方法可以做成 final , 不让子类去覆盖. final void make() { select(); if(customerWantCondiments()) { addCondiments(); } soak(); beat(); } //选材料 void select() { System.out.println("第一步:选择好的新鲜黄豆 "); } //添加不同的配料, 抽象方法, 子类具体实现 abstract void addCondiments(); //浸泡 void soak() { System.out.println("第三步, 黄豆和配料开始浸泡, 需要 3 小时 "); } void beat() { System.out.println("第四步:黄豆和配料放到豆浆机去打碎 "); } //钩子方法,决定是否需要添加配料 ,子类重写该方法实现钩子。 boolean customerWantCondiments() { return true; } }
六、模板方法模式的注意事项和细节
1)基本思想:
算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
2)实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
3)既统一了算法,也提供了很大的灵活性。
父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
4)该模式的不足之处:
每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
5)一般模板方法都加上final 关键字, 防止子类重写模板方法.
6)模板方法模式使用场景:
当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时 可能不同,让子类重写来体现不同,通常考虑用模板方法模式来处理。
外观模式
外观类就是总控开关,去管理聚合的所有类
一、影院管理项目
组建一个家庭影院:
DVD 播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为: 直接用遥控器:统筹各设备开关
开爆米花机放 下 屏 幕 开 投 影 仪 开音响
开 DVD,选 dvd
去拿爆米花调 暗 灯 光 播放
观影结束后,关闭各种设备
二、传统方式解决影院管理
分别定义每个设备的类,然后在主方法按顺序调用打开,或关闭。
三、传统方式解决影院管理问题分析
1)在 ClientTest 的 main 方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程
2)不利于在 ClientTest 中,去维护对子系统的操作
3)解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法
ready, play, pause, end ),用来访问子系统中的一群接口
4)也就是说 就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节 => 外观模式
四、外观模式基本介绍
基本介绍
外观模式(Facade),也叫“过程模式:
外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
外观模式通过: 定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
五、外观模式原理类图
对类图说明(分类外观模式的角色)
1)外观类(Facade): 为调用端提供统一的调用接口, 外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
2)调用者(Client): 外观接口的调用者
3)子系统的集合: 指模块或者子系统,处理 Facade 对象指派的任务,他是功能的实际提供者
六、外观模式解决影院管理
1、传统方式解决影院管理说明
1)外观模式可以理解为转换一群接口,客户只要调用一个接口,而不用调用多个接口才能达到目的。比如:在 pc 上安装软件的时候经常有一键安装选项(省去选择安装目录、安装的组件等等),还有就是手机的重启功能(把关机和启动合为一个操作)。
2)外观模式就是 解决多个复杂接口带来的使用困难,起到简化用户操作的作用
3)示意图说明
2、外观模式应用实例
1)应用实例要求
2)使用外观模式来完成家庭影院项目
3)思路分析和图解
4)代码实现
主函数
public class Client { public static void main(String[] args) { //这里直接调用外观类,然后里面的方法已经做好了他的实现 HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade(); homeTheaterFacade.ready(); homeTheaterFacade.play(); homeTheaterFacade.end(); } }
HomeTheaterFacade外观者类
public class HomeTheaterFacade { //定义各个子系统对象,聚合关系 private TheaterLight theaterLight; private Popcorn popcorn; private Stereo stereo; private Projector projector; private Screen screen; private DVDPlayer dVDPlayer; //构造器 public HomeTheaterFacade() { super(); this.theaterLight = TheaterLight.getInstance(); this.popcorn = Popcorn.getInstance(); this.stereo = Stereo.getInstance(); this.projector = Projector.getInstance(); this.screen = Screen.getInstance(); this.dVDPlayer = DVDPlayer.getInstanc(); } //操作分成 4 步 public void ready() { popcorn.on(); popcorn.pop(); screen.down(); projector.on(); stereo.on(); dVDPlayer.on(); theaterLight.dim(); } public void play() { dVDPlayer.play(); } public void pause() { dVDPlayer.pause(); } public void end() { popcorn.off(); theaterLight.bright(); screen.up(); projector.off(); stereo.off(); dVDPlayer.off(); } }
DVDPlayer
public class DVDPlayer { //使用单例模式, 使用饿汉式 private static DVDPlayer instance = new DVDPlayer(); public static DVDPlayer getInstanc() { return instance; } public void on() { System.out.println(" dvd on "); } public void off() { System.out.println(" dvd off "); } public void play() { System.out.println(" dvd is playing "); } //.... public void pause() { System.out.println(" dvd pause .."); } }
Popcorn
public class Popcorn { private static Popcorn instance = new Popcorn(); public static Popcorn getInstance() { return instance; } public void on() { System.out.println(" popcorn on "); } public void off() { System.out.println(" popcorn ff "); } public void pop() { System.out.println(" popcorn is poping "); } }
Projector
public class Projector { private static Projector instance = new Projector(); public static Projector getInstance() { return instance; } public void on() { System.out.println(" Projector on "); } public void off() { System.out.println(" Projector ff "); } public void focus() { System.out.println(" Projector is Projector "); } //... }
Screen
public class Screen { private static Screen instance = new Screen(); public static Screen getInstance() { return instance; } public void up() { System.out.println(" Screen up "); } public void down() { System.out.println(" Screen down "); } }
Stereo
public class Stereo { private static Stereo instance = new Stereo(); public static Stereo getInstance() { return instance; } public void on() { System.out.println(" Stereo on "); } public void off() { System.out.println(" Screen off "); } public void up() { System.out.println(" Screen up.. "); } //... }
TheaterLight
public class TheaterLight { private static TheaterLight instance = new TheaterLight(); public static TheaterLight getInstance() { return instance; } public void on() { System.out.println(" TheaterLight on "); } public void off() { System.out.println(" TheaterLight off "); } public void dim() { System.out.println(" TheaterLight dim.. "); } public void bright() { System.out.println(" TheaterLight bright.. "); } }
七、外观模式的注意事项和细节
1)外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
2)外观模式对客户端与子系统的耦合关系 - 解耦,让子系统内部的模块更易维护和扩展
3)通过合理的使用外观模式,可以帮我们更好的划分访问的层次
4)当系统需要进行分层设计时,可以考虑使用 Facade 模式
5)在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade 类,来提供遗留系统的比较清晰简单的接口,让新系统与 Facade 类交互,提高复用性
6)不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。
策略模式
将原本继承的方式,换成组合、聚合,让算法变化(策略)与算法客户(对象)分离
一、编写鸭子项目,具体要求如下:
1)有各种鸭子(比如 野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如 叫、飞行等)
2)显示鸭子的信息
二、传统方案解决鸭子问题的分析和代码实现
1)传统的设计方案(类图)
2)类图
3)代码实现
Duck抽象类
public abstract class Duck { public Duck() {} public abstract void display();//显示鸭子信息 public void quack() { System.out.println("鸭子嘎嘎叫~~"); } public void swim() { System.out.println("鸭子会游泳~~"); } public void fly() { System.out.println("鸭子会飞翔~~~"); } }
PekingDuck
public class PekingDuck extends Duck { @Override public void display() { System.out.println("~~北京鸭~~~"); } //因为北京鸭不能飞翔,因此需要重写 fly @Override public void fly() { System.out.println("北京鸭不能飞翔"); } }
ToyDuck
public class ToyDuck extends Duck{ @Override public void display() { System.out.println("玩具鸭"); } //需要重写父类的所有方法 @Override public void quack() { System.out.println("玩具鸭不能叫~~"); } @Override public void swim() { System.out.println("玩具鸭不会游泳~~"); } @Override public void fly() { System.out.println("玩具鸭不会飞翔~~~"); } }
WildDuck
public class WildDuck extends Duck { @Override public void display() { System.out.println(" 这是野鸭 "); } }
三、传统的方式实现的问题分析和解决方案
1)其它鸭子,都继承了 Duck 类,所以 fly 让所有子类都会飞了,这是不正确的
2)上面说的 1 的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。如果基类改变,那么就会有溢出效应
3)为了改进 1 问题,我们可以通过覆盖 fly 方法来解决 => 覆盖解决
4)问题又来了,如果我们有一个玩具鸭子 ToyDuck, 这样就需要 ToyDuck 去覆盖 Duck 的所有实现的方法 ===> 解决思路 -》策略模式 (strategy pattern)
我们的子类需要用到基类的内容,但又不想重写那么就可以使用策略模式
四、 策略模式基本介绍
1)策略模式(Strategy Pattern)中, 定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
2)这算法体现了几个设计原则:
第一、把变化的代码从不变的代码中分离出来;
第二、针对接口编程而不是具体类(定义了策略接口);
第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。
五、类图
说明:
从上图可以看到,客户 context 有成员变量 strategy 或者其他的策略接口,至于需要使用到哪个策略,我们可以在构造器中指定
六、 策略模式解决鸭子问题
1)应用实例要求
编写程序完成前面的鸭子项目,要求使用策略模式
2)思路分析(类图)
抽象鸭子每一个动作为一个行为接口(策略接口),鸭子生物抽象一个抽象类(content类)
策略模式:
分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是: 分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者
3)代码实现
BadFlyBehavior public class BadFlyBehavior implements FlyBehavior { @Override public void fly() { System.out.println(" 飞翔技术一般 "); } }
主函数
public class Client { public static void main(String[] args) { WildDuck wildDuck = new WildDuck(); wildDuck.fly();// ToyDuck toyDuck = new ToyDuck(); toyDuck.fly(); PekingDuck pekingDuck = new PekingDuck(); pekingDuck.fly(); //动态改变某个对象的行为, 北京鸭 不能飞 pekingDuck.setFlyBehavior(new NoFlyBehavior()); System.out.println("北京鸭的实际飞翔能力"); pekingDuck.fly(); } }
Duck抽象类
public abstract class Duck { //属性, 策略接口 FlyBehavior flyBehavior;//聚合关系 //其它属性<->策略接口 QuackBehavior quackBehavior; public Duck() {} public abstract void display();//显示鸭子信息 public void quack() { System.out.println("鸭子嘎嘎叫~~"); } public void swim() { System.out.println("鸭子会游泳~~"); } public void fly() { //改进 if(flyBehavior != null) { flyBehavior.fly(); } } public void setFlyBehavior(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; } public void setQuackBehavior(QuackBehavior quackBehavior) { this.quackBehavior = quackBehavior; } }
FlyBehavior接口,鸭子飞翔策略接口
public interface FlyBehavior { void fly(); // 子类具体实现 }
GoodFlyBehavior
public class GoodFlyBehavior implements FlyBehavior { @Override public void fly() { System.out.println(" 飞翔技术高超 ~~~"); } }
NoFlyBehavior
public class NoFlyBehavior implements FlyBehavior{ @Override public void fly() { System.out.println(" 不会飞翔 "); } }
PekingDuck
public class PekingDuck extends Duck { //假如北京鸭可以飞翔,但是飞翔技术一般 public PekingDuck() { flyBehavior = new BadFlyBehavior(); } @Override public void display() { System.out.println("~~北京鸭~~~"); } }
QuackBehavior鸭叫抽象类,鸭子叫的策略接口
public interface QuackBehavior { void quack();//子类实现 }
ToyDuck
public class ToyDuck extends Duck{ public ToyDuck() { flyBehavior = new NoFlyBehavior(); } @Override public void display() { System.out.println("玩具鸭"); } //需要重写父类的所有方法 public void quack() { System.out.println("玩具鸭不能叫~~"); } public void swim() { System.out.println("玩具鸭不会游泳~~"); } }
WildDuck
public class WildDuck extends Duck { //构造器,传入 FlyBehavor 的对象 public WildDuck() { flyBehavior = new GoodFlyBehavior(); } @Override public void display() { System.out.println(" 这是野鸭 "); } }
七、策略模式的注意事项和细节
使用继承的方式,子类需要根据自身需求重写父类的方法,比较麻烦,可以把子类所拥有的各种行为抽象,并作为属性到父类进行组合,不同的行为子类去实现这些接口,这样继承父类可以根据自身需求使用不同为抽象属性创建为行为子类。
1)策略模式的关键是:分析项目中变化部分与不变部分
2)策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性
3)体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为) 即可,避免了使用多重转移语句(if…else if…else)
4)提供了可以替换继承关系的办法: 策略模式将算法封装在独立的 Strategy 类中使得你可以独立于其 Context 改变它,使它易于切换、易于理解、易于扩展
5)需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞,类爆炸
观察者模式
关注一对多的订阅形式,分离观察者(订阅者)和数据提供者,实现解耦
一、天气预报项目需求,具体要求如下:
1)气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。
2)需要设计开放型 API,便于其他第三方也能接入气象站获取数据。
3)提供温度、气压和湿度的接口
4)测量数据更新时,要能实时的通知给第三方
二、 普通方案
传统的设计方案
代码实现
WeatherData主动推送的方式
CurrentConditions理解为应用网址
主函数:
public class Client { public static void main(String[] args) { //创建接入方 currentConditions CurrentConditions currentConditions = new CurrentConditions(); //创建 WeatherData 并将 接入方 currentConditions 传递到 WeatherData 中 WeatherData weatherData = new WeatherData(currentConditions); //更新天气情况 weatherData.setData(30, 150, 40); //天气情况变化 System.out.println("============天气情况变化============="); weatherData.setData(40, 160, 20); } }
CurrentConditions:
/** * 显示当前天气情况(可以理解成是气象站自己的网站) * @author Administrator * */ public class CurrentConditions { // 温度,气压,湿度 private float temperature; private float pressure; private float humidity; //更新 天气情况,是由 WeatherData 来调用,我使用推送模式 public void update(float temperature, float pressure, float humidity) { this.temperature = temperature; this.pressure = pressure; this.humidity = humidity; display(); } //显示 public void display() { System.out.println("***Today mTemperature: " + temperature + "***"); System.out.println("***Today mPressure: " + pressure + "***"); System.out.println("***Today mHumidity: " + humidity + "***"); } }
WeatherData:
/** * 类是核心 * 1. 包含最新的天气情况信息 * 2. 含有 CurrentConditions 对象 * 3. 当数据有更新时,就主动的调用 CurrentConditions 对象 update 方法(含 display), 这样他们(接入方)就看到最新的信息 * @author Administrator * */ public class WeatherData { private float temperatrue; private float pressure; private float humidity; //加入新的第三方 private CurrentConditions currentConditions; public WeatherData(CurrentConditions currentConditions) { this.currentConditions = currentConditions; } public float getTemperature() { return temperatrue; } public float getPressure() { return pressure; } public float getHumidity() { return humidity; } public void dataChange() { //调用 接入方的 update currentConditions.update(getTemperature(), getPressure(), getHumidity()); } //当数据有更新时,就调用 setData public void setData(float temperature, float pressure, float humidity) { this.temperatrue = temperature; this.pressure = pressure; this.humidity = humidity; //调用 dataChange, 将最新的信息 推送给 接入方 currentConditions dataChange(); } }
问题分析
1)其他第三方接入气象站获取数据的问题
2)无法在运行时动态的添加第三方 (新浪网站)
3)违反 ocp 原则=>观察者模式
//在 WeatherData 中,当增加一个第三方,都需要创建一个对应的第三方的公告板对象,并加入到 dataChange, 不利于维护,也不是动态加入 public void dataChange() { currentConditions.update(getTemperature(), getPressure(), getHumidity()); }
三、 观察者模式原理
观察者模式类似订牛奶业务
1)奶站/气象局:Subject
2)用户/第三方网站:Observer (观察者)
Subject:登记注册、移除和通知 ---->相当于上面的WeatherData
1)registerObserver: 注 册
2)removeObserver: 移 除
3)notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送, 看具体需求定
Observer:接收输入(观察者)
观察者模式:
对象之间多对一依赖的一种设计方案,被依赖的对象为 Subject,依赖的对象为 Observer,Subject通知 Observer 变化, 比如这里的奶站是 Subject,是 1 的一方。用户时 Observer,是多的一方,依赖方通过方法加入被依赖方的list里面实现一对多的观察通知,不破坏OCP原则。
四、观察者模式解决天气预报需求
1、 类图说明
2、代码实现
BaiduSite
public class BaiduSite implements Observer { // 温度,气压,湿度 private float temperature; private float pressure; private float humidity; // 更新 天气情况,是由 WeatherData 来调用,我使用推送模式 public void update(float temperature, float pressure, float humidity) { this.temperature = temperature; this.pressure = pressure; this.humidity = humidity; display(); } // 显 示 public void display() { System.out.println("===百度网站===="); System.out.println("***百度网站 气温 : " + temperature + "***"); System.out.println("***百度网站 气压: " + pressure + "***"); System.out.println("***百度网站 湿度: " + humidity + "***"); } }
主函数
public class Client { public static void main(String[] args) { //创建一个 WeatherData WeatherData weatherData = new WeatherData(); //创建观察者 CurrentConditions currentConditions = new CurrentConditions(); BaiduSite baiduSite = new BaiduSite(); // 注 册 到 weatherData weatherData.registerObserver(currentConditions); weatherData.registerObserver(baiduSite); //测试 System.out.println("通知各个注册的观察者, 看看信息"); weatherData.setData(10f, 100f, 30.3f); weatherData.removeObserver(currentConditions); System.out.println("=========================="); //测试 System.out.println("通知各个注册的观察者, 看看信息"); weatherData.setData(10f, 100f, 30.3f); } }
CurrentConditions
public class CurrentConditions implements Observer { // 温度,气压,湿度 private float temperature; private float pressure; private float humidity; // 更新 天气情况,是由 WeatherData 来调用,我使用推送模式 public void update(float temperature, float pressure, float humidity) { this.temperature = temperature; this.pressure = pressure; this.humidity = humidity; display(); } // 显 示 public void display() { System.out.println("***Today mTemperature: " + temperature + "***"); System.out.println("***Today mPressure: " + pressure + "***"); System.out.println("***Today mHumidity: " + humidity + "***"); } }
Observer
//观察者接口,有观察者来实现 public interface Observer { public void update(float temperature, float pressure, float humidity); }
Subject
//接口, 让 WeatherData 来实现 public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); }
WeatherData
/** * 类是核心 * 1. 包含最新的天气情况信息 * 2. 含有 观察者集合,使用 ArrayList 管理 * 3. 当数据有更新时,就主动的调用 ArrayList, 通知所有的(接入方)就看到最新的信息 * @author Administrator * */ @Data public class WeatherData implements Subject { private float temperatrue; private float pressure; private float humidity; //观察者集合 private ArrayList<Observer> observers; //加入新的第三方 public WeatherData() { observers = new ArrayList<Observer>(); } public void dataChange() { //调用 接入方的 update notifyObservers(); } //当数据有更新时,就调用 setData public void setData(float temperature, float pressure, float humidity) { this.temperatrue = temperature; this.pressure = pressure; this.humidity = humidity; //调用 dataChange, 将最新的信息 推送给 接入方 currentConditions dataChange(); } //注册一个观察者 @Override public void registerObserver(Observer o) { observers.add(o); } //移除一个观察者 @Override public void removeObserver(Observer o) { if(observers.contains(o)) { observers.remove(o); } } //遍历所有的观察者,并通知 @Override public void notifyObservers() { //遍历集合 for(int i = 0; i < observers.size(); i++) { //根据每个订阅的观察者对应的数据 observers.get(i).update(this.temperatrue, this.pressure, this.humidity); } } }
3、观察者模式的好处
1)观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
2)这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类 WeatherData 不会修改代码, 遵守了 ocp 原则
begin----------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
上述知识点总结(重要)
设计模式概述
设计模式:是一套用来提高代码可复用性,可维护性、可读性、稳健型以及安全性的解决方案
设计模式的本质:是面向对象设计原则的实际运用,是对类的封装、继承、多态以及类的关联关系和组合关系的充分理解。
设计模式的的基本要素:模式名称、问题、解决方案、效果
分类
创建型模式:(描述怎样去创建一个对象,创建和使用分离)
- 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构型模式:(描述如何将类或对象安装某种类型组成更大的结构)
- 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为型模式:(描述类和对象如何可以相互协作)
- 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
OOP七大原则
开闭原则:对扩展开发,对修改关闭(当需求需要改变的时候,尽量去扩展)
里氏替换原则: 继承必须确保超类所拥有的性质在子类中仍然成立(尽量不重写父类的方法)
依赖倒置原则: 要面向接口编程,不要面向实现编程。(低级模块不依赖高级模块,而是共同依赖抽象模块、抽象不依赖细节,细节依赖抽象)
单一职责原则: 控制类的粒度大小,将对象解耦,提高其内聚性(一个对象不应该担任太多的职责,原子性,单一的方法做单一的事情)
接口隔离原则: 要为各个类建立他们需要的专用接口
迪米特法则: 只与你的直接朋友交谈,不跟“陌生人”说话,降低代码之间的耦合度(比如A->B->C,而不能直接A->C,,controller,service,dao,不能是直接controller->dao,缺点是增加了一个中介)
合成复用原则: 尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现(如内部类组合外部类,属于has-a关系,但是子类继承则是靠继承关系来实现的属于is-a关系)
常用的设计模式总结
单例模式
包括懒汉式和饿汉式,懒汉式是当对象不存在的时候直接创建,存在就不再创建,饿汉式则是当类创建的时候就创建,之后只返回这个实例。
class Singleton { private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } }+ // 单例模式-延迟加载版 class SingletonLazy { private static SingletonLazy instance; public static SingletonLazy getInstance() { if (instance == null) { instance = new SingletonLazy(); } return instance; } }
- 简单工厂模式:用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
- 工厂方法模式:用来生产同一等级结构中的固定产品(支持增加任意产品)
- 抽象工厂模式:围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
简单工厂模式
根据客户端传入的参数决定创建哪个对象,当加入新的对象的时候会破坏开闭原则
public static Car getCar(String car){ if(car.equals("wuling")){ return new WuLing(); }else if(car.equals("tesila")){ return new Tesla(); }else { return null; } }
工厂方法模式
为每一个新加入的类创建一个工厂,实现动态的增加,不破坏ocp原则。
抽象工厂模式
不但是简单的一个工厂接口,而是一个专门创建工厂的工厂,抽象接口指明了新工厂生产的对象。
// 抽象产品工厂 public interface IProductFactory { // 生产手机 IphoneProduct iphoneProduct(); // 生产路由器 IRouterProduct irouterProduct(); }
建造者模式
该模式强调一个产品分成多个步骤建造完成,建造者提供了建造产品步骤的方法,客户端可以指定建造的过程。
// 抽象的建造者 public abstract class Builder { public abstract Builder builderA(String msg);// 汉堡 public abstract Builder builderB(String msg);// 可乐 public abstract Builder builderC(String msg);// 薯条 public abstract Builder builderD(String msg);// 甜点 abstract Product getProduct(); } public class Test { public static void main(String[] args) { // 服务员 Woker woker = new Woker(); // 链式编程 :在原来的基础上,可以自由的自合,如果不组合也有固定的套餐 Product product = woker.builderA("全家桶").builderB("雪碧") .getProduct(); System.out.println(product.toString()); } }
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式
原型模式
原型模式则是通过实现cloneable接口和重写clone方法完成对象的创建,而不是new产生。
public class Bilibili { public static void main(String[] args) throws CloneNotSupportedException { //原型对象v1 Date date = new Date(); Video v1 = new Video("狂神说Java", date); //v1克隆v2 //Video v2 = new Video( "狂神说Java", date); Video v2 = (Video) v1.clone(); System.out.println("v1=>" + v1); System.out.println("v2=>" + v2); date.setTime(22131231); System.out.println("++++++++++++++++++++++++++++++++++++++"); System.out.println("v1=>" + v1); System.out.println("v2=>" + v2); //没有克隆引用类型,两个对象使用的还是同一个日期引用对象 } }
适配器模式
- 将一个类的接口转换成客户希望的另外一个接口. Adapter模式使得原本由于接口不兼容而不能一起工作的那些类中间类适配可以在一起工作
public static void main(String[] args) { /* //电脑,适配器,网线~ Computer computer = new Computer(); //电脑 Adaptee adaptee = new Adaptee(); //网线 Adapter adapter = new Adapter(); //转按器 computer.net(adapter);*/ System.out.println("++++++++++++++++++++++++++++++"); //电脑,适配器,网线~ Computer computer = new Computer(); //电脑 Adaptee adaptee = new Adaptee(); //网线 Adapter2 adapter = new Adapter2(adaptee); //转换器 computer.net(adapter); }
桥接模式
多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案更好的解决方法。桥接模式是比多继承方案更好的解决方法,,就像一座桥,可以把两个变化的维度连接起来,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则。
代理模式
1.静态代理
可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
公共也就交给代理角色,实现了业务的分工
公共业务发生扩展的时候,方便集中管理,我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想
2.动态代理
动态代理的代理类是动态生成的,不是我们直接写好的。实现invokehandle,重写invoke方法
//租客 public class Client { public static void main(String[] args) { //真实角色 Host host = new Host(); //代理实例的调用处理程序 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setRent(host); //将真实角色放置进去! Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类! proxy.rent(); } }
模板模式
模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
制作豆浆的流程 选材—>添加配料(每个种类的配料材料不一样)—>浸泡—>放到豆浆机打碎通过添加不同的配料,可以制作出不同口味的豆浆
选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的(红豆、花生豆浆…),但配料可以根据子类实现不同,制造不同的豆浆对象。
当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时 可能不同,让子类重写来体现不同,通常考虑用模板方法模式来处理。
外观模式
外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
对子系统进一步封装,使得对子系统的调用更加简单,不关注子系统具体细节。
策略模式
将原本继承的方式,改为组合聚合,让算法变化(策略)和算法客户(对象)分离。体现了“对修改关闭,对扩展开放”原则。)需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞,类爆炸。
定义了一系列的算法 或 逻辑 或 相同意义的操作,并将每一个算法、逻辑、操作封装起来,而且使它们还可以相互替换。
public class WildDuck extends Duck { //构造器,传入 FlyBehavor 的对象 public WildDuck() { flyBehavior = new GoodFlyBehavior(); } @Override public void display() { System.out.println(" 这是野鸭 "); } }
观察者模式
关注一对多的订阅形式,分离观察者(订阅者)和数据提供者,实现解耦。
对象之间多对一依赖的一种设计方案,被依赖的对象为 Subject,依赖的对象为 Observer,Subject通知 Observer 变化, 比如这里的奶站是 Subject,是 1 的一方。用户时 Observer,是多的一方,依赖方通过方法加入被依赖方的list里面实现一对多的观察通知,不破坏OCP原则。
---------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------- end
设计模式常见面试题汇总一
1.说一下设计模式?你都知道哪些?
答:设计模式总共有 23 种,总体来说可以分为三大类:创建型模式( Creational Patterns )、结构型模式( Structural Patterns )和行为型模式( Behavioral Patterns )。
**分类** **包含** **关注点** 创建型模式 工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式
关注于对象的创建,同时隐藏创建逻辑 结构型模式 适配器模式、过滤器模式、装饰模式、享元模式、代理模式、外观模式、组合模式、桥接模式
关注类和对象之间的组合 行为型模式 责任链模式、命令模式、中介者模式、观察者模式、状态模式、策略模式、模板模式、空对象模式、备忘录模式、迭代器模式、解释器模式、访问者模式 关注对象之间的通信
下面会对常用的设计模式分别做详细的说明。
2.什么是单例模式?
答:单例模式是一种常用的软件设计模式,在应用这个模式时,单例对象的类必须保证只有一个实例存在,整个系统只能使用一个对象实例。
优点:不会频繁地创建和销毁对象,浪费系统资源。
使用场景:IO 、数据库连接、Redis 连接等。
单例模式代码实现:
class Singleton { private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } }