设计模式学习
设计模型
狂神课程链接:【狂神说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 连接等。
单例模式代码实现:
单例模式调用代码:
程序的输出结果:true
可以看出以上单例模式是在类加载的时候就创建了,这样会影响程序的启动速度,那如何实现单例模式的延迟加载?在使用时再创建?
单例延迟加载代码:
// 单例模式-延迟加载版
以上为非线程安全的,单例模式如何支持多线程?
使用 synchronized 来保证,单例模式的线程安全代码:
3.什么是简单工厂模式?
答:简单工厂模式又叫静态工厂方法模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。比如,一台咖啡机就可以理解为一个工厂模式,你只需要按下想喝的咖啡品类的按钮(摩卡或拿铁),它就会给你生产一杯相应的咖啡,你不需要管它内部的具体实现,只要告诉它你的需求即可。
优点:
- 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象;
- 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量;
- 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
缺点:
- 不易拓展,一旦添加新的产品类型,就不得不修改工厂的创建逻辑;
- 产品类型较多时,工厂的创建逻辑可能过于复杂,一旦出错可能造成所有产品的创建失败,不利于系统的维护。
简单工厂示意图如下:
简单工厂代码实现:
4.什么是抽象工厂模式?
答:抽象工厂模式是在简单工厂的基础上将未来可能需要修改的代码抽象出来,通过继承的方式让子类去做决定。
比如,以上面的咖啡工厂为例,某天我的口味突然变了,不想喝咖啡了想喝啤酒,这个时候如果直接修改简单工厂里面的代码,这种做法不但不够优雅,也不符合软件设计的“开闭原则”,因为每次新增品类都要修改原来的代码。这个时候就可以使用抽象工厂类了,抽象工厂里只声明方法,具体的实现交给子类(子工厂)去实现,这个时候再有新增品类的需求,只需要新创建代码即可。
抽象工厂实现代码如下:
5.什么是观察者模式?
观察者模式是定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。 优点:
- 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色;
- 观察者模式在观察目标和观察者之间建立一个抽象的耦合;
- 观察者模式支持广播通信;
- 观察者模式符合开闭原则(对拓展开放,对修改关闭)的要求。
缺点:
- 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间;
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃;
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
在观察者模式中有如下角色:
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象;
- ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知;
- Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己;
- ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
观察者模式实现代码如下。
1)定义观察者(消息接收方)
2)定义被观察者(消息发送方)
3)代码调用
程序执行结果如下:
老王:更新了
Java:更新了
6.什么是装饰器模式?
答:装饰器模式是指动态地给一个对象增加一些额外的功能,同时又不改变其结构。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
装饰器模式的关键:装饰器中使用了被装饰的对象。
比如,创建一个对象“laowang”,给对象添加不同的装饰,穿上夹克、戴上帽子......,这个执行过程就是装饰者模式,实现代码如下。
1)定义顶层对象,定义行为
interface IPerson {
void show();
}
2)定义装饰器超类
class DecoratorBase implements IPerson{
IPerson iPerson;
public DecoratorBase(IPerson iPerson){
this.iPerson = iPerson;
}
@Override
public void show() {
iPerson.show();
}
}
3)定义具体装饰器
class Jacket extends DecoratorBase {
public Jacket(IPerson iPerson) {
super(iPerson);
}
@Override
public void show() {
// 执行已有功能
iPerson.show();
// 定义新行为
System.out.println("穿上夹克");
}
}
class Hat extends DecoratorBase {
public Hat(IPerson iPerson) {
super(iPerson);
}
@Override
public void show() {
// 执行已有功能
iPerson.show();
// 定义新行为
System.out.println("戴上帽子");
}
}
4)定义具体对象
class LaoWang implements IPerson{
@Override
public void show() {
System.out.println("什么都没穿");
}
}
5)装饰器模式调用
public class DecoratorTest {
public static void main(String[] args) {
LaoWang laoWang = new LaoWang();
Jacket jacket = new Jacket(laoWang);
Hat hat = new Hat(jacket);
hat.show();
}
}
7.什么是模板方法模式?
答:模板方法模式是指定义一个模板结构,将具体内容延迟到子类去实现。
优点:
- 提高代码复用性:将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中;
- 实现了反向控制:通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制并且符合开闭原则。
以给冰箱中放水果为例,比如,我要放一个香蕉:开冰箱门 → 放香蕉 → 关冰箱门;如果我再要放一个苹果:开冰箱门 → 放苹果 → 关冰箱门。可以看出它们之间的行为模式都是一样的,只是存放的水果品类不同而已,这个时候就非常适用模板方法模式来解决这个问题,实现代码如下:
程序执行结果:
开冰箱门
放香蕉
关冰箱门
8.什么是代理模式?
代理模式是给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
优点:
- 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;
- 可以灵活地隐藏被代理对象的部分功能和服务,也增加额外的功能和服务。
缺点:
- 由于使用了代理模式,因此程序的性能没有直接调用性能高;
- 使用代理模式提高了代码的复杂度。
举一个生活中的例子:比如买飞机票,由于离飞机场太远,直接去飞机场买票不太现实,这个时候我们就可以上携程 App 上购买飞机票,这个时候携程 App 就相当于是飞机票的代理商。
代理模式实现代码如下:
9.什么是策略模式?
答:策略模式是指定义一系列算法,将每个算法都封装起来,并且使他们之间可以相互替换。
优点:遵循了开闭原则,扩展性良好。
缺点:随着策略的增加,对外暴露越来越多。
以生活中的例子来说,比如我们要出去旅游,选择性很多,可以选择骑车、开车、坐飞机、坐火车等,就可以使用策略模式,把每种出行作为一种策略封装起来,后面增加了新的交通方式了,如超级高铁、火箭等,就可以不需要改动原有的类,新增交通方式即可,这样也符合软件开发的开闭原则。 策略模式实现代码如下:
程序执行的结果:
骑自行车
10.什么是适配器模式?
答:适配器模式是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而无法一起工作的两个类能够在一起工作。
优点:
- 可以让两个没有关联的类一起运行,起着中间转换的作用;
- 灵活性好,不会破坏原有的系统。
缺点:过多地使用适配器,容易使代码结构混乱,如明明看到调用的是 A 接口,内部调用的却是 B 接口的实现。
以生活中的例子来说,比如有一个充电器是 MicroUSB 接口,而手机充电口却是 TypeC 的,这个时候就需要一个把 MicroUSB 转换成 TypeC 的适配器,如下图所示:
适配器实现代码如下:
程序执行结果:
TypeC 充电
11.JDK 类库常用的设计模式有哪些?
答:JDK 常用的设计模式如下:
1)工厂模式
java.text.DateFormat 工具类,它用于格式化一个本地日期或者时间。
public final static DateFormat getDateInstance();
public final static DateFormat getDateInstance(int style);
public final static DateFormat getDateInstance(int style,Locale locale);
加密类
KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");
Cipher cipher = Cipher.getInstance("DESede");
2)适配器模式
把其他类适配为集合类
List<Integer> arrayList = java.util.Arrays.asList(new Integer[]{1,2,3});
List<Integer> arrayList = java.util.Arrays.asList(1,2,3);
3)代理模式
如 JDK 本身的动态代理。
4)单例模式
全局只允许有一个实例,比如:
Runtime.getRuntime();
5)装饰器
为一个对象动态的加上一系列的动作,而不需要因为这些动作的不同而产生大量的继承类。
java.io.BufferedInputStream(InputStream);
java.io.DataInputStream(InputStream);
java.io.BufferedOutputStream(OutputStream);
java.util.zip.ZipOutputStream(OutputStream);
java.util.Collections.checkedList(List list, Class type) ;
6)模板方法模式
定义一个操作中算法的骨架,将一些步骤的执行延迟到其子类中。
比如,Arrays.sort() 方法,它要求对象实现 Comparable 接口。
class Person implements Comparable{
private Integer age;
public Person(Integer age){
this.age = age;
}
@Override
public int compareTo(Object o) {
Person person = (Person)o;
return this.age.compareTo(person.age);
}
}
public class SortTest(){
public static void main(String[] args){
Person p1 = new Person(10);
Person p2 = new Person(5);
Person p3 = new Person(15);
Person[] persons = {p1,p2,p3};
//排序
Arrays.sort(persons);
}
}
12.IO 使用了什么设计模式?
答:IO 使用了适配器模式和装饰器模式。
- 适配器模式:由于 InputStream 是字节流不能享受到字符流读取字符那么便捷的功能,借助 InputStreamReader 将其转为 Reader 子类,因而可以拥有便捷操作文本文件方法;
- 装饰器模式:将 InputStream 字节流包装为其他流的过程就是装饰器模式,比如,包装为 FileInputStream、ByteArrayInputStream、PipedInputStream 等。
13.Spring 中都使用了哪些设计模式?
答:Spring 框架使用的设计模式如下。
- 代理模式:在 AOP 中有使用
- 单例模式:bean 默认是单例模式
- 模板方法模式:jdbcTemplate
- 工厂模式:BeanFactory
- 观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用,比如,ContextStartedEvent 就是 ApplicationContext 启动后触发的事件
- 适配器模式:Spring MVC 中也是用到了适配器模式适配 Controller
设计模式面试题(总结最全面的面试题!!!)二
设计模式
什么是设计模式
设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
为什么要学习设计模式
看懂源代码:如果你不懂设计模式去看Jdk、Spring、SpringMVC、IO等等等等的源码,你会很迷茫,你会寸步难行
看看前辈的代码:你去个公司难道都是新项目让你接手?很有可能是接盘的,前辈的开发难道不用设计模式?
编写自己的理想中的好代码:我个人反正是这样的,对于我自己开发的项目我会很认真,我对他比对我女朋友还好,把项目当成自己的儿子一样
设计模式分类
- 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
- 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式的六大原则
开放封闭原则(Open Close Principle)
- 原则思想:尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化
- 描述:一个软件产品在生命周期内,都会发生变化,既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,以提高项目的稳定性和灵活性。
- 优点:单一原则告诉我们,每个类都有自己负责的职责,里氏替换原则不能破坏继承关系的体系。
里氏代换原则(Liskov Substitution Principle)
- 原则思想:使用的基类可以在任何地方使用继承的子类,完美的替换基类。
- 大概意思是:子类可以扩展父类的功能,但不能改变父类原有的功能。子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,子类中可以增加自己特有的方法。
- 优点:增加程序的健壮性,即使增加了子类,原有的子类还可以继续运行,互不影响。
依赖倒转原则(Dependence Inversion Principle)
- 依赖倒置原则的核心思想是面向接口编程.
- 依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,
- 这个是开放封闭原则的基础,具体内容是:对接口编程,依赖于抽象而不依赖于具体。
接口隔离原则(Interface Segregation Principle)
- 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
- 例如:支付类的接口和订单类的接口,需要把这俩个类别的接口变成俩个隔离的接口
迪米特法则(最少知道原则)(Demeter Principle)
- 原则思想:一个对象应当对其他对象有尽可能少地了解,简称类间解耦, 只与你的直接朋友交谈,不跟“陌生人”说话,降低代码之间的耦合度
- 大概意思就是一个类尽量减少自己对其他对象的依赖,原则是低耦合,高内聚,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。
- 优点:低耦合,高内聚。
单一职责原则(Principle of single responsibility)
- 原则思想:一个方法只负责一件事情。
- 描述:单一职责原则很简单,一个方法 一个类只负责一个职责,各个职责的程序改动,不影响其它程序。 这是常识,几乎所有程序员都会遵循这个原则。
- 优点:降低类和类的耦合,提高可读性,增加可维护性和可拓展性,降低可变性的风险。
单例模式
1.什么是单例
保证一个类只有一个实例,并且提供一个访问该全局访问点
2.那些地方用到了单例模式
- 网站的计数器,一般也是采用单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都是单例模式实现,只有一个实例去操作才好,否则内容不好追加显示。
- 多线程的线程池的设计一般也是采用单例模式,因为线程池要方便对池中的线程进行控制
3.单例优缺点
优点:
- 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例
- 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
- 提供了对唯一实例的受控访问。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。
- 避免对共享资源的多重占用。
缺点:
- 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
- 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
4.单例模式使用注意事项:
- 使用时不能用反射模式创建单例,否则会实例化一个新的对象
- 使用懒单例模式时注意线程安全问题
- 饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)
5.单例防止反射漏洞攻击
private static boolean flag = false;
private Singleton() {
if (flag == false) {
flag = !flag;
} else {
throw new RuntimeException("单例模式被侵犯!");
}
}
public static void main(String[] args) {
}
6.如何选择单例创建方式
如果不需要延迟加载单例,可以使用枚举或者饿汉式,相对来说枚举性好于饿汉式。
如果需要延迟加载,可以使用静态内部类或者懒汉式,相对来说静态内部类好于懒韩式。
最好使用饿汉式
7.单例创建方式
- (主要使用懒汉和懒汉式)
- 饿汉式:类初始化时,会立即加载该对象,线程天生安全,调用效率高。
- 懒汉式: 类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。
- 静态内部方式:结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。
- 枚举单例: 使用枚举实现单例模式 优点:实现简单、调用效率高,枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞, 缺点没有延迟加载。
- 双重检测锁方式 (因为JVM本质重排序的原因,可能会初始化多次,不推荐使用)
1.饿汉式
饿汉式:类初始化时,会立即加载该对象,线程天生安全,调用效率高。
2.懒汉式
懒汉式: 类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。
3.静态内部类
静态内部方式:结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。
4.枚举单例式
枚举单例: 使用枚举实现单例模式 优点:实现简单、调用效率高,枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞, 缺点没有延迟加载。
package com.lijie;
//使用枚举实现单例模式 优点:实现简单、枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞 缺点没有延迟加载
public class Demo4 {
public static Demo4 getInstance() {
return Demo.INSTANCE.getInstance();
}
public static void main(String[] args) {
Demo4 s1 = Demo4.getInstance();
Demo4 s2 = Demo4.getInstance();
System.out.println(s1 == s2);
}
//定义枚举
private static enum Demo {
INSTANCE;
// 枚举元素为单例
private Demo4 demo4;
private Demo() {
System.out.println("枚举Demo私有构造参数");
demo4 = new Demo4();
}
public Demo4 getInstance() {
return demo4;
}
}
}
5.双重检测锁方式
双重检测锁方式 (因为JVM本质重排序的原因,可能会初始化多次,不推荐使用)
工厂模式
1.什么是工厂模式
- 它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。实现了创建者和调用者分离,工厂模式分为简单工厂、工厂方法、抽象工厂模式
2.工厂模式好处
- 工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。
- 利用工厂模式可以降低程序的耦合性,为后期的维护修改提供了很大的便利。
- 将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
3.为什么要学习工厂设计模式
- 不知道你们面试题问到过源码没有,你知道Spring的源码吗,MyBatis的源码吗,等等等
如果你想学习很多框架的源码,或者你想自己开发自己的框架,就必须先掌握设计模式(工厂设计模式用的是非常非常广泛的)
4.Spring开发中的工厂设计模式
1.Spring IOC
- 看过Spring源码就知道,在Spring IOC容器创建bean的过程是使用了工厂设计模式
- Spring中无论是通过xml配置还是通过配置类还是注解进行创建bean,大部分都是通过简单工厂来进行创建的。
- 当容器拿到了beanName和class类型后,动态的通过反射创建具体的某个对象,最后将创建的对象放到Map中。
2.为什么Spring IOC要使用工厂设计模式创建Bean呢
- 在实际开发中,如果我们A对象调用B,B调用C,C调用D的话我们程序的耦合性就会变高。(耦合大致分为类与类之间的依赖,方法与方法之间的依赖。)
- 在很久以前的三层架构编程时,都是控制层调用业务层,业务层调用数据访问层时,都是是直接new对象,耦合性大大提升,代码重复量很高,对象满天飞
- 为了避免这种情况,Spring使用工厂模式编程,写一个工厂,由工厂创建Bean,以后我们如果要对象就直接管工厂要就可以,剩下的事情不归我们管了。Spring IOC容器的工厂中有个静态的Map集合,是为了让工厂符合单例设计模式,即每个对象只生产一次,生产出对象后就存入到Map集合中,保证了实例不会重复影响程序效率。
5.工厂模式分类
工厂模式分为简单工厂、工厂方法、抽象工厂模式简单工厂
:用来生产同一等级结构中的任意产品。(不支持拓展增加产品)工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品) 抽象工厂
:用来生产不同产品族的全部产品。(不支持拓展增加产品;支持增加产品族)我下面来使用代码演示一下:
5.1 简单工厂模式
什么是简单工厂模式
- 简单工厂模式相当于是一个工厂中有各种产品,创建在一个类中,客户无需知道具体产品的名称,只需要知道产品类所对应的参数即可。但是工厂的职责过重,而且当类型过多时不利于系统的扩展维护。
代码演示:
单工厂的优点/缺点
- 优点:简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。
- 缺点:很明显工厂类集中了所有实例的创建逻辑,容易违反GRASPR的高内聚的责任分配原则
5.2 工厂方法模式
什么是工厂方法模式
工厂方法模式Factory
Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节
代码演示:
5.3 抽象工厂模式
什么是抽象工厂模式
抽象工厂简单地说是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来产生具体产品。
代码演示:
创建第一个子工厂,及实现类
代理模式
1.什么是代理模式
- 通过代理控制对象的访问,可以在这个对象调用方法之前、调用方法之后去处理/添加新的功能。(也就是AO的P微实现)
- 代理在原有代码乃至原业务流程都不修改的情况下,直接在业务流程中切入新代码,增加新功能,这也和Spring的(面向切面编程)很相似
2.代理模式应用场景
- Spring AOP、日志打印、异常处理、事务控制、权限控制等
3.代理的分类
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类,也称为Jdk自带动态代理)
- Cglib 、javaassist(字节码操作库)
4.三种代理的区别
- 静态代理:简单代理模式,是动态代理的理论基础。常见使用在代理模式
- jdk动态代理:使用反射完成代理。需要有顶层接口才能使用,常见是mybatis的mapper文件是代理。
- cglib动态代理:也是使用反射完成代理,可以直接代理类(jdk动态代理不行),使用字节码技术,不能对 final类进行继承。(需要导入jar包)
5.用代码演示三种代理
5.1.静态代理
什么是静态代理
- 由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
代码演示:
我有一段这样的代码:(如何能在不修改UserDao接口类的情况下开事务和关闭事务呢)
- 缺点:必须是面向接口,目标业务类必须实现接口
- 优点:不用关心代理类,只需要在运行阶段才指定代理哪一个对象
5.3.CGLIB动态代理
CGLIB动态代理原理:
- 利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
什么是CGLIB动态代理
- CGLIB动态代理和jdk代理一样,使用反射完成代理,不同的是他可以直接代理类(jdk动态代理不行,他必须目标业务类必须实现接口),CGLIB动态代理底层使用字节码技术,CGLIB动态代理不能对 final类进行继承。(CGLIB动态代理需要导入jar包)
代码演示:
建造者模式
1.什么是建造者模式
- 建造者模式:是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的方式进行创建。
- 工厂类模式是提供的是创建单个类的产品
- 而建造者模式则是将各种产品集中起来进行管理,用来具有不同的属性的产品
建造者模式通常包括下面几个角色:
- uilder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建。
- ConcreteBuilder:实现Builder接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例。
- Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
- Product:要创建的复杂对象。
2.建造者模式的使用场景
使用场景:
- 需要生成的对象具有复杂的内部结构。
- 需要生成的对象内部属性本身相互依赖。
- 与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
- JAVA 中的 StringBuilder就是建造者模式创建的,他把一个单个字符的char数组组合起来
- Spring不是建造者模式,它提供的操作应该是对于字符串本身的一些操作,而不是创建或改变一个字符串。
3.代码案例
模板方法模式
1.什么是模板方法
- 模板方法模式:定义一个操作中的算法骨架(父类),而将一些步骤延迟到子类中。
模板方法使得子类可以不改变一个算法的结构来重定义该算法的
2.什么时候使用模板方法
- 实现一些操作时,整体步骤很固定,但是呢。就是其中一小部分需要改变,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
3.实际开发中应用场景哪里用到了模板方法
- 其实很多框架中都有用到了模板方法模式
- 例如:数据库访问的封装、Junit单元测试、servlet中关于doGet/doPost方法的调用等等
4.现实生活中的模板方法
例如:
- 去餐厅吃饭,餐厅给我们提供了一个模板就是:看菜单,点菜,吃饭,付款,走人
(这里 “点菜和付款” 是不确定的由子类来完成的,其他的则是一个模板。)
5.代码实现模板方法模式
先定义一个模板。把模板中的点菜和付款,让子类来实现。
外观模式
1.什么是外观模式
- 外观模式:也叫门面模式,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。
- 它向现有的系统添加一个接口,用这一个接口来隐藏实际的系统的复杂性。
- 使用外观模式,他外部看起来就是一个接口,其实他的内部有很多复杂的接口已经被实现
2.外观模式例子
- 用户注册完之后,需要调用阿里短信接口、邮件接口、微信推送接口。
原型模式
1.什么是原型模式
- 原型设计模式简单来说就是克隆
- 原型表明了有一个样板实例,这个原型是可定制的。原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效。
- 对于简单的类当然new会更快,但是当创建一个类比较耗时,比如构造器有大量的逻辑处理耗时,次吃原型模式clone更高效。
2.原型模式的应用场景
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。这时我们就可以通过原型拷贝避免这些消耗。
- 通过new产生的一个对象需要非常繁琐的数据准备或者权限,这时可以使用原型模式。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。
- 我们Spring框架中的多例就是使用原型。
3.原型模式的使用方式
- 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
- 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此Prototype类需要将clone方法的作用域修改为public类型。
3.1原型模式分为浅复制和深复制
- (浅复制)只是拷贝了基本类型的数据,而引用类型数据,只是拷贝了一份引用地址。
- (深复制)在计算机中开辟了一块新的内存地址用于存放复制的对象。
4.代码演示
策略模式
1.什么是策略模式
- 定义了一系列的算法 或 逻辑 或 相同意义的操作,并将每一个算法、逻辑、操作封装起来,而且使它们还可以相互替换。(其实策略模式Java中用的非常非常广泛)
- 我觉得主要是为了 简化 if…else 所带来的复杂和难以维护。
2.策略模式应用场景
策略模式的用意是针对一组算法或逻辑,将每一个算法或逻辑封装到具有共同接口的独立的类中,从而使得它们之间可以相互替换。
- 例如:我要做一个不同会员打折力度不同的三种策略,初级会员,中级会员,高级会员(三种不同的计算)。
- 例如:我要一个支付模块,我要有微信支付、支付宝支付、银联支付等
3.策略模式的优点和缺点
- 优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性非常良好。
- 缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
4.代码演示
- 模拟支付模块有微信支付、支付宝支付、银联支付
观察者模式
1.什么是观察者模式
- 先讲什么是行为性模型,行为型模式关注的是系统中对象之间的相互交互,解决系统在运行时对象之间的相互通信和协作,进一步明确对象的职责。
- 观察者模式,是一种行为性模型,又叫发布-订阅模式,他定义对象之间一种一对多的依赖关系,使得当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。
2.模式的职责
- 观察者模式主要用于1对N的通知。当一个对象的状态变化时,他需要及时告知一系列对象,令他们做出相应。
实现有两种方式:
- 推:每次都会把通知以广播的方式发送给所有观察者,所有的观察者只能被动接收。
- 拉:观察者只要知道有情况即可,至于什么时候获取内容,获取什么内容,都可以自主决定。
3.观察者模式应用场景
- 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。事件多级触发场景。
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
4.代码实现观察者模式
- 定义抽象观察者,每一个实现该接口的实现类都是具体观察者。
文章就到这了,没错,没了
如果不是必要,准备上面那九个设计模式就好了,全部记住有当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时 可能不同,让子类重写来体现不同,通常考虑用模板方法模式来处理。