图解23种设计模式

原文章地址

创建型模式:

1、单例模式

单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

单例模式有 3 个特点:

1、单例类只有一个实例对象;
2、该单例对象必须由单例类自行创建;
3、单例类对外提供一个访问该单例的全局访问点。
单例模式的结构与实现(太简单不配图)

单例模式是设计模式中最简单的模式之一。
通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。

//单例模式推荐使用(双重检查)
public class SingletonTest {
    public static void main(String[] args) {
        System.out.println("双重检查");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);//true
        System.out.println("instance.hashCode="+instance.hashCode());
        System.out.println("instance2.hashCode="+instance2.hashCode());
    }
}

//懒汉式 线程安全
class Singleton{
    private Singleton(){};//构造方法私有化,别的类就不能去new
    private static volatile Singleton instance; //volatile作用:更新为最新的数据
    //提供一个静态公有方法,加入双重检查代码,解决线程安全问题,同时满足懒加载(用到时才加载)
    public static Singleton getInstance(){
        if (instance == null){
        	//synchronized任何线程要执行以下代码,必须获取括号内Singleton类的(锁底层是用该对象的前2个符号表示)
            synchronized (Singleton.class){
	            if (instance==null){
	              instance = new Singleton();
	            }
            }
        }
        return instance;
    }
}


//饿汉式 (可用)
public class Singleton {
    private final static Singleton INSTANCE = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return INSTANCE;
    }
}

jdk中的Runtime 类,是饿汉式的单例模式,代码如下:

public class Runtime {
    private static Runtime currentRuntime = new Runtime();
    public static Runtime getRuntime() {
        return currentRuntime;
    }
    private Runtime() {}
    
    //手动垃圾回收方法
	public native void gc();
}

为什么要双重检查

1、去掉第一个判断为空:即懒汉式(线程安全),这会导致所有线程在调用getInstance()方法的时候,直接排队等待同步锁,然后等到排到自己的时候进入同步处理时,才去校验实例是否为空,这样子做会耗费很多时间(即线程安全,但效率低下)。

2、去掉第二个判断为空:即懒汉式(线程不安全),这会出现 线程A先执行了getInstance()方法,同时线程B在因为同步锁而在外面等待,等到A线程已经创建出来一个实例出来并且执行完同步处理后,B线程将获得锁并进入同步代码,如果这时B线程不去判断是否已经有一个实例了,然后直接再new一个。这时就会有两个实例对象,即破坏了设计的初衷。(即线程不安全,效率高)

2、工厂模式(简单工厂、工厂方法)

1、简单工厂模式:
举一个客户买车的示例 【客户自己不去new车(不用关心车怎么实现的细节),而是通过静态工厂生产(new)具体车,减少硬编码】
简单工厂模式图:
在这里插入图片描述
新增类型时的图:

在这里插入图片描述

public interface Car{
    void name();
}
public class Bench implements Car{
    @Override
    public void name() {
       System.out.println("奔驰车");
    }
}
public class Baoma implements Car{
    @Override
    public void name() {
        System.out.println("宝马车");
    }  
}
//静态(简单)工厂模式
public class CarFactory {
    public static Car getCar(String car){
        if(car.equals("奔驰"))
            return new Bench();
        else if(car.equals("宝马"))
            return new Baoma();
        else
            return null;
    }
}
public class Customer {
    public static void main(String[] args) {
        Car car = CarFactory.getCar("奔驰");
        car.name();
    }
}

注:大多数情况下用的都是简单工厂模式,这种模式仍有不足,如果再增加另一种车,则会修改CarFactory.java的getCar方法,违反了开闭原则,我们应该扩展,不应该修改。

2、工厂方法模式

之前提到了简单工厂模式违背了开闭原则,而“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。

优点:
1、用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
2、灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
3、典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。

缺点:
1、类的个数容易过多,增加复杂度
2、增加了系统的抽象性和理解难度
3、抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。

模式的结构与实现:
工厂方法模式由抽象工厂、具体工厂、抽象产品和具体产品等4个要素构成。

工厂方法模式的主要角色如下:

1、抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
2、具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
3、抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
4、具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一 一对应。

在这里插入图片描述

3、抽象工厂模式

抽象工厂(AbstractFactory)模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

使用抽象工厂模式一般要满足以下条件。
1、系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
2、系统一次只可能消费其中某一族产品,即同族的产品一起使用。

抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点如下。

可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。

其缺点是:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。

在这里插入图片描述
在这里插入图片描述
代码如下:

//抽象产品工厂接口,决定生产哪些产品
public interface IProductFactory {
    //生产手机
    IPhoneProduct phoneProduct();
    //生产路由器
    IRouterProduct routerProduct();
}

//手机接口,决定手机有哪些功能
public interface IPhoneProduct {
    void start();
    void shutdowm();
    void call();
    void sendMassage();
}
//路由器接口
public interface IRouterProduct {
    void start();
    void shutdowm();
    void openWifi();
}
//小米手机
public class HuaweiPhone implements IPhoneProduct{
    @Override
    public void start() {
        System.out.println("华为开机");
    }
    @Override
    public void shutdowm() {
        System.out.println("华为关机");
    }
    @Override
    public void call() {
        System.out.println("华为打电话");
    }
    @Override
    public void sendMassage() {
        System.out.println("华为发信息");
    }
}
//小米手机
public class XiaomiPhone implements IPhoneProduct{
    @Override
    public void start() {
        System.out.println("小米开机");
    }
    @Override
    public void shutdowm() {
        System.out.println("小米关机");
    }
    @Override
    public void call() {
        System.out.println("小米打电话");

    }
    @Override
    public void sendMassage() {
        System.out.println("小米发信息");
    }
}
//华为路由
public class HuaweiRouter implements IRouterProduct {
    @Override
    public void start() {
        System.out.println("华为路由开机");
    }
    @Override
    public void shutdowm() {
        System.out.println("华为路由关机");
    }
    @Override
    public void openWifi() {
        System.out.println("华为路由开Wifi");
    }
}
//小米路由
public class XiaomiRouter implements IRouterProduct {
    @Override
    public void start() {
        System.out.println("小米路由开机");
    }
    @Override
    public void shutdowm() {
        System.out.println("小米路由关机");
    }
    @Override
    public void openWifi() {
        System.out.println("小米路由开Wifi");
    }
}
//华为工厂
public class HuaweiFactory implements IProductFactory {
    @Override
    public IPhoneProduct phoneProduct() {
        return new HuaweiPhone();
    }
    @Override
    public IRouterProduct routerProduct() {
        return new HuaweiRouter();
    }
}
//小米工厂
public class XiaomiFactory implements IProductFactory {
    @Override
    public IPhoneProduct phoneProduct() {
        return new XiaomiPhone();
    }
    @Override
    public IRouterProduct routerProduct() {
        return new XiaomiRouter();
    }
}


工厂模式的总结:一个工厂多种品牌车,多个品牌工厂多种品牌车,多个品牌工厂多种品牌车多种产品(不止有车,还有轮胎,雨刮器等产品),对应3种工厂模式

4、建造者模式

建造者(Builder)模式的定义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。

该模式的主要优点如下:
1、封装性好,构建和表示分离。
2、扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
3、客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。

其缺点如下:
1、产品的组成部分必须相同,这限制了其使用范围。
2、如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。

建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。

模式的结构

建造者(Builder)模式的主要角色如下:

1、产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
2、抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
3、具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
4、指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

其结构图如图 所示
在这里插入图片描述
在这里插入图片描述

代码如下:

//产品
public class Product {
    private String bulidA;
    private String bulidB;
    private String bulidC;
    private String bulidD;
    public void setBulidA(String bulidA) {
        this.bulidA = bulidA;
    }
    public void setBulidB(String bulidB) {
        this.bulidB = bulidB;
    }
    public void setBulidC(String bulidC) {
        this.bulidC = bulidC;
    }
    public void setBulidD(String bulidD) {
        this.bulidD = bulidD;
    }
    @Override
    public String toString() {
        return "Product{" +
                "bulidA='" + bulidA + '\'' +
                ", bulidB='" + bulidB + '\'' +
                ", bulidC='" + bulidC + '\'' +
                ", bulidD='" + bulidD + '\'' +
                '}';
    }
}
//抽象的建造者:具体需要做哪些方法,得到什么产品

public abstract class Builder {
    abstract void buildA();//地基
    abstract void buildB();//钢筋工程
    abstract void buildC();//铺电线
    abstract void buildD();//粉刷
    //完工:得到产品
    abstract Product getProduct();
}
//具体的建造者 工人,可以多种
public class Worker extends Builder {
    private Product product;
    public Worker() {
        //此处是工人去new产品,不是通过传参
        product = new Product();
    }
    @Override
    void buildA() {
        product.setBulidA("地基");
    }
    @Override
    void buildB() {
        product.setBulidB("钢筋");
    }
    @Override
    void buildC() {
        product.setBulidC("铺电线");
    }
    @Override
    void buildD() {
        product.setBulidD("粉刷");
    }
    @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 Client {
    public static void main(String[] args) {
        //指挥
        Director director = new Director();
        //指挥具体的工人完成产品
        Product product = director.build(new Worker());
        System.out.println(product.toString());
    }
}


5、原型模式

原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。。

原型模式的优点:
1、Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
2、可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

原型模式的缺点:
1、需要为每一个类都配置一个 clone 方法
2、clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
3、当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。

模式的实现
原型模式的克隆分为 浅克隆 和 深克隆 。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
浅克隆代码:

//1、实现一个接口 Cloneable
//2、重写一个方法 clone()
//原型
public class Video implements Cloneable{
    private String name;
    private Date createTime;
    public Video(String name, Date createTime) {
        this.name = name;
        this.createTime = createTime;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //默认父类的方法浅克隆
        return super.clone();
    }
     public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Video{" +
                "name='" + name + '\'' +
                ", createTime=" + createTime +
                '}';
    }
}

//客户端:浅克隆
public class Bili {
    public static void main(String[] args) throws CloneNotSupportedException {
        //原型对象 v1
        Date date = new Date();
        Video v1 = new Video("huige", date);
        System.out.println("v1=>"+v1);
        System.out.println("v1=>hash:"+v1.hashCode());
       //浅克隆:通过v1克隆出v2,内容和原来一样,但是hashCode不一样是两个不同的实例,
        //创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
        // 即都指向同一个Date空间,修改一个date后两个会一样
        Video v2 = (Video) v1.clone();
        System.out.println("v2=>"+v2);
        System.out.println("v2=>hash:"+v2.hashCode());
        System.out.println("===============================");
        v2.setName("Clone");
        date.setTime(2343443);//Date特有的方法,修改原型v1时间,v2也会变
        System.out.println(v2);
        System.out.println(v1);
    }
}

深度克隆代码:


//深克隆
public class Video implements Cloneable{
    private String name;
    private Date createTime;
//深克隆代码主要区别在这个clone函数
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object obj = super.clone();
        Video v=(Video) obj;
        //深克隆:将这个对象的属性也进行克隆
        v.createTime = (Date) this.createTime.clone();
        return obj;
    }
    public Video(String name, Date createTime) {
        this.name = name;
        this.createTime = createTime;
    }

  
    public void setName(String name) {
        this.name = name;
    }
}

//客户端:深克隆
public class Bili {
    public static void main(String[] args) throws CloneNotSupportedException {
        //原型对象 v1
        Date date = new Date();
        Video v1 = new Video("huige", date);
        System.out.println("v1=>"+v1);
        System.out.println("v1=>hash:"+v1.hashCode());

        //深克隆:通过v1克隆出v2,内容和原来一样,但是hashCode不一样是两个不同的实例,
        // 修改内容各自不影响,都指向不同Date空间
        Video v2 = (Video) v1.clone();
        System.out.println("v2=>"+v2);
        System.out.println("v2=>hash:"+v2.hashCode());
        System.out.println("===============================");
        v2.setName("Clone");
        date.setTime(2343443);//Date特有的方法,修改原型v1时间,v2不会变
        System.out.println("v1=>"+v1);
        System.out.println("v2=>"+v2);
    }
}

结构型模式

1、适配器模式

适配器模式(Adapter)的定义如下:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

类和对象结构的区别:
1、类适配器的重点在于类,是通过构造一个继承Adaptee类来实现适配器的功能;
2、对象适配器的重点在于对象,是通过在直接包含Adaptee类来实现的,当需要调用特殊功能的时候直接使用Adapter中包含的那个Adaptee对象来调用特殊功能的方法即可。

优点:
1、客户端通过适配器可以透明地调用目标接口。
2、复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
3、将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。

对象适配器代码:(常用)
就是转换器调用了网线的功能,电脑再调用转换器,电脑间接调用网线功能


//接口转换器的抽象实现 接口,为了可以造更多类型的转换器
public interface NetToUsb {
    //作用:处理连接请求, 网线=>usb
    public void handleConnet();
}

//要被适配的类:网线(想直接插只要USB的电脑,不行)
public class NetLine {
    public void connet(){
        System.out.println("连接网线上网");
    }
}

//组合NetLine (对象适配器)
public class Adapter implements NetToUsb {
    private NetLine netLine;
    public Adapter(NetLine netLine) {
        this.netLine = netLine;
    }
    @Override
    public void handleConnet() {
        netLine.connet();//可以连接上网了
    }
}
//客户端类:电脑  想上网,插不上网线
public class Computer {
    //传一个转接头过来,我们电脑才可以连上
    public void net(NetToUsb adapter){
        //上网的具体实现,找一个转接头连接
        adapter.handleConnet();
    }
    public static void main(String[] args) {
        //电脑,适配器,网线
        Computer computer = new Computer();
        NetLine netLine = new NetLine();
        Adapter adapter = new Adapter(netLine);//把网线装上
        computer.net(adapter);
    }
}

类适配器:(很少用)

//继承NetLine (类适配器)
public class Adapter2 extends NetLine implements NetToUsb {
    @Override
    public void handleConnet() {
        super.connet();//可以连接上网了
    }
}
//客户端类:电脑  想上网,插不上网线
    //类适配器
public class Computer2 {
    //传一个转接头过来,我们电脑才可以连上
    public void net(NetToUsb adapter){
        //上网的具体实现,找一个转接头
        adapter.handleConnet();
    }
    public static void main(String[] args) {
        //电脑,适配器
        Computer2 computer = new Computer2();
        Adapter2 adapter2 = new Adapter2();
        computer.net(adapter2);
    }
}

2、桥接模式

桥接(Bridge)模式的定义:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

桥接(Bridge)模式的优点:
由于抽象与实现分离,所以扩展能力强;
其实现细节对客户透明。

缺点:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度。

模式的结构桥接(Bridge)模式包含以下主要角色
1、抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
2、扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
3、实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
4、具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。

如下图:
在这里插入图片描述
普通多继承类导致的问题:

在这里插入图片描述
为解决上面这个问题引出桥接模式(把上图横向设计再组合就是桥接):

在这里插入图片描述


//品牌的接口
public interface Brand {
    void info();
}
//苹果品牌
public class Apple implements Brand {
    @Override
    public void info() {
        System.out.print("苹果");
    }
}
public class Lenovo implements Brand {
    @Override
    public void info() {
        System.out.print("联想");
    }
}

//电脑类型的抽象类
public abstract class Computer {
    //把品牌 组合进来  起到桥的作用
    protected Brand brand;//保证子类也能用,把 private 换成 protected
    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.println("台式电脑");
    }
}
//笔记本电脑
class Laptop extends Computer{
    public Laptop(Brand brand) {
        super(brand);
    }
    @Override
    public void info() {
        super.info();
        System.out.println("笔记本电脑");
    }
}
//测试
public class Client {
    public static void main(String[] args) {
        //苹果笔记本
        Computer computer = new Laptop(new Apple());
        computer.info();
        //联系台式机
        Computer computer2 = new Desktop(new Lenovo());
        computer2.info();
    }
}

3、代理模式

3.1 静态代理模式

代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

代理模式的主要优点有:

1、代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
2、代理对象可以扩展目标对象的功能;
3、代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性

其主要缺点是:

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

代理模式的主要角色如下:

1、抽象角色(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
2、真实角色(Real Subject)类:被代理的角色;实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
3、代理(Proxy)角色:代理真实角色,一般还会有自己的一些附属操作
4、客户:访问代理对象。

在这里插入图片描述
代码如下:


//租房 接口
public interface Rent {
    void rent();
}
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东要出租房子");
    }
}
public class Proxy implements Rent {
    private  Host host;
    public Proxy(Host host) {
        seeHouse();//中介特有的附属操作
        this.host = host;//调用房东租房业务
        fare();//中介特有的附属操作
    }
    @Override
    public void rent() {
        host.rent();
    }
    //中介特有的附属操作
    public void seeHouse(){
        System.out.println("中介带你看房");
    }
    //中介特有的附属操作
    public void fare(){
        System.out.println("中介收费");
    }
}
//测试客户访问代理类
public class Client {
    public static void main(String[] args) {
        //房东要租房子
        Host host = new Host();
        //代理,中介帮房东租房子,但是代理一般会有一些附属操作
        Proxy proxy = new Proxy(host);
        //你不用面对房东,直接找中介租房即可
        proxy.rent();
    }
}

深入理解静态代理的代码

//抽象接口
public interface UserService {
    void add();
    void delete();
    void update();
    void query();
}
//具体实现类,被代理
public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        //要求:在执行这些方法时,输出日志中要说明使用了这个方法。
        // 即新加一句 System.out.println("使用了add方法");
        //但如果实现类太多,全部的方法内部都要修改(但公司原则又是尽量不改原来的代码),
        // 而且代码很累赘,所以想到使用代理类
        //System.out.println("使用了add方法");
        System.out.println("增加了一个用户");
    }
    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }
    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }
    @Override
    public void query() {
        System.out.println("查询了一个用户");
    }
}
//代理
public class UserServiceProxy implements UserService {
        private UserServiceImpl userService;

        //springboot 推荐用set注入 而不用构造函数
        public void setUserService(UserServiceImpl userService) {
            this.userService = userService;
        }
        //自己的附属操作:日志方法
        public void log(String mesg){
            System.out.println("使用了"+mesg+"方法");
        }
        @Override
        public void add() {
            log("add");
            userService.add();
        }
        @Override
        public void delete() {
            log("delete");
            userService.delete();
        }
    @Override
    public void update() {
        log("update");
        userService.update();
    }
    @Override
    public void query() {
        log("query");
        userService.query();
    }
}
//客户
public class Client {
    public static void main(String[] args) {
        //不引入代理类时直接访问的代码
       // UserServiceImpl userService = new UserServiceImpl();
        //userService.add();

        //引入代理类时访问代理对象的代码
        UserServiceImpl userService = new UserServiceImpl();
        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);//传入真实要代理的对象
        proxy.add();
    }
}

3.2 动态代理(角色不变)

java动态代理是Spring AOP 的核心技术。这种动态代理也是jdk的一个特性。
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

Proxy:提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。

InvocationHandler:需新建一个类去实现这个接口,配置好要代理的目标后,重写它的invoke方法

动态代理代码:(抽象接口和具体实现类代码与上相同)

//新建 代理类调用程序处理器
// InvocationHandler:调用程序处理器。
// 用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }
    
    //Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类
    //Proxy生成得到代理类,需三个参数
    //1、代理类调用程序处理器(即本类)的类加载器
    //2、被代理的(真实对象实现的)接口(自动生成的代理类就会是实现了这个接口);
    //3、实现了InvocationHandler接口的类(即本类)
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }
    //处理代理实例,并返回结果
    //proxy - 调用该方法的代理实例
    //method -所述方法对应于调用代理实例上的接口方法的实例。
    //args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());//动态获得方法名
        //动态代理的本质,就是使用反射机制实现!
        Object result = method.invoke(target, args);
        return result;
    }
    public void log(String mesg){
        System.out.println("执行了"+mesg+"方法");
    }
}
//客户
public class Client {
    public static void main(String[] args) {
        //真实角色
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        //代理角色,现在还没有,
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //设置要代理谁
        pih.setTarget(userServiceImpl);
        //通过调用ProxyInvocationHandler的getProxy()生成一个代理对象,而且是实现了UserService接口的
        UserService proxy = (UserService) pih.getProxy();//这里的 proxy 代理类是被动态生成的

        proxy.add();
    }
}

springAOP 核心就是动态代理模式

在这里插入图片描述

4、装饰模式

装饰(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态的将新功能 附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则。

装饰(Decorator)模式的主要优点有:
采用装饰模式扩展对象的功能比采用继承方式更加灵活。
可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。

其主要缺点:装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。

装饰模式的结构与实现
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。

装饰模式主要包含以下角色:

抽象构件(Component)角色:定义一个抽象接口或类以规范准备接收附加责任的对象。
具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

在这里插入图片描述
客户测试代码(其余代码太多不放)

public class Client {
    public static void main(String[] args) {
        //装饰者模式下订单:2份巧克力和一份牛奶的LongBlack 咖啡
        //只要把对象的参数在原来的基础上再传一次就好了
        //1、先点一份LongBlack
        Drink order = new LongBlack();
        //2、加一份牛奶
        order = new Milk(order);
        //3、再原来基础上再加一份巧克力
        order = new Chocolate(order);
        //4、再原来基础上再加另一份巧克力
        order = new Chocolate(order);
    }
}

java IO 中用到的装饰者模式:

在这里插入图片描述

public class JavaIO {
    public static void main(String[] args) throws IOException {
        DataInputStream dis = new DataInputStream(new FileInputStream("c:\\a.txt"));
        System.out.println(dis.read());
        dis.close();
    }
}

5、组合模式

组合(Composite)模式的定义:有时又叫作 部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。

组合模式的主要优点有:

1、组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
2、更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

其主要缺点是:

1、设计较复杂,客户端需要花更多时间理清类之间的层次关系;
2、不容易限制容器中的构件;
3、不容易用继承的方法来增加构件的新功能;

组合模式包含以下主要角色。
抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。
树枝构件(Composite)角色:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

例子(一个大学包含很多学院,一个学院包含多种专业)

在这里插入图片描述
Java HashMap 应用的组合模式分析图:

在这里插入图片描述
在这里插入图片描述

6、外观模式

外观(Facade)模式定义:外观模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

通常只要是高层模块需要调度多个子系统,我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。

外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点:

1、降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
2、对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
3、降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

外观(Facade)模式的主要缺点如下。

1、不能很好地限制客户使用子系统类,很容易带来未知风险。
2、增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

外观模式的角色:

外观类(Facade): 为调用端提供统一的调用接口, 外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
调用者(Client): 外观接口的调用者
子系统的集合:指模块或者子系统,处理Facade 对象指派的任务,他是功能的实际提供者

在这里插入图片描述
子系统内部也可存在复杂调用关系:

myBatis中外观模式源码分析:
在这里插入图片描述
上图的角色分析:
在这里插入图片描述

7、享元模式

享元(Flyweight)模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

其主要缺点是:为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
读取享元模式的外部状态会使得运行时间稍微变长。

享元模式中存在以下两种状态:
内部状态,即不会随着环境的改变而改变的可共享部分;
外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。

享元模式的主要角色有如下:
1、抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
2、具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
3、非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
4、享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

享元模式基本介绍:

享元模式(Flyweight Pattern) 也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象
常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在
这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
享元模式能够解决 重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需
总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
享元模式 经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式
的应用,享元模式是池技术的重要实现方式

业务要求:
给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:

有客户要求以新闻的形式发布
有客户人要求以博客的形式发布
有客户希望以微信公众号的形式发布

在这里插入图片描述
代码如下:

//抽象享元
public abstract class WebSite {
    public  abstract void use(User user);
}
//具体享元
public class ConcareteWebSite extends WebSite {
    //共享的部分,内部状态,同一种type就会被共享(比如都是博客类型)
    private String type = "";//网站发布的形式(类型)

    public ConcareteWebSite(String type) {
        this.type = type;
    }
    @Override
    public void use(User user ) {
        System.out.println("网站的发布形式为:"+type+"  使用者:"+user.getName());
    }
}
//网站工厂类,根据需求返回一个具体网站类型
public class WebSiteFactory {
    //集合,充当池使用
    private HashMap<String,ConcareteWebSite> pool = new HashMap<>();

    //根据网站类型返回一个网站,如果没有就创建一个,放入池中,并返回
    public WebSite getWebSiteCategory(String type){
        if (!pool.containsKey(type)){
            pool.put(type,new ConcareteWebSite(type));
        }
        return (WebSite) pool.get(type);
    }
    //获取网站类型的总数
    public int getWebSiteCount(){
        return pool.size();
    }
}
//非享元类
public class User {
    private String name;
    public User(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
//客户测试
public class Client {
    public static void main(String[] args) {
        //创建一个工厂
        WebSiteFactory factory = new WebSiteFactory();
        //客户要一个新闻形式发布的网站
        WebSite webSite1 = factory.getWebSiteCategory("新闻");
        webSite1.use(new User("tom"));
        //客户要一个博客形式发布的网站
        WebSite webSite2 = factory.getWebSiteCategory("博客");
        webSite1.use(new User("moti"));
        //客户要一个博客形式发布的网站,已经创建过了,去工厂池共享拿来就行
        WebSite webSite3 = factory.getWebSiteCategory("博客");
        webSite1.use(new User("kiti"));
        //输出创建的网站类型个数
        System.out.println(factory.getWebSiteCount());//2
    }
}


String池 享元原理(虽然引用不同,但拿到的是同一个hello对象)
在这里插入图片描述

valueOf源码:(low为-128,high为127)
在这里插入图片描述

在这里插入图片描述valueOf 和String池 享元模式源码测试:

public class JdkInteger {
    public static void main(String[] args) {
        Integer x = Integer.valueOf(127);
        Integer y = Integer.valueOf(127);
        Integer z = new Integer(127);
        Integer k = new Integer(127);

        System.out.println(x.equals(z));//true  因为比较的是数值大小
        System.out.println(x==y);//true 因为源码里数值-128到127内,如果创建过对象,会共享,是同个对象
        System.out.println(x==z);//false
        System.out.println(z==k);//false

        Integer a = Integer.valueOf(128);
        Integer b = Integer.valueOf(128);
        //源码中超过127 就new一个对象,所以他们不是用一个
        System.out.println(a==b);//false 

        String s = "hello";
        String str = new String("hello");
        //hashCode 通过将对象的内部地址(String池里的hello对象)转换为整数。
        System.out.println(s.hashCode()==str.hashCode());//true
        System.out.println(s==str);//false
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目 录 序言 前言 读者指南 第1章 引言 1 1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象接口 9 1.6.4 描述对象的实现 10 1.6.5 运用复用机制 13 1.6.6 关联运行时刻和编译时刻的 结构 15 1.6.7 设计应支持变化 16 1.7 怎样选择设计模式 19 1.8 怎样使用设计模式 20 第2章 实例研究:设计一个文档编 辑器 22 2.1 设计问题 23 2.2 文档结构 23 2.2.1 递归组合 24 2.2.2 图元 25 2.2.3 组合模式 27 2.3 格式化 27 2.3.1 封装格式化算法 27 2.3.2 Compositor和Composition 27 2.3.3 策略模式 29 2.4 修饰用户界面 29 2.4.1 透明围栏 29 2.4.2 Monoglyph 30 2.4.3 Decorator 模式 32 2.5 支持多种视感标准 32 2.5.1 对象创建的抽象 32 2.5.2 工厂类和产品类 33 2.5.3 Abstract Factory模式 35 2.6 支持多种窗口系统 35 2.6.1 我们是否可以使用Abstract Factory 模式 35 2.6.2 封装实现依赖关系 35 2.6.3 Window和WindowImp 37 2.6.4 Bridge 模式 40 2.7 用户操作 40 2.7.1 封装一个请求 41 2.7.2 Command 类及其子类 41 2.7.3 撤消和重做 42 2.7.4 命令历史记录 42 2.7.5 Command 模式 44 2.8 拼写检查和断字处理 44 2.8.1 访问分散的信息 44 2.8.2 封装访问和遍历 45 2.8.3 Iterator类及其子类 46 2.8.4 Iterator模式 48 2.8.5 遍历和遍历过程中的动作 48 2.8.6 封装分析 48 2.8.7 Visitor 类及其子类 51 2.8.8 Visitor 模式 52 2.9 小结 53 第3章 创建型模式 54 3.1 Abstract Factory(抽象工厂)— 对象创建型模式 57 3.2 Builder(生成器)—对象创建型 模式 63 3.3 Factory Method(工厂方法)— 对象创建型模式 70 3.4 Prototype(原型)—对象创建型 模式 87 3.5 Singleton(单件)—对象创建型 模式 84 3.6 创建型模式的讨论 89 第4章 结构型模式 91 4.1 Adapter(适配器)—类对象结构型 模式 92 4.2 Bridge(桥接)—对象结构型 模式 100 4.3 Composite(组成)—对象结构型 模式 107 4.4 Decorator(装饰)—对象结构型 模式 115 4.5 FACADE(外观)—对象结构型 模式 121 4.6 Flyweight(享元)—对象结构型 模式 128 4.7 Proxy(代理)—对象结构型 模式 137 4.8 结构型模式的讨论 144 4.8.1 Adapter与Bridge 144 4.8.2 Composite、Decorator与Proxy 145 第5章 行为模式 147 5.1 CHAIN OF RESPONSIBIL ITY(职责链) —对象行为型模式 147 5.2 COMMAND(命令)—对象行为型 模式 154 5.3 INTERPRETER(解释器)—类行为型 模式 162 5.4 ITERATOR(迭代器)—对象行为型 模式 171 5.5 MEDIATOR(中介者)—对象行为型 模式 181 5.6 MEMENTO(备忘录)—对象行为型 模式 188 5.7 OBSERVER(观察者)—对象行为型 模式 194 5.8 STATE(状态)—对象行为型模式 201 5.9 STRATEGY(策略)—对象行为型 模式 208 5.10 TEMPLATE METHOD(模板方法) —类行为型模式 214 5.11 VISITOR(访问者)—对象行为型 模式 218 5.12 行为模式的讨论 228 5.12 1 封装变化 228 5.12.2 对象作为参数 228 5.12.3 通信应该被封装还是被分布 229 5.12.4 对发送者和接收者解耦 229 5.12.5 总结 231 第6章 结论 232 6.1 设计模式将带来什么 232 6.2 一套通用的设计词汇 232 6.3 书写文档和学习的辅助手段 232 6.4 现有方法的一种补充 233 6.5 重构的目标 233 6.6 本书简史 234 6.7 模式界 235 6.8 Alexander 的模式语言 235 6.9 软件中的模式 236 6.10 邀请参与 237 6.11 临别感想 237 附录A 词汇表 238 附录B 图示符号指南 241 附录C 基本类 244 参考文献 249
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值