初识设计模式

文章参考视频

一、 设计模式简介

设计模式 是前辈们对代码开发经验的总结, 是解决特定问题的一系列套路, 他不是语法规定, 而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案
是一种思维, 一种态度, 一种进步
1995年,GoF合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了23种设计模式,从此树立了软件设计模式领域的里程碑,人称GoF设计模式

1.1 设计模式的意义

设计模式的本质是面向对象设计原则的实际运用, 是对类的封装性, 继承性和多态性以及类的关联关系和组合关系的充分理解
正确使用设计模式具有以下优点:
可以提高程序员的思维能力, 编程能力和设计能力
使程序设计更加标准化, 代码编制更加工程化, 使软件开发效率大大提高, 从而缩短软件的开发周期
使设计的代码可重用性高, 可读性强, 可靠性高, 灵活性好, 可维护性强
GoF 23
Gof23

1.2 设计模式的种类

  • 创建型模式:
    单例模式, 工厂模式, 抽象工厂模式, 建造者模式, 原型模式
  • 结构型模式:
    适配器模式, 桥接模式, 装饰模式, 组合模式, 外观模式, 享元模式, 代理模式
  • 行为型模式:
    模板方法模式, 命令模式, 迭代器模式, 观察者模式, 中介者模式, 备忘录模式, 解释器模式, 状态模式, 策略模式, 职责链模式, 访问者模式

OOP 七大原则
开闭原则: 对扩展开放, 对修改关闭
里氏替换原则: 继承必须确保超类所拥有的性质在子类中仍然成立
依赖倒置原则: 要面向接口编程, 不要面向实现编程
单一职责原则: 控制类的粒度大小, 将对象解耦, 提高其内聚性
接口隔离原则: 要为各个类建立它们需要的专用接口
迪米特法则: 只与你的直接朋友交谈,不跟"陌生人"说话
合成复用原则: 尽量先使用组合或者聚合等关联关系实现,其次才考虑使用继承关系来实现

二、 设计模式实现

2.1 单例模式

1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

  • 优点:
    在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
    避免对资源的多重占用(比如写文件操作)。
  • 缺点:
    没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

饿汉式、DCL懒汉式,深究

2.1.1 饿汉式

//饿汉式单例
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;
    }
}

2.1.2 懒汉式

存在多线程并发模式,后面的DCL懒汉式解决并发问题

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"OK");
    }

    private static LazyMan lazyMan;


    //双重检测锁模式的  懒汉式单例    DCL懒汉式
    public static LazyMan getInstance(){
        if(lazyMan==null){
            lazyMan = new LazyMan();//不是一个原子性操作
        }
        return lazyMan;
    }
    /*
    * 1.分配内存空间
    * 2、执行构造方法,初始化对象
    * 3、把这个对象指向者个空间
    *
    * 123
    * 132 A
    *
    *     B //此时lazyMan还没有完成构造
    *
    * */


    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

2.1.3 DCL懒汉式

注意:synchronized 解决并发问题,但是因为lazyMan = new LazyMan();不是原子性操作(可以分割,见代码注释),可能发生指令重排序的问题,通过volatil来解决

Java 语言提供了volatilesynchronized两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。
原子性就是指该操作是不可再分的。不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。比如 a = 1;

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"OK");
    }

    private volatile static LazyMan lazyMan;


    //双重检测锁模式的  懒汉式单例    DCL懒汉式
    public static LazyMan getInstance(){
        if(lazyMan==null){
            synchronized (LazyMan.class){//synchronized加锁解决多线程下的问题
                if(lazyMan == null){
                    lazyMan = new LazyMan();//不是一个原子性操作
                }
            }

        }
        return lazyMan;
    }
    /*
    * 1.分配内存空间
    * 2、执行构造方法,初始化对象
    * 3、把这个对象指向者个空间
    *
    * 123
    * 132 A
    *
    *     B //此时lazyMan还没有完成构造
    *
    * */


    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

静态内部类

//静态内部类
public class Holder {

    //构造器私有
    private Holder(){

    }
    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
        private static final Holder HOLDER = new Holder();

    }
}

单例不安全,反射破坏(见注释及main方法中反射破解步骤)

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

//单例懒汉式
//懒汉式单例

public class LazyMan {
    private static boolean qingjiang = false;//红绿等解决通过反射创建对象(反编译可以破解该方法)
    private LazyMan(){
        synchronized (LazyMan.class){
            if (qingjiang==false){
                qingjiang = true;
            }else{
                throw new RuntimeException("不要试图使用反射破坏单例");
            }

        }
        System.out.println(Thread.currentThread().getName()+"OK");
    }

    private volatile static LazyMan lazyMan;//volatile避免指令重排


    //双重检测锁模式的  懒汉式单例    DCL懒汉式
    public static LazyMan getInstance(){
        if(lazyMan==null){
            lazyMan = new LazyMan();//不是一个原子性操作
        }
        return lazyMan;
    }

//反射!
public static void main(String[] args) throws Exception {
    //LazyMan instance = LazyMan.getInstance();
    Field qingjiang = LazyMan.class.getDeclaredField("qingjiang");
    qingjiang.setAccessible(true);

    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);//无视私有的构造器
    LazyMan instance1 = declaredConstructor.newInstance();
    qingjiang.set(instance1,false);
    System.out.println(instance1);
    LazyMan instance2 = declaredConstructor.newInstance();

    System.out.println(instance2);

}

    /*
    * 1.分配内存空间
    * 2、执行构造方法,初始化对象
    * 3、把这个对象指向者个空间
    *
    * 123
    * 132 A
    *
    *     B //此时lazyMan还没有完成构造
    *
    * */


    //多线程并发
   /* public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }*/
}

枚举: 通过反射破解枚举发现不成功:
1、普通的反编译会欺骗开发者,说enum枚举是无参构造
2、实际enum为有参构造(见后面);
3、通过反射破解枚举会发现抛出异常

Exception in thread “main” java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417) at com.ph.single.Test.main(EnumSingle.java:19)

import java.lang.reflect.Constructor;

//enmu是什么?本身也是一个class类
public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();

        //java.lang.NoSuchMethodException: com.ph.single.EnumSingle.<init>()
        System.out.println(instance);
        System.out.println(instance2);

    }
}

通过idea和jdk自带的反编译枚举如下:
在这里插入图片描述
通过jad反编译枚举的代码如下

在这里插入图片描述

发现枚举是有参构造

2.2 工厂模式

作用:

实现了创建者和调用者的分离
详细分类

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式
  • OOP七大原则

开闭原则: 一个软件的实体应当对扩展开放, 对修改关闭
依赖倒转原则:
要针对接口编程, 不要针对实现编程
迪米特法则: 只与你直接的朋友通信, 而避免和陌生人通信

核心本质

实例化对象不使用new, 用工厂方法代替
将选择实现类, 创建对象统一管理和控制. 从而将调用者跟我们的实现类解耦

三种模式:

简单工厂模式
用来生产统一等级结构中的任意产品(对于增加新的产品,需要覆盖已有代码)
工厂方法模式
用来生产同一等级结构中的固定产品(支持增加任意产品)
抽象工厂模式
围绕一个超级工厂创建其他工厂, 该超级工厂又称为其他工厂的工厂
简单工厂模式

2.1.1 简单工程模式

Car

package com.kuang.factory.simple;

public interface Car {
    void name();
}
Tesla
package com.kuang.factory.simple;

public class Tesla implements Car {
    public void name() {
        System.out.println("特斯拉");
    }
}
WuLing
package com.kuang.factory.simple;

public class WuLing implements Car {
    public void name() {
        System.out.println("五菱宏光");
    }
}
CarFactory

package com.kuang.factory.simple;

public class CarFactory {
    //静态工厂模式
    //增加一个新的产品如果不修改代码做不到

    //方法一
    public static Car getCar(String car){
        if (car.equals("五菱宏光")){
            return new WuLing();
        }else if(car.equals("特斯拉")){
            return new Tesla();
        }else return null;
    }

    //方法二
    public static Car getWuLing(){
        return new WuLing();
    }

    public static Car getTesla(){
        return new Tesla();
    }
}
Consumer
package com.kuang.factory.simple;

public class Consumer {

    public static void main(String[] args) {
        //接口,所有实现类
        Car wuLing = new WuLing();
        Car tesla = new Tesla();
        wuLing.name();
        tesla.name();

        wuLing=CarFactory.getCar("五菱宏光");
        tesla=CarFactory.getCar("特斯拉");
        wuLing.name();
        tesla.name();


    }
}

2.1.2 工厂方法模式

Car

package com.kuang.factory.method;

public interface Car {
    void name();
}
CarFactory

package com.kuang.factory.method;

public interface CarFactory {
    //工厂方法模式
    Car getCar();
}
Tesla
package com.kuang.factory.method;

public class Tesla implements Car {
    public void name() {
        System.out.println("特斯拉");
    }
}
TeslaFactory

package com.kuang.factory.method;

public class TeslaFactory implements CarFactory {
    public Car getCar() {
        return new Tesla();
    }
}
WuLing
package com.kuang.factory.method;

public class WuLing implements Car {
    public void name() {
        System.out.println("五菱宏光");
    }
}
WuLingFactory

package com.kuang.factory.method;

public class WuLingFactory implements CarFactory {
    public Car getCar() {
        return new WuLing();
    }
}
Consumer

package com.kuang.factory.method;

import com.kuang.factory.simple.CarFactory;
import com.kuang.factory.simple.Tesla;
import com.kuang.factory.simple.WuLing;

public class Consumer {

    public static void main(String[] args) {
        //接口,所有实现类
        Car wuLing = new WuLingFactory().getCar();
        Car tesla = new TeslaFactory().getCar();
        wuLing.name();
        tesla.name();
    }
//结构复杂度: simple
//代码复杂度: simple
//编程复杂度: simple
//管理上的复杂度: simple
//舰据设计原则:工厂方法模式!
//根据实际业务:简单工厂模式!

}

2.1.3 抽象工厂模式

定义︰抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类
适用场景:
客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现
优点:
具体产品在应用层的代码隔离,无需关心创建的细节
将一个系列的产品统一到一起创建
缺点:
规定了所有可能被创建的产品集合,产品簇中扩展新的产品困难;
增加了系统的抽象性和理解难度

代码测试

IPhoneProduct

package com.kuang.factory.abstract1;

//手机产品接口
public interface IPhoneProduct {
    void start();
    void shutdown();
    void call();
    void sendMsg();
}
IRouterProduct

package com.kuang.factory.abstract1;

//路由器产品接口
public interface IRouterProduct {
    void start();
    void shutdown();
    void openWifi();
    void setting();
}
IProductFactory

package com.kuang.factory.abstract1;

//抽象产品工厂
public interface IProductFactory {
    //生产手机
    IPhoneProduct iPhoneProduct();
    //生产路由器
    IRouterProduct iRouterProduct();
}
XiaomiPhone

package com.kuang.factory.abstract1;

public class XiaomiPhone implements IPhoneProduct {
    @Override
    public void start() {
        System.out.println("小米开机");
    }

    @Override
    public void shutdown() {
        System.out.println("小米关机");
    }

    @Override
    public void call() {
        System.out.println("小米打电话");
    }

    @Override
    public void sendMsg() {
        System.out.println("小米发信息");
    }
}
XiaomiRouter

package com.kuang.factory.abstract1;

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("小米开启Wifi");
    }

    @Override
    public void setting() {
        System.out.println("小米设置路由器");
    }
}
XiaomiFactory

package com.kuang.factory.abstract1;

public class XiaomiFactory implements IProductFactory {

    @Override
    public IPhoneProduct iPhoneProduct() {
        return new XiaomiPhone();
    }

    @Override
    public IRouterProduct iRouterProduct() {
        return new XiaomiRouter();
    }
}
Client

package com.kuang.factory.abstract1;

public class Client {
    public static void main(String[] args) {
        System.out.println("=================小米系列产品==============");
        XiaomiFactory xiaomiFactory = new XiaomiFactory();
        IPhoneProduct iPhoneProduct = xiaomiFactory.iPhoneProduct();
        iPhoneProduct.start();

        IRouterProduct iRouterProduct = xiaomiFactory.iRouterProduct();
        iRouterProduct.start();
    }
}

运行结果

=================小米系列产品==============
小米开机
小米开启路由器

2.1.4 小结

简单工厂模式(静态工厂模式)
虽然某种程度上不符合设计原则, 但实际使用最多
工厂方法模式
不修改已有类的前提下, 通过增加新的工厂类实现扩展
抽象工厂模式
不可以增加产品, 可以增加产品族

JDK中Calendar的getInstance方法
JDBC中的Connection对象的获取
Spring中IOC容器创建管理bean对象
反射中Class对象的newstance方法

2.3 建造者模式

建造者模式也属于创建型模式,它提供了一种创建对象的最佳方式。

2.3.1 定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

主要作用:在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。

用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)

2.3.2 例子

工厂(建造者模式)︰负责制造汽车(组装过>程和细节在工厂内)
汽车购买者(用户)∶你只需要说出你需要的>型号(对象的类型和内容),然后直接购买就可以使用了(不需要知道汽车是怎么组装的(车轮、车门、>发动机、方向盘等等)
角色分析

既然是建造者模式,那么我们还是继续造房吧,其实我也想不到更简单的例子。假设造房简化为如下步骤:(1)地基(2)钢筋工程(3)铺电线(4)粉刷;“如果”要盖一座房子,首先要找一个建筑公司或工程承包商(指挥者)。承包商指挥工人(具体建造者)过来造房子(产品),最后验收。

2.3.3 代码实现

Product

package com.kuang.builder;

//产品,房子
public class Product {
    private String builderA;
    private String builderB;
    private String builderC;
    private String builderD;

    public String getBuilderA() {
        return builderA;
    }

    public void setBuilderA(String builderA) {
        this.builderA = builderA;
    }

    public String getBuilderB() {
        return builderB;
    }

    public void setBuilderB(String builderB) {
        this.builderB = builderB;
    }

    public String getBuilderC() {
        return builderC;
    }

    public void setBuilderC(String builderC) {
        this.builderC = builderC;
    }

    public String getBuilderD() {
        return builderD;
    }

    public void setBuilderD(String builderD) {
        this.builderD = builderD;
    }

    @Override
    public String toString() {
        return "Product{" +
                "builderA='" + builderA + '\'' +
                ", builderB='" + builderB + '\'' +
                ", builderC='" + builderC + '\'' +
                ", builderD='" + builderD + '\'' +
                '}';
    }
}
Builder

package com.kuang.builder;

//抽象的建造者:方法
public abstract class Builder {

    abstract void builderA();//地基
    abstract void builderB();//钢筋工程
    abstract void builderC();//铺电线
    abstract void builderD();//粉刷

    //完工:得到产品
    abstract Product getProduct();
}
Worker

package com.kuang.builder;

//具体的建造者:工人
public class Worker extends Builder {

    private Product product;

    public Worker() {
        product = new Product();
    }

    @Override
    void builderA() {
        product.setBuilderA("地基");
        System.out.println("地基");
    }

    @Override
    void builderB() {
        product.setBuilderB("钢筋工程");
        System.out.println("钢筋工程");
    }

    @Override
    void builderC() {
        product.setBuilderC("铺电线");
        System.out.println("铺电线");
    }

    @Override
    void builderD() {
        product.setBuilderD("粉刷");
        System.out.println("粉刷");
    }

    @Override
    Product getProduct() {
        return product;
    }
}
Director

package com.kuang.builder;

//指挥:核心,负责指挥构建一个工程,工程如何构建,由它决定
public class Director {

    //指挥工人按照顺序建房子
    public Product build(Builder builder){
        builder.builderA();
        builder.builderB();
        builder.builderC();
        builder.builderD();
        return builder.getProduct();
    }
}
Test

package com.kuang.builder;

public class Test {
    public static void main(String[] args) {
        //指挥
        Director director = new Director();
        //指挥具体的工人完成产品
        Product build = director.build(new Worker());
        System.out.println(build.toString());
    }
}

2.3.4 建造者模式的意图和适用场景(拓展)

模式意图
将一个复杂的构件与其表示相分离,使得同样的构件过程可以创建不同的表示

使用场景
需要生成的产品对象有复杂的内部结构
当一些基本部件不会变, 而其组合经常变化的时候
需要生成的对象内部属性本身相互依赖
建造者的参与者(拓展)

  • Builder
    抽象建造者,给出一个抽象接口, 以规范产品对象的各个组成成分的建造. 这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建

  • ConcreteBuilder
    实现Builder接口, 针对不同的商业逻辑, 具体化复杂对象的各部分的创建, 即具体建造者

  • Director
    指挥者, 调用具体建造者来创建复杂对象的各个部分, 在指导者不涉及具体产品的信息, 只负责保证对象各部分完整创建或者按某种顺序创建

  • Product
    要创建的复杂对象, 即产品角色

举例说明: 设计房屋

在这里插入图片描述

2.3.5 总结 (拓展)

建造者模式的使用使得产品的内部表象可独立的变化. 使用建造者模式可以使客户端不必知道产品内部组成的细节
每一个Builder都相对独立, 而与其它的Builder无关
可使得对构造过程更加精细控制
将构建代码和表示代码分开
建造者模式的缺点在于难于应付"分步骤构建算法"的需求变动

代码再实现
Product

package com.kuang.builder.demo02;

//产品:套餐
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 + '\'' +
                '}';
    }
}

Builder

package com.kuang.builder.demo02;

//建造者
public abstract class Builder {
    abstract Builder BuildA(String msg);//"汉堡";
    abstract Builder BuildB(String msg);//"可乐";
    abstract Builder BuildC(String msg);//"薯条";
    abstract Builder BuildD(String msg);//"甜点";

    abstract Product getProduct();
}

Worker

package com.kuang.builder.demo02;

public class Worker extends Builder {

    private Product product;

    public Worker() {
        product = new Product();
    }

    @Override
    Builder BuildA(String msg) {
        product.setBuildA(msg);
        return this;
    }

    @Override
    Builder BuildB(String msg) {
        product.setBuildB(msg);
        return this;
    }

    @Override
    Builder BuildC(String msg) {
        product.setBuildC(msg);
        return this;
    }

    @Override
    Builder BuildD(String msg) {
        product.setBuildD(msg);
        return this;
    }

    @Override
    Product getProduct() {
        return product;
    }
}

Test

package com.kuang.builder.demo02;

public class Test {
    public static void main(String[] args) {
        //服务员
        Worker worker = new Worker();
        //可以按默认走,也可以自由组合
        Product product = worker.BuildA("全家桶").BuildB("雪碧").getProduct();

        System.out.println(product.toString());
    }
}

运行结果

Product{BuildA='全家桶', BuildB='雪碧', BuildC='薯条', BuildD='甜点'}

2.3.6 优缺点

  • 优点:
    产品的建造和表示分离, 实现了解耦, 使用建造者模式可以使客户端不必知道产品内部组成的细节.
    将复杂产品的创建步骤分解在不同的方法中, 使得创建过程更加清晰
    具体的建造者类之间是相互独立的, 这有利于系统的扩展. 增加新的具体建造者无需修改原有的类库的代码, 符合"开闭原则"
  • 缺点:
    建造者模式所创建的产品一般具有较多的共同点,其组成部分相似; 如果产品之间的差异性很大, 则不适合使用建造者模式, 因此其使用范围受到一定的限制
    如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类实现这种变化, 导致系统变得很庞大
    应用场景以及与抽象工厂模式对比

2.4 原型模式

克隆

Prototype

Cloneable接口

clone()方法

2.4.1 代码实现

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);
    }
}

运行结果

v1=>Video{name='狂神说Java', createTime=Thu Dec 10 23:52:48 CST 2020}
v2=>Video{name='狂神说Java', createTime=Thu Dec 10 23:52:48 CST 2020}
++++++++++++++++++++++++++++++++++++++
v1=>Video{name='狂神说Java', createTime=Thu Jan 01 14:08:51 CST 1970}
v2=>Video{name='狂神说Java', createTime=Thu Jan 01 14:08:51 CST 1970}

2.4.2 原理理解

在这里插入图片描述

2.4.3 代码再实现

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);
    }
}

运行结果

v1=>Video{name='狂神说Java', createTime=Thu Dec 10 23:56:38 CST 2020}
v2=>Video{name='狂神说Java', createTime=Thu Dec 10 23:56:38 CST 2020}
+++++++++++++++++++++++++++++++++++
v1=>Video{name='狂神说Java', createTime=Thu Jan 01 14:08:51 CST 1970}
v2=>Video{name='狂神说Java', createTime=Thu Dec 10 23:56:38 CST 2020}

2.5 适配器模式

结构性模式:

作用

从程序的结构上实现松耦合, 从而可以扩大整体的类结构,用来解决更大的问题

适配器模式就像USB网线转换器

2.5.1 代码实现

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);
    }
}

2.5.2 角色分析

将一个类的接口转换成客户希望的另外一个接口. Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作
角色分析

目标接口: 客户所期待的接口, 目标可以是具体的或抽象的类, 可以是接口
需要适配的类: 需要适配的类或适配者类
适配器: 通过包装一个需要适配的对象, 把原接口转换成目标对象
原理理解

2.5.3 对象适配器优点

一个对象适配器可以把多个不同的适配者适配到同一个目标
可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据"里氏代换原则", 适配者的子类也可通过该适配器进行适配

2.5.4 类适配器缺点

对于Java, C#等不支持多重类继承的语言, 一次最多只能适配一个适配者类,不能同时适配多个适配者,
在Java, C#等语言中, 类适配器模式中的目标抽象类只能为接口, 不能为类, 其使用有一定的局限性
适用场景
系统需要使用一些现有的类, 而这些类的接口(如方法名)不符合系统的需求,甚至没有这些类的源代码
想创建一个可以重复使用的类, 用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作

2.6 桥接模式

桥接模式是将抽象部分与它的实现部分分离, 使他们都可以独立地变化, 它是一种对象结构型模式, 又称为柄体(Handle and Body)模式或接口(Interface)模式

分析:

这个场景中有两个变化的维度: 品牌, 类型

​ 代码实现大脑所想

2.6.1 代码实现

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();
    }
}

运行结果

苹果笔记本
联想台式机

2.6.2 优劣势分析

  • 好处分析:
    桥接模式偶尔类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案更好的解决方法。极大的减少了子类的个数,从而降低管理和维护的成本
    桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则,就像一座桥,可以把两个变化的维度连接起来!
  • 劣势分析:
    桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
    桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性.
  • 最佳实践:
    如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
    一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
    虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
  • 场景:
    Java语言通过Java虚拟机实现了平台的无关性。
    AWT中的Peer架构
    JDBC驱动程序也是桥接模式的应用之一。
    原理简单理解

在这里插入图片描述

2.7 代理模式

是SpringAOP的底层 [SpringAOP和SpringMVC]

2.7.1 代理模式的分类

静态代理
动态代理

2.7.2 静态代理

角色分析:

抽象角色: 一般会使用接口或抽象类来解决
真实角色: 被代理的角色
代理角色: 代理真实角色,代理真实角色后,我们一般会做一些附属操作
客户: 访问代理对象的人

  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();
    }
  1. 代理模式的好处:
    可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
    公共也就交给代理角色,实现了业务的分工
    公共业务发生扩展的时候,方便集中管理

  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();
  }
}

2.7.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. 一个动态代理 , 一般代理某一类业务
  5. 一个动态代理可以代理多个类,代理的是接口!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值