常用设计模式的学习

2023-05-16

1、设计模式

1.1概述:

软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。

1.2学习设计模式的必要性

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。

正确使用设计模式具有以下优点。

  • 可以提高程序员的思维能力、编程能力和设计能力。
  • 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
  • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

1.3设计模式分类

  • 创建型模式

    用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF(四人组)书中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。

  • 结构型模式

    用于描述如何将类或对象按某种布局组成更大的结构,GoF(四人组)书中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。

  • 行为型模式

    用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。GoF(四人组)书中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。

1.4OOP七大原则

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

2、创建型模式

创建型模式的主要关注点是怎样创建对象?,它的主要特点是“将对象的创建与使用分离”。

这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。

创建型模式分为:

  • 单例模式
  • 工厂方法模式
  • 抽象工程模式
  • 原型模式
  • 建造者模式

2.1单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

2.1.1 单例模式的结构

单例模式的主要有以下角色:

  • 单例类。只能创建一个实例的类
  • 访问类。使用单例类

2.1.2 单例模式的实现

单例设计模式分类两种:

​ 饿汉式:类加载就会导致该单实例对象被创建

​ 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

1.饿汉式-方式1(静态变量方式)

/**
 * 饿汉式
 *      静态变量创建类的对象
 */
public class Singleton {
    //私有构造方法
    private Singleton() {}

    //在成员位置创建该类的对象
    private static Singleton instance = new Singleton();

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return instance;
    }
}

说明:

​ 该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

2.饿汉式-方式2(静态代码块方式)

/**
 * 恶汉式
 *      在静态代码块中创建该类对象
 */
public class Singleton {

    //私有构造方法
    private Singleton() {}

    //在成员位置创建该类的对象
    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return instance;
    }
}

说明:

​ 该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本上一样,当然该方式也存在内存浪费问题。

3.DCL赖汉式

添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

/**
 * DCL 赖汉式 (双重检测锁+volatile)
 */
public class Singleton {
    //构造方法私有
    private Singleton(){}

    //在成员位置创建该类的对象
    private static volatile Singleton instance;

    //对外提供方法获取该对象
    public static Singleton getInstance(){
        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
        if (instance == null){
            synchronized (Singleton.class){
                //抢到锁之后再次判断是否为空
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

4.静态内部类的方式

静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。

/**
 * 静态内部类的方式
 */
public class Singleton {
    //私有构造方法
    private Singleton(){}

    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    //对外提供静态方法获取该对象
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

说明:

​ 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。

小结:

​ 静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

5.枚举方式

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

/**
 * 枚举方式
 */
public enum Singleton {
    INSTANCE;
}

说明:

​ 枚举方式属于恶汉式方式。

2.1.3 存在的问题

2.1.3.1 问题演示

破坏单例模式:

使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射。

序列化反序列化

Singleton类:

public class Singleton implements Serializable {
    //私有构造方法
    private Singleton(){}

    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    //对外提供静态方法获取该对象
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

Test类:

public class Test {
    public static void main(String[] args) {
        //writeObjectToFile();
        Singleton read = (Singleton)read();
        Singleton  read2 = (Singleton)read();
        System.out.println(read);
        System.out.println(read2);

    }

    public static Object read(){
        Singleton singleton = null;
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("D:\\微信\\a.txt"));
            singleton = (Singleton)ois.readObject();
            return singleton;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                return singleton;
            }

        }
    }

    public static void writeObjectToFile(){
        Singleton instance = Singleton.getInstance();
        ObjectOutputStream oos = null;
        try {
         oos = new ObjectOutputStream(new FileOutputStream("D:\\微信\\a.txt"));
            oos.writeObject(instance);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

上面代码运行结果是false,表明序列化和反序列化已经破坏了单例设计模式。

反射

Singleton类:

public class Singleton {

    //私有构造方法
    private Singleton() {}
    
    private static volatile Singleton instance;

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {

        if(instance != null) {
            return instance;
        }

        synchronized (Singleton.class) {
            if(instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}

Test类:

public class Test {
    public static void main(String[] args) throws Exception {
        //获取Singleton类的字节码对象
        Class clazz = Singleton.class;
        //获取Singleton类的私有无参构造方法对象
        Constructor constructor = clazz.getDeclaredConstructor();
        //取消访问检查
        constructor.setAccessible(true);

        //创建Singleton类的对象s1
        Singleton s1 = (Singleton) constructor.newInstance();
        //创建Singleton类的对象s2
        Singleton s2 = (Singleton) constructor.newInstance();

        //判断通过反射创建的两个Singleton对象是否是同一个对象
        System.out.println(s1 == s2);
    }
}

注意:枚举方式不会出现这两个问题。

2.1.3.2 问题的解决

  • 序列化、反序列方式破坏单例模式的解决方法

在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。

Singleton类:

public class Singleton implements Serializable {

    //私有构造方法
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    /**
     * 下面是为了解决序列化反序列化破解单例模式
     */
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

  • 反射方式破解单例的解决方法
public class Singleton {

    //私有构造方法
    private Singleton() {
        /*
           反射破解单例模式需要添加的代码
        */
        if(instance != null) {
            throw new RuntimeException();
        }
    }
    
    private static volatile Singleton instance;

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {

        if(instance != null) {
            return instance;
        }

        synchronized (Singleton.class) {
            if(instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}

Runtime类就是使用的单例设计模式。

2.2工厂模式

概述

作用

  • 实现了创建者和调用者的分离
    • 实例化对象不使用new,用工厂方法代替
    • 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
  • 详细分类
    • 简单工厂模式
      • 用来生产同一等级结构中的任意产品(对于增加新的产品,需要覆盖已有代码)
    • 工厂方法模式
      • 用来生产同一等级结构中的固定产品(支持增加任意产品)
    • 抽象工厂模式
      • 围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
  • OOP七大原则:
    • 开闭原则
    • 依赖倒转原则
    • 迪米特法则

2.2.1简单工厂模式

简单工厂不是一种设计模式,反而比较像是一种编程习惯。

结构

简单工厂包含如下角色:

  • 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品 :实现或者继承抽象产品的子类
  • 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。

实现

工厂类代码如下:

public class CarFactory {
    public  Car getCar(String car){
        if (car.equals("五菱")){
            return new WuLing();
        } else if (car.equals("特斯拉")) {
            return new Tesla();
        }else {
            return null;
        }
    }
}

工厂(factory)处理创建对象的细节,一旦有了CarFactoryCarStore类中的getCar()就变成此对象的客户,后期如果需要Car对象直接从工厂中获取即可。这样也就解除了和Car实现类的耦合,同时又产生了新的耦合,CarStore对象和CarFactory工厂对象的耦合,工厂对象和商品对象的耦合。

后期如果再加新品种的车子,我们势必要需求修改CarFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。

优缺点

优点:

封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。

缺点:

增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。

扩展

静态工厂

在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是23种设计模式中的。代码如下:

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

2.2.2工厂方法模式

针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。

概念

定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。

结构

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

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

抽象工厂:

public interface CarFactory {
    Car getCar();
}

具体工厂:

public class TeslaFactory implements CarFactory{
    @Override
    public Car getCar() {
        return new Tesla();
    }
}

public class WulLingFactory implements CarFactory{
    @Override
    public Car getCar() {
        return new WuLing();
    }
}

消费者类

public class Consumer {
    public static void main(String[] args) {
        Car car = new WulLingFactory().getCar();
        Car car2 = new TeslaFactory().getCar();
        car.name();
        car2.name();
    }
}

从以上的编写的代码可以看到,要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点。

工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。

优缺点

优点:

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

缺点:

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

2.2.3抽象工厂模式

概念

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

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

结构

抽象工厂模式的主要角色如下:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
  • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。

实现

抽象工厂:

public interface MyFactory {
    Phone getPhone();
    Computer getComputer();
}

具体工厂:

public class HuaWeiFactory implements MyFactory {
    @Override
    public Phone getPhone() {
        return new HuaWeiPhone();
    }

    @Override
    public Computer getComputer() {
        return new HuaWeiComputer();
    }
}


public class MiUiFactory implements MyFactory {
    @Override
    public Phone getPhone() {
        return new MiUiPhone();
    }

    @Override
    public Computer getComputer() {
        return new MiUiComputer();
    }
}

如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。

优缺点

优点:

当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点:

当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。

使用场景

  • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。

  • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。

  • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。

如:输入法换皮肤,一整套一起换。生成不同操作系统的程序。

2.2.4模式扩展

简单工厂+配置文件解除耦合

可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。

第一步:定义配置文件

为了演示方便,我们使用properties文件作为配置文件,名称为bean.properties

tesla=com.wdzl.factory.config_factory.Tesla
wuling=com.wdzl.factory.config_factory.WuLing

第二步:改进工厂类

public class CarFactory {
    //加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储
    //1,定义容器对象存储咖啡对象
    private static Map<String,Car> map = new HashMap<>();

    //2.加载配置文件,只需要加载一次
    static {
        //2.1创建Properties对象
        Properties properties = new Properties();
        //2.2对配置文件进行加载
        InputStream is = CarFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            properties.load(is);
            //从集合中获取全类名并创建对象
             Set<Object> keys = properties.keySet();
            for (Object key : keys) {
                String className = properties.getProperty((String) key);
                 Class clazz = Class.forName(className);
                Car car = (Car) clazz.newInstance();
                map.put((String)key,car);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Car createCar(String name){
        return map.get(name);
    }
}

静态成员变量用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需要执行一次。

2.2.5Java中的使用

Collection.iterator方法

在这里插入图片描述

Collection接口是抽象工厂类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。

另:

​ 1.DateForamt类中的getInstance()方法使用的是工厂模式;

​ 2.Calendar类中的getInstance()方法使用的是工厂模式;

2.3原型模式

概述

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

结构

原型模式包含如下角色:

  • 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
  • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

实现

原型模式的克隆分为浅克隆和深克隆。

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

Java中的Object类中提供了 clone() 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。代码如下:

public class Realizetype implements Cloneable{

    public Realizetype(){
        System.out.println("具体的原型对象已经创建");
    }


    @Override
    protected Realizetype clone() throws CloneNotSupportedException {
        System.out.println("具体原型复制成功!");
        return (Realizetype) super.clone();
    }
}

PrototypeTest(测试访问类):

public class Test {
    public static void main(String[] args) {
        Realizetype realizetype = new Realizetype();
        try {
            Realizetype clone = realizetype.clone();
            System.out.println("具体对象和克隆是同一个对象?" + (realizetype == clone));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

案例

用原型模式生成“三好学生”奖状

具体原型类:

public class Citation implements Cloneable{
    private String name;

    public String getName() {
        return name;
    }

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

    public void show(){
        System.out.println(this.name+"同学:在这一年表现优秀,被评为三好学生");
    }

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }
}

测试类:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation citation = new Citation();
        citation.setName("张三");

        Citation clone = citation.clone();
        clone.setName("李四");

        citation.show();
        clone.show();
    }
}

使用场景

  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
  • 性能和安全要求比较高。

扩展(深克隆)

将上面的“三好学生”奖状的案例中Citation类的name属性修改为Student类型的属性。代码如下:

//具体圆形类
public class Citation implements Cloneable , Serializable {
    private Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public void show(){
        System.out.println(this.student.getName()+"同学:在这一年表现优秀,被评为三好学生");
    }

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }
}

说明

浅克隆克隆的对象的应用指向原有属性所指向的对象的内存地址。对具体原型类(Citation)中的引用类型的属性进行引用的复制。这种情况需要使用深克隆,而进行深克隆需要使用对象流。

//测试类
public class Test {
    public static void main(String[] args) throws Exception {
        Citation citation = new Citation();
        Student s = new Student();
        s.setName("张三");
        citation.setStudent(s);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\微信\\a.txt"));
        oos.writeObject(citation);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\微信\\a.txt"));
        Citation clone = (Citation) ois.readObject();
        clone.getStudent().setName("李四");

        citation.show();
        clone.show();
    }
}

注意:Citation类和Student类必须实现Serializable接口,否则会抛NotSerializableException异常。

2.4建造者模式

概述

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

在这里插入图片描述

  • 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况
  • 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
  • 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

结构

建造者(Builder)模式包含如下角色:

  • 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。

  • 具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。

  • 产品类(Product):要创建的复杂对象。

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

实例

创建共享单车

生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。
在这里插入图片描述

这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体的建造者;Director是指挥者。

产品类:

//自行车类
public class Bike {
    private String frame;
    private String seat;

    public String getFrame() {
        return frame;
    }

    public void setFrame(String frame) {
        this.frame = frame;
    }

    public String getSeat() {
        return seat;
    }

    public void setSeat(String seat) {
        this.seat = seat;
    }

}

抽象建造者类:

// 抽象 builder 类
public abstract class Builder {
    protected Bike bike = new Bike();

    public abstract void buildFrame();
    public abstract void buildSeat();
    public abstract Bike createBike();

}

具体建造者类:

//哈喽单车Builder类
public class HelloBuilder extends Builder {
    @Override
    public void buildFrame() {
        bike.setFrame("铝合金的车架");
    }

    @Override
    public void buildSeat() {
        bike.setSeat("貂皮车座");
    }

    @Override
    public Bike createBike() {
        return bike;
    }
}

//摩拜单车Builder类
public class MobikeBuilder extends Builder{
    @Override
    public void buildFrame() {
        bike.setFrame("铁做的车架");
    }

    @Override
    public void buildSeat() {
        bike.setSeat("真皮车座");
    }

    @Override
    public Bike createBike() {
        return bike;
    }
}

指挥者类:

//指挥者类
public class Director {
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }
    public Bike construct(){
        builder.buildFrame();
        builder.buildSeat();
        return builder.bike;
    }
}

注意:

上面示例是 Builder模式的常规用法,指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合

// 抽象 builder 类
public abstract class Builder {

    protected Bike mBike = new Bike();

    public abstract void buildFrame();
    public abstract void buildSeat();
    public abstract Bike createBike();
    
    public Bike construct() {
        this.buildFrame();
        this.BuildSeat();
        return this.createBike();
    }
}

说明:

这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,也不是太符合单一职责原则,如果construct() 过于复杂,建议还是封装到 Director 中。

优缺点

优点:

  • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
  • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。

缺点:

建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

模式扩展

建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。

public class Phone {
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;

    private Phone(Builder builder){
        this.cpu = builder.B_cpu;
        this.mainboard= builder.B_mainboard;
        this.memory = builder.B_memory;
        this.screen = builder.B_memory;
    }

    public static final class Builder{
        private String B_cpu;
        private String B_screen;
        private String B_memory;
        private String B_mainboard;

        public Builder() {
        }

        public Builder setB_cpu(String b_cpu) {
            B_cpu = b_cpu;
            return this;
        }

        public Builder setB_screen(String b_screen) {
            B_screen = b_screen;
            return this;
        }

        public Builder setB_memory(String b_memory) {
            B_memory = b_memory;
            return this;
        }

        public Builder setB_mainboard(String b_mainboard) {
            B_mainboard = b_mainboard;
            return this;
        }

        public Phone build(){
            return new Phone(this);
        }
    }

    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}

public class Test {
    public static void main(String[] args) {
        Phone phone = new Phone.Builder()
                .setB_cpu("intel")
                .setB_mainboard("华硕")
                .setB_memory("金士顿")
                .setB_screen("三星")
                .build();
        System.out.println(phone);
    }
}

重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。

2.5 创建型模式对比

2.5.1 工厂方法模式VS建造者模式

工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

2.5.2 抽象工厂模式VS建造者模式

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。

建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

3、结构型模式

结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

结构型模式分为以下 7 种:

  • 代理模式
  • 适配器模式
  • 装饰者模式
  • 桥接模式
  • 外观模式
  • 组合模式
  • 享元模式

3.1 代理模式

3.1.1 概述

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

Java中的代理按照代理类生成时机不同又分为静态代理动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理CGLib代理两种。

3.1.2 结构

代理(Proxy)模式分为三种角色:

  • 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

3.1.3 静态代理

我们通过案例来感受一下静态代理。

【例】火车站卖票

如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。

代码如下:

//抽象主题(Subject)类
public interface SellTickets {
    void sell();
}

//真实主题(Real Subject)类
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火车站买票");
    }
}

//代理(Proxy)类
public class ProxyPoint implements SellTickets {

    //引用火车类
    private TrainStation trainStation = new TrainStation();

    @Override
    public void sell() {
        System.out.println("受点服务费");
        trainStation.sell();
    }
}


public class Test {
    public static void main(String[] args) {
        ProxyPoint point = new ProxyPoint();
        point.sell();
    }
}

从上面代码中可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强(代理点收取一些服务费用)。

3.1.4 JDK动态代理

我们使用动态代理实现上面案例,先使用JDK提供的动态代理。Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。

//卖票接口
public interface SellTickets {
    void sell();
}

//卖票接口
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火车站买票");
    }
}

//代理工厂,用来创建代理对象
public class ProxyFactory {
    //声明目标对象
    private TrainStation trainStation = new TrainStation();

    //获取代理对象
    public SellTickets getProxyPoint(){
        //使用Proxy获取代理对象
        /**
         * newProxyInstance()方法参数说明:
         * ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的加载器即可
         * Class<?>[] interfaces : 真实对象所实现的接口,代理模式的真实对象和代理对象实现相同的接口
         * InvocationHandler h  : 代理对象的调用处理程序
         */
        SellTickets sellTickets  = (SellTickets) Proxy.newProxyInstance(trainStation.getClass().getClassLoader(),
                trainStation.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     *  InvocationHandler中invoke方法参数说明:
                     * @param proxy   代理对象
                     * @param method  对应于在代理对象上调用的接口方法的Method实例
                     * @param args    代理对象调用接口方法时传递的实际参数
                     * @return    代理对象调用接口方法时,接口的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //代理对象经行方法的增强
                        System.out.println("代理点收点幸苦费(JDK)");
                        //执行真实对象
                        final Object invoke = method.invoke(trainStation, args);
                        return invoke;
                    }
                });
        return sellTickets;
    }
}

//测试
public class Test {
    public static void main(String[] args) {
        //创建代理工厂的对象
        ProxyFactory factory = new ProxyFactory();
        //生产代理对象
        SellTickets proxyPoint = factory.getProxyPoint();

        proxyPoint.sell();
    }
}

使用了动态代理,我们思考下面问题:

  • ProxyFactory是代理类吗?

    ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构:

    package com.sun.proxy;
    
    import com.itheima.proxy.dynamic.jdk.SellTickets;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy0 extends Proxy implements SellTickets {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
                return;
            }
            catch (NoSuchMethodException noSuchMethodException) {
                throw new NoSuchMethodError(noSuchMethodException.getMessage());
            }
            catch (ClassNotFoundException classNotFoundException) {
                throw new NoClassDefFoundError(classNotFoundException.getMessage());
            }
        }
    
        public final boolean equals(Object object) {
            try {
                return (Boolean)this.h.invoke(this, m1, new Object[]{object});
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final String toString() {
            try {
                return (String)this.h.invoke(this, m2, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final int hashCode() {
            try {
                return (Integer)this.h.invoke(this, m0, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final void sell() {
            try {
                this.h.invoke(this, m3, null);
                return;
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    }
    
    

    从上面的类中,我们可以看到以下几个信息:

    • 代理类($Proxy0)实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口。
    • 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。
  • 动态代理的执行流程是什么样?

    下面是摘取的重点代码:

    //程序运行过程中动态生成的代理类
    public final class $Proxy0 extends Proxy implements SellTickets {
        private static Method m3;
    
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    
        static {
            m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
        }
    
        public final void sell() {
            this.h.invoke(this, m3, null);
        }
    }
    
    //Java提供的动态代理相关类
    public class Proxy implements java.io.Serializable {
    	protected InvocationHandler h;
    	 
    	protected Proxy(InvocationHandler h) {
            this.h = h;
        }
    }
    
    //代理工厂类
    public class ProxyFactory {
    
        private TrainStation station = new TrainStation();
    
        public SellTickets getProxyObject() {
            SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                    station.getClass().getInterfaces(),
                    new InvocationHandler() {
                        
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                            System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
                            Object result = method.invoke(station, args);
                            return result;
                        }
                    });
            return sellTickets;
        }
    }
    
    
    //测试访问类
    public class Client {
        public static void main(String[] args) {
            //获取代理对象
            ProxyFactory factory = new ProxyFactory();
            SellTickets proxyObject = factory.getProxyObject();
            proxyObject.sell();
        }
    }
    
    

执行流程如下:

1. 在测试类中通过代理对象调用sell()方法
2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法

3.1.5 CGLIB动态代理

同样是上面的案例,我们再次使用CGLIB代理实现。

如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

CGLIB是第三方提供的包,所以需要引入jar包的坐标:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

代理类是目标类的子类

代码如下:

//卖票接口
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火车站买票");
    }
}

//代理工厂,用来创建代理对象
public class ProxyFactory implements MethodInterceptor {
    //声明目标对象
    private TrainStation trainStation = new TrainStation();

    //获取代理对象
    public TrainStation getProxyPoint(){
        //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        Enhancer enhancer = new Enhancer();
        //设置父类字节码对象
        enhancer.setSuperclass(TrainStation.class);
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
         TrainStation obj = (TrainStation) enhancer.create();
         return obj;
    }

    /**
     *intercept方法参数说明:
     * @param o          代理对象
     * @param method     真实对象中的方法的Method实例
     * @param objects    实际参数
     * @param methodProxy  代理对象中的方法的method实例
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
        final Object invoke = method.invoke(trainStation,objects);
        return invoke;
    }
}


public class Test {
    public static void main(String[] args) {
        //创建代理工厂的对象
        ProxyFactory factory = new ProxyFactory();
        //获取代理对象
        final TrainStation proxyPoint = factory.getProxyPoint();
        proxyPoint.sell();
    }
}

3.1.6 三种代理的对比

  • jdk代理和CGLIB代理

    使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。

    在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

  • 动态代理和静态代理

    动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

    如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

3.1.7 优缺点

优点:

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

缺点:

  • 增加了系统的复杂度;

3.1.8 使用场景

  • 远程(Remote)代理

    本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

  • 防火墙(Firewall)代理

    当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

  • 保护(Protect or Access)代理

    控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

3.2 适配器模式

3.2.1 概述

如果去欧洲国家去旅游的话,他们的插座如下图最左边,是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑,手机在当地不能直接充电。所以就需要一个插座转换器,转换器第1面插入当地的插座,第2面供我们充电,这样使得我们的插头在当地能使用。生活中这样的例子很多,手机充电器(将220v转换为5v的电压),读卡器等,其实就是使用到了适配器模式。

在这里插入图片描述

定义:

​ 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

​ 适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

3.2.2 结构

适配器模式(Adapter)包含以下主要角色:

  • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

3.2.3 类适配器模式

实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。

【例】读卡器

现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。

//SD卡的接口
public interface SDCard {
    //读取SD卡方法
    String readSD();
    //写入SD卡功能
    void writeSD(String msg);
}

//SD卡实现类
public class SDCardImpl implements SDCard {
    public String readSD() {
        String msg = "sd card read a msg :hello word SD";
        return msg;
    }

    public void writeSD(String msg) {
        System.out.println("sd card write msg : " + msg);
    }
}

//电脑类
public class Computer {

    public String readSD(SDCard sdCard) {
        if(sdCard == null) {
            throw new NullPointerException("sd card null");
        }
        return sdCard.readSD();
    }
}

//TF卡接口
public interface TFCard {
    //读取TF卡方法
    String readTF();
    //写入TF卡功能
    void writeTF(String msg);
}

//TF卡实现类
public class TFCardImpl implements TFCard {

    public String readTF() {
        String msg ="tf card read msg : hello word tf card";
        return msg;
    }

    public void writeTF(String msg) {
        System.out.println("tf card write a msg : " + msg);
    }
}

//定义适配器类(SD兼容TF)
public class SDAdapterTF extends TFCardImpl implements SDCard {

    public String readSD() {
        System.out.println("adapter read tf card ");
        return readTF();
    }

    public void writeSD(String msg) {
        System.out.println("adapter write tf card");
        writeTF(msg);
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        Computer computer = new Computer();
        SDCard sdCard = new SDCardImpl();
        System.out.println(computer.readSD(sdCard));

        System.out.println("------------");

        SDAdapterTF adapter = new SDAdapterTF();
        System.out.println(computer.readSD(adapter));
    }
}

类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。

3.2.4对象适配器模式

实现方式:对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。

【例】读卡器

我们使用对象适配器模式将读卡器的案例进行改写。

类适配器模式的代码,我们只需要修改适配器类(SDAdapterTF)和测试类。

//创建适配器对象(SD兼容TF)
public class SDAdapterTF  implements SDCard {

    private TFCard tfCard;

    public SDAdapterTF(TFCard tfCard) {
        this.tfCard = tfCard;
    }

    public String readSD() {
        System.out.println("adapter read tf card ");
        return tfCard.readTF();
    }

    public void writeSD(String msg) {
        System.out.println("adapter write tf card");
        tfCard.writeTF(msg);
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        Computer computer = new Computer();
        SDCard sdCard = new SDCardImpl();
        System.out.println(computer.readSD(sdCard));

        System.out.println("------------");

        TFCard tfCard = new TFCardImpl();
        SDAdapterTF adapter = new SDAdapterTF(tfCard);
        System.out.println(computer.readSD(adapter));
    }
}

注意:还有一个适配器模式是接口适配器模式。当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter ,实现所有方法。而此时我们只需要继承该抽象类即可。

3.2.5 应用场景

  • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。

3.2.6 JDK源码解析

Reader(字符流)、InputStream(字节流)的适配使用的是InputStreamReader。

InputStreamReader继承自java.io包中的Reader,对他中的抽象的未实现的方法给出实现。如:

public int read() throws IOException {
    return sd.read();
}

public int read(char cbuf[], int offset, int length) throws IOException {
    return sd.read(cbuf, offset, length);
}

如上代码中的sd(StreamDecoder类对象),在Sun的JDK实现中,实际的方法实现是对sun.nio.cs.StreamDecoder类的同名方法的调用封装。

  • InputStreamReader是对同样实现了ReaderStreamDecoder的封装。
  • StreamDecoder不是Java SE API中的内容,是Sun JDK给出的自身实现。但我们知道他们对构造方法中的字节流类(InputStream)进行封装,并通过该类进行了字节流和字符流之间的解码转换。

结论:

​ 从表层来看,InputStreamReader做了InputStream字节流类到Reader字符流之间的转换。而从如上Sun JDK中的实现类关系结构中可以看出,是StreamDecoder的设计实现在实际上采用了适配器模式。

3.4 桥接模式

3.4.1 概述

现在有一个需求,需要创建不同的图形,并且每个图形都有可能会有不同的颜色。我们可以利用继承的方式来设计类的关系:

在这里插入图片描述

我们可以发现有很多的类,假如我们再增加一个形状或再增加一种颜色,就需要创建更多的类。

试想,在一个有多种可能会变化的维度的系统中,用继承方式会造成类爆炸,扩展起来不灵活。每次在一个维度上新增一个具体实现都要增加多个子类。为了更加灵活的设计系统,我们此时可以考虑使用桥接模式。

定义:

​ 将抽象实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

3.4.2 结构

桥接(Bridge)模式包含以下主要角色:

  • 抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。
  • 扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  • 实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用。
  • 具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。

3.4.3 案例

【例】视频播放器

需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度,适合使用桥接模式。

代码如下:

/**
 * 操作系统版本
 * 抽象化角色
 */
public abstract class OperatingSystemVersion {
    protected VideFile videFile;

    public OperatingSystemVersion(VideFile videFile){
        this.videFile=videFile;
    }

    public abstract void VideoPlayback(String fileName);
}

//扩展抽象化角色
public class Linux extends OperatingSystemVersion{
    public Linux(VideFile videFile) {
        super(videFile);
    }

    @Override
    public void VideoPlayback(String fileName) {
        videFile.decode(fileName);
    }
}

public class Windows extends OperatingSystemVersion{


    public Windows(VideFile videFile) {
        super(videFile);
    }

    @Override
    public void VideoPlayback(String fileName) {
        videFile.decode(fileName);
    }
}

/**
 * 视频文件
 * 实现化角色
 */
public interface VideFile {
    void decode(String fileName);
}

//具体实现化角色
public class AVIFile implements VideFile{
    @Override
    public void decode(String fileName) {
        System.out.println("AVI格式的视频文件:"+fileName);
    }
}

public class MOVFile implements VideFile {
    @Override
    public void decode(String fileName) {
        System.out.println("MOV格式的视频文件:"+fileName);
    }
}

//测试
public class Test {
    public static void main(String[] args) {
        OperatingSystemVersion osv = new Windows(new MOVFile());
        osv.VideoPlayback("激战");
    }
}

3.4.4 优点

  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。

    如:如果现在还有一种视频文件类型wmv,我们只需要再定义一个类实现VideoFile接口即可,其他类不需要发生变化。

  • 实现细节对客户透明

3.4.4 使用场景

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  • 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
  • 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。

4、行为型模式

为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。

行为型模式分为类行为模式对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。

行为型模式分为:

  • 模板方法模式
  • 策略模式
  • 命令模式
  • 职责链模式
  • 状态模式
  • 观察者模式
  • 中介者模式
  • 迭代器模式
  • 访问者模式
  • 备忘录模式
  • 解释器模式

以上 11 种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。

4.1 模板方法模式

4.1.1 概述

在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

定义:

定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

4.1.2 结构

模板方法(Template Method)模式包含以下主要角色:

  • 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。

    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

    • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

      • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。

      • 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。

      • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

        一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。

  • 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

4.1.3 案例实现

【例】炒菜

炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。

代码如下:

//抽象类
public abstract class AbstractClass {

    //模板方法
    public final void cookProcess() {
        this.pourOil();
        this.heatOil();
        this.pourVegetable();
        this.pourSauce();
        this.fry();
    }


    //具体方法
    //第一步:倒油
    public void pourOil() {
        System.out.println("倒油");
    }

    //第二步:热油是一样的,所以直接实现
    public void heatOil() {
        System.out.println("热油");
    }

    //第三步:倒蔬菜是不一样的
    public abstract void pourVegetable();

    //第四步:倒调味料是不一样
    public abstract void pourSauce();


    //第五步:翻炒是一样的,所以直接实现
    public void fry(){
        System.out.println("翻炒,熟了");
    }
}

//具体子类
public class ConcreteClass_BaoCai extends AbstractClass {
    @Override
    public void pourVegetable() {
        System.out.println("下入包菜");
    }

    @Override
    public void pourSauce() {
        System.out.println("放入辣椒");
    }
}

public class ConcreteClass_CaiXin extends AbstractClass {
    @Override
    public void pourVegetable() {
        System.out.println("下入菜心");
    }

    @Override
    public void pourSauce() {
        System.out.println("放入蒜蓉");
    }
}
//测试
public class Test {
    public static void main(String[] args) {
        //蒜蓉炒菜心
        ConcreteClass_CaiXin caiXin =  new ConcreteClass_CaiXin();
        caiXin.cookProcess();

        //爆炒包菜
        ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
        baoCai.cookProcess();
    }
}

注意:为防止恶意操作,一般模板方法都加上 final 关键词。

4.1.4 优缺点

优点:

  • 提高代码复用性

    将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。

  • 实现了反向控制

    通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合’'开闭原则"

缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

4.1.5 适用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

4.1.5 JDK源码解析

InputStream类就使用了模板方法模式。在InputStream类中定义了多个 read() 方法,如下:

public abstract class InputStream implements Closeable {
    //抽象方法,要求子类必须重写
    public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read(); //调用了无参的read方法,该方法是每次读取一个字节数据
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }
}

从上面代码可以看到,无参的 read() 方法是抽象方法,要求子类必须实现。而 read(byte b[]) 方法调用了 read(byte b[], int off, int len) 方法,所以在此处重点看的方法是带三个参数的方法。 read(byte b[], int off, int len) 就是模板方法。

在该方法中第18行、27行,可以看到调用了无参的抽象的 read() 方法。

总结如下: 在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取len个字节数据。具体如何读取一个字节数据呢?由子类实现。

4.2 观察者模式

4.2.1 概述

定义:

又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。

4.2.2 结构

在观察者模式中有如下角色:

  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

4.4.3 案例实现

【例】微信公众号

在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。

代码如下:

定义抽象主题类,提供了add、delete、notify三个方法

//抽象主题类
public interface Subject {
    //添加用户
    void add(Observer observer);

    //删除用户
    void delete(Observer observer);

    //更新方法
    void notify(String msg);

}

微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法

//具体主题类
public class OAccounts implements Subject {
    //储存订阅公众号的微信用户
    private List<Observer> users = new ArrayList<>();


    @Override
    public void add(Observer observer) {
        users.add(observer);
    }

    @Override
    public void delete(Observer observer) {
        users.remove(observer);
    }

    @Override
    public void notify(String msg) {
        for (Observer user : users) {
            user.update(msg);
        }
    }
}

定义抽象观察者类,里面定义一个更新的方法

//抽象观察者
public interface Observer {
    void update(String msg);
}

定义具体观察者类,微信用户是观察者,里面实现了更新的方法

//具体观察者
public class WeChatUser implements Observer {
    private String name;

    public WeChatUser(String name) {
        this.name = name;
    }

    @Override
    public void update(String msg) {
        System.out.println(name+"--->"+msg);
    }
}

测试:

public class Demo {
    public static void main(String[] args) {
        OAccounts accounts = new OAccounts();
        accounts.add(new WeChatUser("张三"));
        accounts.add(new WeChatUser("李四"));
        accounts.add(new WeChatUser("王五"));
        accounts.add(new WeChatUser("杨戬"));


        accounts.notify("我发了新的推文,都看看吧!");
    }
}

4.2.4 优缺点

优点

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

缺点

  • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
  • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

4.2.5 使用场景

  • 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

常用设计模式的学习 的相关文章

  • JavaWeb介绍

    文章目录 1 基本概念1 1介绍1 2 Web应用程序1 静态web2 动态web 2 Web服务器2 1 技术介绍1 ASP2 PHP3 JSP Servlet 2 2 服务器介绍1 IIS2 Tomcat 3 Tomcat3 1 安装3
  • 邮件发送原理及实现

    文章目录 一 邮件发送原理1 1 接收发送过程1 2 邮件服务器1 3 邮件传输协议 二 Java邮件发送2 1 准备环境2 2 介绍2 2 1 授权码 2 3 简单邮件2 3 1 引入2 3 2 步骤一 xff1a 准备参数2 3 3 步
  • 11、MyBatis的逆向工程

    文章目录 11 MyBatis的逆向工程11 1 创建逆向工程的步骤1 添加依赖和插件2 创建mybatis config xml的核心配置文件3 创建逆向工程的配置文件4 执行MBG插件的generate目标5 效果6 窜库问题 11 2
  • 20、单元测试

    文章目录 1 JUnit5 的变化2 JUnit5常用注解3 断言 xff08 assertions xff09 1 简单断言2 数组断言3 组合断言4 异常断言5 超时断言6 快速失败 4 前置条件 xff08 assumptions x
  • 使用模拟器发送短信出现错误的解决方法

    在安卓应用开发揭秘第四章讲解使用模拟器发送短信并使用Toast显示短信息的时候 xff0c 本人遇到过如下错误 xff0c 现有一点个人的浅显理解 xff1a 错误 xff1a Couldn 39 t open fd for content
  • 22、原理解析

    文章目录 1 Profile功能1 application profile功能2 64 Profile条件装配功能3 profile分组 2 外部化配置1 外部配置源2 配置文件查找位置3 配置文件加载顺序 xff1a 4 指定环境优先 x
  • 5、网络配置

    文章目录 5 网络配置5 1 VMware三种模式5 1 1 桥连模式5 1 2 NAT模式5 1 3 仅主机模式 5 2 查看网络IP和网关5 2 1 查看虚拟网络编辑器5 2 2 修改虚拟网卡 Ip5 2 3 查看网关5 2 4 查看
  • 1、认识IntelliJ IDEA

    文章目录 1 认识IntelliJ IDEA1 1 JetBrains公司介绍1 2 IntelliJ IDEA介绍1 3 IDEA的主要优势 xff08 对比Eclipse xff09 1 3 1 功能强大1 3 2 符合人体工程学 1
  • 21、指标监控

    文章目录 1 SpringBoot Actuator1 简介2 1 x与2 x的不同3 如何使用4 可视化 2 Actuator Endpoint1 最常使用的端点2 Health Endpoint3 Metrics Endpoint4 管
  • Android->Activity四种启动模式详解和onNewIntent调用时机

    Activity四种启动模式详解 xff1a 1 standard 默认启动模式 xff0c 每次激活Activity时都会创建Activity xff0c 并放入任务栈中 xff0c 永远不会调用onNewIntent 2 singleT
  • MyEclipse调试小技巧

    前言 现在我们很多人都是使用 MyEclipse 来进行开发 xff0c 最近一段时间我也在使用 MyEclipse xff0c 结合我自己使用过程中的经验以及搜罗的一些小技巧 xff0c 在这里跟大家分享一下 xff0c 帮助我们大家一起
  • Log4j漏洞补救 Log4j2 + SLF4j 升级到最新版本

    一 背景 因Log4j的2 X版本和1 x版本接连爆出漏洞 xff0c 使Log4j不得不升级到最新版本了 xff0c 本博客整合了网上的文章结合自己的实际项目记录本次升级过程 二 搭建步骤 2 1 xff1a 去除直接和间接依赖的log4
  • 统计这句话中每个字母出现的次数 并 打印次数最多和最少的两个字母

    package com zhiyou entity import java util HashMap import java util Map public class ZYtongjicishu public static lt K V
  • 什么是范数(Norm),其具有哪些性质

    文章目录 直观的感受一下范数范数的定义直观的感受下范数的边界图像范数的性质参考资料 直观的感受一下范数 先直观的感受一下二维空间的范数 xff0c 假设在二维空间的向量为 v 61 x y
  • Hugging Face快速入门(重点讲解模型(Transformers)和数据集部分(Datasets))

    文章目录 本文内容HuggingFace简介Hugging Face模型讲解Transforms简介Transformers安装使用Transformers进行推理查找Hugging Face模型使用Hugging Face模型迁移学习 H
  • Eslint 规则说明

    1 34 no alert 34 0 禁止使用alert confirm prompt 2 34 no array constructor 34 2 禁止使用数组构造器 3 34 no bitwise 34 0 禁止使用按位运算符 4 34
  • eclipse无线循环输出时,怎样关闭

    eclipse控制台无限循环输出的时候找到 xff0c 找到控制台右边有一个红色方块按钮 xff0c 点击即可停止运行 如下图 有问题欢迎私聊或者发送邮箱 xff08 964427082 64 qq com xff09 一起讨论
  • goland中报错: Unresolved reference 错误解决

    前言 今天早上项目导入的包标红了 xff0c 而且也包了unresolved reference的错误 xff0c 但是程序却可以正常运行 xff0c 在网上找了多种方法 xff0c 最后可以了 xff0c 但是并不知道是哪一个起了作用 x
  • 关于打游戏ping值不稳定问题的解决经历(疑难篇)

    首先 xff0c 大概几天之前 xff0c 笔者发现自己的电脑在打游戏 xff08 lol xff09 的时候ping值忽高忽低 xff0c 就是突然从20跳到10000 43 xff0c 没有丝毫夸张 xff0c 就是这样吓人 xff0c
  • react、angularjs、vue原理应用场景总结

    React 如图 xff1a React的虚拟DOM的生成是可以在任何支持Javascript的环境生成的 xff0c 所以可以在NodeJS或Iojs环境生成 虚拟DOM可以直接转成String 然后插入到html文件中输出给浏览器便可

随机推荐

  • c++中对象和类的概念以及联系

    1 概念 xff1a 类是对一组性质相同的事物的程序描述 如果类在定义中不指定是private或者public的 xff0c 则系统默认为private的 使用struct声明的类 xff0c 如果对其成员不作private或者public
  • c++学习总结(一些零碎的小知识点)

    1 C 语言中 和 gt 区别 结构体变量用 运算符来访问结构体的成员 指向结构体的指针用 gt 来访问其指向的结构体的成员 gt 指向指针变量的运算符 举例 xff1a p gt m 表示指针 p 指向结构体变量中的成员 m xff1b
  • html学习之

    1 lt xff01 DOCTYPE gt 声明帮助浏览器正确的显示网页 xff0c 不是HTML标签 xff0c 它为浏览器提供了一项声明 xff0c 即HTML是用什么版本编写的 lt DOCTYE html gt lt html gt
  • Ubuntu下ssh服务器文件操作命令

    用java写了一个监视 web服务器的程序 需要部署到Ubuntu服务器版本的系统中 xff0c 遇到的第一个问题就是怎么把这个程序copy到服务器上去 xff33 xff33 xff28 服务器 什么是 xff33 xff33 xff28
  • 小狼毫配置

    小狼毫配置 安装下载 设置 安装完成后 xff0c 右键单击任务栏的小狼毫图标 xff0c 点击 输入法设定 xff0c 勾选输入法 xff08 推荐 朙月拼音 简化字 xff09 xff0c 点击 中 xff0c 选择皮肤后即可使用小狼毫
  • 第27章 联合网关 - Identity Server 4 中文文档(v1.0.0)

    通用架构是所谓的联合网关 在此方法中 xff0c IdentityServer充当一个或多个外部身份提供商的网关 该架构具有以下优点 您的应用程序只需要了解一个令牌服务 xff08 网关 xff09 xff0c 并且屏蔽了有关连接到外部提供
  • Asp.net core3.1 框架中 采用Serilog实现log日志记录

    本文以MVC框架为例 xff0c 实现log记录 在默认情况下 xff0c asp net core有自带的可实现将日志输出到控制台 xff0c 注意 xff0c 此时需要 xff0c 运行时 xff0c 要运行自托管模式才能调出控制台 如
  • 关于Lwip如何实现单网卡多IP设置

    记录 xff1a 关于Lwip如何实现单网卡多IP设置 https wenku baidu com view fb49542683d049649b6658fe html https blog csdn net sinat 20006769
  • 学习java的第一步

    1 走进Java 1 Java的三个平台版本 1 JavaSE J2SE JavaSE是其他两个平台版本的基础 2 JavaME J2ME 针对于移动端开发的版本 3 JavaEE J2EE 针对于web应用的开发版本 跨平台性 因为Jav
  • 面向对象的三大特点

    封装继承多态 1 封装 概念 xff1a 隐藏对象的属性和实现细节 xff0c 对外提供公共的访问方式 原则 xff1a 不需要用户访问的内容隐藏起来 优点 xff1a 1 安全性高 2 独立性高 3 复用性高 span class tok
  • 面向对象版图书管理系统

    span class token keyword package span org span class token punctuation span wdit span class token punctuation span unit0
  • 抽象类

    1 抽象类 概述 xff1a 用来描述抽象概念的类 xff0c 叫做抽象类 抽象类中的方法不一定有具体的实现 span class token keyword package span org span class token punctu
  • 对于MyBatis框架的学习

    1 MyBatis MyBatis 它是Apache的一个开源项目 iBatis 2010年这个项目由apache software foundation 迁 移到了google code xff0c 并且改名为MyBatis 2013年1
  • Spring Boot 原理的分析(超详细!!!)

    1 Spring Boot Spring Boot 没有特定的业务 xff0c 将其他框架进行整合 xff0c 去掉配置 开箱即用 Spring Boot 跟 Spring MVC 的整合 Spring Boot 跟 Thymeleaf 的
  • 多线程进阶=> JUC并发编程(超详细!)

    多线程进阶 61 gt JUC并发编程 1 什么是JUC java util 工具包 xff08 包 分类 xff09 业务 xff1a 普通的线程代码 Thread Runnable 没有返回值 效率相比于 Callable 相对较低 x
  • @Data 注解在实体类的使用可省去生成GET,SET方法

    描述 xff1a 使用lombok插件省去实体类生成GET xff0c SET方法 使用步骤 xff1a 1 打开intellij idea开发工具 File Settings Plugins xff0c 在右侧单击 Browse repo
  • Android ViewBinding的使用详解

    一 什么是view binding 视图绑定会替代 findViewById 通过视图绑定功能 xff0c 您可以更轻松地编写可与视图交互的代码 在模块中启用视图绑定之后 xff0c 系统会为该模块中的每个 XML 布局文件生成一个绑定类
  • 对树结构的学习

    树的常用术语 节点 xff0c 树中的一个连接点 父节点 xff0c 若一个节点含有子节点 xff0c 则这个节点成为其子节点的父节点 子节点 xff0c 一个节点含有的子树的根节点成为该节点的子节点 节点的权 xff0c 节点的具体值 节
  • 对Redis数据库的学习!

    Redis的学习 1 Nosql概述 为什么要用Nosql 1 单击MySQL的年代 90年代 xff0c 一个基本的网站访问量一般不会太大 单个数据库完全足够 那个时候 更多的去使用静态网页Html 服务器根本没有太大的压力 思考一下 这
  • 常用设计模式的学习

    1 设计模式 1 1概述 xff1a 软件设计模式 xff08 Software Design Pattern xff09 xff0c 又称设计模式 xff0c 是一套被反复使用 多数人知晓的 经过分类编目的 代码设计经验的总结 它描述了在