JAVA设计模式

一、对象之间的关系

1. 综述

在Java中对象与对象的关系总体分为四类,分别是:依赖、关联、聚合和组合。

依赖关系:是类和类之间的联接。依赖关系表示一个类依赖于另外一个类的定义,一般而言,依赖关系在Java语言中体现为局域变量、方法的形参,或者对静态方法的调用。

关联关系:是类和类之间的联接,他使一个类知道另外一个类的属性和方法。关联可以是双向的,也可以是单向的。在Java语言中,关联关系一般使用成员变量来实现。

聚合关系:是关联关系的一种,是强的关联关系。聚合是整体和个体之间的关系。

组合关系:是关联关系的一种,是比聚合关系强的关系。

2. 依赖关系

个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A。

class A{
	public void fun_A(){
		...
	}
}

class B{
	public void fun1(A a){//使用参数方式发生与A的依赖关系,fun1方法依赖于A
		a.fun_A();
	}
	
	public void fun2(){//使用创建对象的方式与A发生依赖关系,fun2方法依赖于A
		A a = new A();
		a.fun_A();
	}
}

3. 关联关系

表现在代码上,就是一个类包含另一个类的实例,通常表现为被关联类以类属性的形式出现在关联类的类定义中,也可以表现为关联类引用了一个类型为被关联类的全局变量。关联可以使单向的,也可以使双向的。

依赖和关联的区别在于依赖是使用,关联是拥有。

class A{
	public void fun_A(){
	}

}
class B{
	private A a ;
	public B(A a){
		this.a =a ;
	}
	public void work(){
		a.fun_A() ;
	}
}

4. 聚合关系

它是一种强关联关系(has-a),聚合关系是整体和个体/部分之间的关系,一种单项关系。
与关联关系的区别:关联关系的两个类处于同一个层次上,而聚合关系的两个类处于不同的层次上,一个是整体,一个是个体/部分

class A{
}

class B{
	private A a ;//与上面关联的代码不同之处在于使用A对象构造B,也就是说B是A的不可缺少的一部分
	public B(A a){
		this.a =a ;
	}
}

5. 组合关系

组合也是关联关系的一种(is-a),但它是比聚合关系更强的关系,也是整体/部分之间的关系,但是生命周期相同。
聚合和组合关系的区别:
1、依赖性区别
聚合中的两种类(或实体)是可以单独存在的,不会相互影响;被关联的一方可以独立于关联一方,依赖性不强。
相反,组合中的两个实体(或者类)是高度依赖于彼此的,它们之间会相互影响。
2、生命周期的不同
在聚合关系中的两个类(或实体)的生命周期是不同步;
但在组合关系中的两个类(或实体)的生命周期是同步的。
3、关联强度的不同
聚合关联程度(耦合性)比组合要弱;
在各种对象关系中,关系的强弱顺序为:泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖。
4、关系类型的区别
聚合代表了has-a关系,一种单向关系;组合代表了part-of关系。

class A{
...
}

class B{
	private A a;
	public B(){
		a= newA() ; //强调了生命周期相同
	}
}

二、七大原则

1. 单一职责原则

在软件系统中,一个类只负责一个功能领域中的相应职责

好处

  1. 类的复杂性降低,实现什么职责都有清晰明确的定义
  2. 可读性提高,复杂性降低,那当然可读性提高了
  3. 可维护性提高,那当然了,可读性提高,那当然更容易维护了
  4. 变更引起的风险降低,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大帮助

注意:
单一职责的应用应该要有一个合理的限度,并不是越单一越好。如果类的功能越单一,那么就会产生越多的类,类的数量越多就会造成整个系统的结构越复杂。所以我们在设计系统的时候需要找到一个合理的平衡点。

2. 开放封闭原则

一个软件实体应当对扩展开放,对修改关闭。在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。

好处:

  1. 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  2. 对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何的修改

样例
一个计算器的例子:

//计算器类,可以根据输入的参数和操作进行一定运算
public class Cal{
    public double oper( String oper , double num1, double num2){
        double rst = 0;
        if(oper.equals("+")){
            Add add = new Add();
            rst = add.cal(num1,num2) ;
        }else if(oper.equals("-")){
            sub sub = new Sub();
            rst = sub.cal( num1, num2);
        }
        return rst;
    }
}

使用开闭原则对其进行修改:

//定义一个计算接口
public interface Cal {
    public double cal(double num1,double num2);
}

//加法实现类
public class Add implements Cal {
    @Override
    public double cal(double num1, double num2) {
        return num1+num2;
    }
}

//减法实现类
public class Sub implements Cal {
    @Override
    public double cal(double num1, double num2) {
        return num1-num2;
    }
}

//工厂类
public class Factory {
    public static Cal createInstance(String oper){
        Cal cal = null;
        switch (oper){
            case "+":
                cal = new Add();
                break;
            case "-":
                cal = new Sub();
                break;
        }
        return cal;
    }
}

public class MainTest {
    public static void main(String[] args) {
        //收集数据
        Scanner input = new Scanner(System.in);
        System.out.println("输入num1");
        double num1 = input.nextDouble();
        System.out.println("输入运算符");
        String oper = input.next();
        System.out.println("输入num2");
        double num2 = input.nextDouble();

        //调用工厂类的方法获取具体的计算实例
        Cal cal = Factory.createInstance(oper);
        double result = cal.cal(num1, num2);
        //输出结果
        System.out.println(result);
    }
}

分析:原本的代码,如果后期要添加新的操作(比如乘/除)就需要在原来的代码上进行修改,显然这违背了开放封闭原则。修改的思想是,使用一个计算的接口,每一种操作都实现计算器接口的一项功能,在工厂类中调用该计算器接口,根据不同的操作符生成不同的操作对象。如果要扩展操作功能,就可以扩展Cal接口。此处,其实Factory也不符合开闭原则,可以使用反射对其进行修改:

//工厂类
public class Factory {
    public static Cal createInstance(String oper) {
        Cal cal = null;
        try {
            //加载配置文件
            Properties properties = new Properties();
            properties.load(Factory.class.getClassLoader().getResourceAsStream("oper.properties"));
            //获取需要执行类的全类名
            String clz = properties.getProperty(oper);
            System.out.println(clz);
            //动态加载
            Class clazz = Class.forName(clz);
            //创建实例
             cal = (Cal)clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cal;
    }
}

可以使用配置文件的方式对Factory的功能进行扩充:

//oper.properties配置文件:
+=com.zsn.test.Add
-=com.zsn.test.sub

这样经过上述的修改,如果想要扩充计算方法,只需要在配置文件中添加相应运算符指定新的运算类即可。以上也被称之为简单工厂模式(后文详细讲解)。

3. 依赖倒置原则

高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。

好处: 当两个模块之间存在紧密的耦合关系时,可以在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系。这样,高层模块就不再依赖于底层模块。

和开放封闭原则的关系:依赖倒置类似于实现开放封闭原则的方法。

样例:

//定义奔驰小车类
class Benz {
	public void run(){
		System.out.println("大奔来了,前面的让一让……");
	}
}

//定义一个司机类(能开奔驰小车)
class Driverc {
	public void drive(Benz benz){
		System.out.println("老司机开车了,上车的赶快……");
        benz.run();
	}
}

public class Client {
	public static void main( String[] args){
		Benz benz = new Benz( );
		Driverc driver = new DriverC();
        //让司机去开奔驰车
		driver.drive(benz) ;
	}
}

优化修改之后:

//定义小车接口
interface Car {
	public void run();
}

//定义奔驰小车类(实现car接口)
class Benz implements Car{
	@override
	public void run(){
		System.out.print1n("大奔来了,前面的让一让……");
	}
}

//BMW类,实现car接口
class BMw implements Car i
	@override
	public void run() {
		System.out.println("BMw来了,前面的让一让……");
	}
}

//定义一个司机类(能开小车)
class Driverc {
	public void drive(car car){
		System.out.println("老司机开车了,上车的赶快……");
        car.run();
}
public class client {
	public static void main(String[] args) i
		Car car1 = new Benz();
		Car car2 = new BMIW();
		Driverc driver = new DriverC();
   		 //让司机去开奔驰车
		driver.drive(car1);
    	//让司机去开BMW
    	driver.drive(car2);
	}
}

分析:原来的程序,司机直接依赖于具体的实现类(大奔),这样如果该司机要开新的车(BMw),就需要对司机的drive函数进行修改,这违背了开放封闭原则。优化的程序中,司机直接调用接口(车)中的方法,而不同类型的车直接实现车接口,就可以做到扩展功能而不修改代码(开闭原则)。

4. 里氏替换原则

子类必须可以代替父类。基于该原则可知:在继承时,子类尽量不要重写父类的方法,子类可以扩展新的方法。

好处: 能保证继承复用是可靠的,父类替换子类不会产生任何问题。

经典问题:
“鸵鸟非鸟”:鸟是基类,鸵鸟是鸟的实现类,该类需要覆盖原来的fly()方法,这样鸵鸟就不能代替鸟。
“长方形不是正方形”:长方形是基类,正方形是子类,正方形计算面积的area()方法需要覆盖基类的方法,这样使用正方形代替长方形是就会出现一些问题。

样例:

class A {
    public void fun(int a, int b) {
        System.out.println(a + "+" + b + "=" + (a + b));
    }
}

class B extends A {
    @Override
    public void fun(int a, int b) {
        System.out.println(a + "-" + b + "=" + (a - b));
    }
}

public class demo {
    public static void main(String[] args) {
        System.out.println("父类的运行结果");
        A a = new A();
        a.fun(1, 2);
        //父类存在的地方,可以用子类替代
        //子类B替代父类A
        System.out.println("子类替代父类后的运行结果");
        B b = new B();
        b.fun(1, 2);
    }
}

分析:可以看到,子类的方法如果覆盖的父类的方法,则在使用子类代替父类时结果是不一样的。

5. 接口隔离原则

使用多个小的专门的接口,而不要使用一个大的总接口。

分析:个类对另外一个类的依赖应该建立在最小的接口上,胖接口会导致实现的类型必须完全实现接口的所有方法,显然这是一种浪费。可以通过接口的多继承来实现客户的需求。

样例:

/**
* 定义接口1,里面定义了N个方法
*/
interface Interface1{
    void fun1();
	void fun2();
    void fun3();
    void fun4();
    void fun5();
}

/**
* Class1类中具有多个无用的方法。
* 虽然在这里可以不用去写太多的代码,但是在调用的时候会引起混乱
*/
public class Class1 implements Interface1{
	@Override
	public void fun1() {
		system.out.println("这个方法是有用的.....");
	}
	@Override
	public void fun2() {
		System.out.println("这个方法是有用的.....");
	}
	@Override
	public void fun3() {
        
    }
    @Override
	public void fun4() {
        
    }
    @Override
	public void fun5() {
        
    }
}

使用接口隔离优化的代码:

/**
* 首先将胖接口根据具体的需求隔离成多个小的接口
*/
interface Interface1{
    void fun1();
	void fun2();
}
interface Interface2{
    void fun3();
	void fun4();
}
public interface Interface3{  
	void fun5();
}

//如果一个类需要实现方法fun1,fun2,fun5。再定义一个接口,继承接口1和接口3
//这时,接口4利用接口的多继承特性,就具有了三个方法的定义
interface Interface4 extends Interface1, Interface3{  

}

//实现类实现接口4
public class Class1 implements Interface4{
	@Override
	public void fun1() {
		
	}
	@Override
	public void fun2() {
		
	}
    @Override
	public void fun5() {
        
    }
}

分析:使用一个大的接口,如果创建实现类的话需要实现所有的方法。而使用小接口,多接口继承的方式,则避免了不必要的方法实现。

6. 合成复用原则

尽量使用对象组合,而不是继承来达到复用的目的。也就是,不要为了继承而继承。

好处

  • 使用继承的方式来进行代码复用,缺点在于继承来的方法相当于是自身的方法,那么这个方法如果在需求发生变更的时候,就无法改变(必须要修改源代码来达到目的)。这破坏开放封闭原则。
  • 而使用合成(组合)复用,方法是其他类中的方法,在本类中只是去调用其他的类。所以可以使用依赖倒置原则,可以达到在需求发生变更的时候,扩展现有代码达到目的。
  • 组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用

样例:

class DBConnection {
	public String getConnection() {
	return "MySQL数据库连接";
	}
}
class UserDao extends DBConnection {
	public void addUser(){
	//此处复用了父类的方法
	String connection = super.getConnection();//省略后续添加客户的操作
	}
}
public class Test {
	public static void main(String[] args){
		UserDao userDao = new UserDao();
     	userDao.addUser();
	}
}

优化后的代码:

abstract class DBConnection {
	public abstract String getConnection();
}

class MySQLConnection extends DBConnection {
	@Override
	public String getConnection() {
		return "MySQL数据库连接";
}
class OracleConnection extends DBConnection {
	@Override
	public String getConnection() {
		return "Oracle数据库连接";
}

class UserDao {
	private DBConnection connection;
    
    //通过组合(聚合)的方式调用外部对象的方法,外部方法可以使用依赖倒置进行扩展
	public void setConnection(DBConnection connection) {
		this.connection = connection;
	}
	public void addUser() {
	//省略后续利用connection添加客户的操作
	}
}
public class Test {
	public static void main(String[] args) {
		UserDao userDao = new UserDao();
		userDao.setConnection(new MySQLConnection());
	    userDao.addUser();
	    
		userDao.setConnection(new OracleConnection());
	    userDao.addUser();
	}
}

分析:如果使用继承的方式复用父类中的代码,则需要扩展时,就需要修改父类代码,违背开放封闭原则。优化后,使用组合的方法,相当于调用外部对象的方法,该对象可以通过依赖倒置的原则进行方法扩展。

7. 迪米特法则(最少知识原则)

又称为最少知识原则(Least Knowledge Principle,LKP),含义为:不要和“陌生人”说话;只与你的直接朋友通信。

好处:尽可能少的与其他实体发生相互作用(与其他实体发生关系越多,那么耦合度就越高),降低耦合。

样例:

//商品类
class Goods{
    
}
//员工类
class Staff {
	public void checkNumber(List<Goods> goods) {
		System.out.println("目前超市内商品总数为: "+goods.size());
	}
}
//老板类
class Boss {
	public void requireCheckNumber(Staff staff) {
		List<Goods> goodsList = new ArrayList<Goods>();
        for (int i = 0; i<50; i++) {
			goodsList.add(new Goods());
		}
		staff.checkNumber(goodsList);	
	}
}

//测试类
public class Test{
    public static void main(String[] args){
        Boss boss = new Boss();
        Staff staff = new Staff();
		boss.requireCheckNumber(staff);
	}
}

代码优化:

class Boss {
	public void requireCheckNumber(Staff staff) {
		staff.checkNumber();
	}
}

public class Staff {
	public void checkNumber() {
		List<Goods> goodsList = new ArrayList<Goods>();
        for ( int i = 0; i < 50; i++) {
			goodsList.add(new Goods());
		}
		System.out.println("目前超市内商品总数为: "+goodsList.size());
	}
}

分析:未优化前,老板类中既出现了商品类,又出现了员工类,不符合迪米特法则。优化后,商品类的信息通过员工类传递给了老板类。

8. 七大原则总结

单一职责原则:要求在软件系统中,一个类只负责一个功能领域中的相应职责。
开闭原则要求:一个软件实体应当对扩展开放,对修改关闭,即在不修改源代码的基础上扩展一个系统的行为。
里氏代换原则:可以通俗表述为在软件中如果能够使用基类对象,那么一定能够使用其子类对象。
依赖倒转原则:要求抽象不应该依赖于细节,细节应该依赖于抽象;要针对接口编程,不要针对实现编程。
接口隔离原则:要求客户端不应该依赖那些它不需要的接口,即将一些大的接口细化成一些小的接口供客户端使用。
合成复用原则:要求复用时尽量使用对象组合,而不使用继承。
迪米特法则:要求一个软件实体应当尽可能少的与其他实体发生相互作用。

三、设计模式

1. 单例模式

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

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

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

关键代码:构造函数是私有的

单例模式有以下几种实现方式:

1.1 懒汉模式

描述: 这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
缺点: 不要求线程安全,在多线程不能正常工作。

public class Singleton {
    private static Singleton instance;

    private Singleton(){};

    static Singleton getInstance(){
        if(instance == null){//懒汉模式:要调用时没有才创建
            instance = new Singleton();
        }
        return instance;
    }
}

class Test{
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(singleton.hashCode());
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton2.hashCode());
        /*
        结果为:
        1846274136
		1846274136
        */
    }
}

1.2 饿汉模式

描述:这种方式比较常用,但容易产生垃圾对象。
优点:线程安全,没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。

public class Singleton {
	//类加载的时候就创建对象
    static private Singleton instance = new Singleton();

    private Singleton(){}

    static Singleton getInstance(){
        return instance;
    }
}

class Test{
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(singleton.hashCode());
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton2.hashCode());
    }
}

1.3 线程安全懒汉模式

使用 synchronized关键字对getInstance()方法进行修饰
优点:线程安全,实现简单
缺点:效率低下

public class Singleton {
    private static Singleton instance;

    private Singleton(){}

    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

1.4 双重校验锁(DCL,即 double-checked locking)

优点:使用synchronized代码块,效率高,且线程安全
缺点:写起来需要注意volatile关键字修饰单例对象以及双重校验单例是否为空。

public class Singleton {
    private static volatile Singleton instance = null;

    private Singleton(){}

    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

1.5 使用静态内部类

在Singleton类中定义一个静态内部类,静态内部类中包含一个Singleton的实例成员变量。相比于1.1的懒汉模式,静态内部类的初始化发生在静态方法/静态变量调用的时候,于是就不再有资源的浪费。

优点:线程安全,简单

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

    public static Singleton getInstance(){
        return InnerClass.INSTANCE;//在调用静态成员变量时该变量才会初始化创建一个Singleton实例,且该实例不可变唯一。
    }
}

1.6 使用枚举

描述: 这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

这种方式是 Effective Java 作者 Josh Bloch提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

public enum Singleton {
    INSTANCE;
    public void func(){
        System.out.println("执行了func方法");
    }
}

class Test{
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.INSTANCE;
        System.out.println(singleton1.hashCode());

        Singleton singleton2 = Singleton.INSTANCE;
        System.out.println(singleton2.hashCode());

    }
}

1.7 总结

经验之谈: 一般情况下,不建议使用第 1 种和第 3 种懒汉方式,建议使用第 2 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

2.工厂模式

核心本质:创建对象不使用new,而是交给工厂来完成。将选择实现类、创建对象统一管理和控制,从而将调用者和实现类解耦。

不使用工厂模式的代码:

接口:

public interface Car {
    void name();
}

Byd类:

public class Byd implements Car {
    @Override
    public void name() {
        System.out.println("比亚迪");
    }
}

Tesla类:

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

测试类:

public class Test {
    public static void main(String[] args) {
        Car car1 = new Byd();
        Car car2 = new Tesla();

        car1.name();
        car2.name();
    }
}

2.1 简单工厂模式

使用简单工厂模式对上述代码进行修改:

工厂类:

public class CarFactory {
    public static Car getCar(String string){
        if(string.equals("比亚迪")){
            return new Byd();
        }else if(string.equals("特斯拉")){
            return new Tesla();
        }else return null;
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Car car1 = CarFactory.getCar("比亚迪");
        Car car2 = CarFactory.getCar("特斯拉");

        car1.name();
        car2.name();
    }
}

依赖图
在这里插入图片描述
优势: 不需要客户自己创建对象,将创建对象的任务交给工厂来完成。如果要扩展其他类型的车,则可以直接添加一个车类实现车接口,然后在工厂方法中创建具体的车对象。

缺点: 新增其他类型的车,仍然需要修改工厂类。

2.2 工厂方法模式

创建一个车工厂接口:

public interface CarFactory {
    Car getCar();
}

创建一个比亚迪车工厂:

public class BydFactory implements CarFactory{
    @Override
    public Car getCar() {
        return new Byd();
    }
}

创建一个特斯拉车工厂:

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

测试类:

public class Test {
    public static void main(String[] args) {
        Car car1 = new BydFactory().getCar();
        Car car2 = new TeslaFactory().getCar();

        car1.name();
        car2.name();
    }
}

依赖关系图:
在这里插入图片描述

优势: 相比于简单工厂,如果需要添加新的车类型,只需要创建一个对应类型的车工厂,然后使用该工厂生产某种类型的车即可。满足开放封闭原则。

缺点: 多了一层嵌套,比较繁琐。另外,如果子类越来越多,如果给每个子类都创建一个工厂,则会使系统中的类成倍增加。

3. 抽象工厂模式

定义:抽象工厂提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类,(可以理解为工厂的工厂)。为了解决工厂方法中可能越来越多的类和工厂,抽象工厂将这些产品类进行分组,有些工厂可以同时生产其中的几个产品,这样既减少了工厂的数量,同时也能保证同一工厂生产的产品具有同一工厂的统一风格。

例如:华为工厂同时生产手机和路由器,小米工厂也生产手机和路由器。避免了工厂方法模式0分别创建华为手机工厂,华为路由器工厂,小米手机工厂,小米路由器工厂这四个工厂,而是只用建立小米和华为这两个工厂即可,每个工厂负责多种产品,比如华为生产的产品分别叫“华为手机”和“华为路由器”,也保证了产品的一致性。

在这里插入图片描述

适用场景:
1.客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
2.强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
3.提供一个产品类的库,所有产品以相同接口出现,从而使客户端不依赖于具体的实现。

优点
1.可以确保同一工厂生成的产品相互匹配。
2.可以避免客户端和具体产品代码的耦合。
3.单一职责原则。 可以将产品生成代码抽取到同一位置, 使得代码易于维护。
4.开闭原则。 容易扩展厂商,但是不能扩展产品。

缺点
由于采用该模式需要向应用中引入众多接口和类,代码可能会比之前更加复杂。

实例:
手机产品接口:

public interface IphoneProduct {

    void openPhone();
    void shutDown();
    void call();
    void sendMES();
}

手机实体类:

//小米手机类
class XiaomiPhone implements IphoneProduct {
    @Override
    public void openPhone() {
        System.out.println("打开小米手机");
    }

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

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

    @Override
    public void sendMES() {
        System.out.println("使用小米手机发短信");
    }
}
//华为手机类
class HuaWeiPhone implements IphoneProduct {
    @Override
    public void openPhone() {
        System.out.println("打开华为手机");
    }

    @Override
    public void shutDown() {
        System.out.println("关闭华为手机");
    }

    @Override
    public void call() {
        System.out.println("使用华为手机打电话");
    }

    @Override
    public void sendMES() {
        System.out.println("使用华为手机发短信");
    }
}

路由器产品接口:

public interface IRouterProduct {

    void openWifi();
    void shutDown();
}

路由器实体类:

//华为路由器
class HuaWeiRouter implements IRouterProduct {
    @Override
    public void openWifi() {
        System.out.println("打开华为WiFi");
    }

    @Override
    public void shutDown() {
        System.out.println("关闭华为路由器");
    }
}

//小米路由器实体类
class XiaomiRouter implements IRouterProduct {
    @Override
    public void openWifi() {
        System.out.println("打开小米WiFi");
    }

    @Override
    public void shutDown() {
        System.out.println("关闭小米路由器");
    }
}

产品生产工厂接口:

public interface ProductFactory {
    IphoneProduct getPhone();
    IRouterProduct getRouter();
}

产品生产具体工厂类:

//华为工厂
class HuaweiFactory implements ProductFactory {
    @Override
    public IphoneProduct getPhone() {
        return new HuaWeiPhone();
    }

    @Override
    public IRouterProduct getRouter() {
        return new HuaWeiRouter();
    }
}

//小米工厂
class XiaomiFactory implements ProductFactory {
    @Override
    public IphoneProduct getPhone() {
        return new XiaomiPhone();
    }

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

客户端测试类:

public class Client {
    public static void main(String[] args) {
        ProductFactory factory;
        System.out.println("========华为=========");
        factory = new HuaweiFactory();
        IphoneProduct phone = factory.getPhone();
        IRouterProduct router = factory.getRouter();
        phone.openPhone();
        phone.call();
        phone.sendMES();
        phone.shutDown();

        router.openWifi();
        router.shutDown();

        System.out.println("========小米=========");
        factory = new XiaomiFactory();
        phone = factory.getPhone();
        router = factory.getRouter();
        phone.openPhone();
        phone.call();
        phone.sendMES();
        phone.shutDown();

        router.openWifi();
        router.shutDown();
    }
}

在这里插入图片描述

4. 构造者模式

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

适用场景:当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。

4.1 传统Builder 模式

构建者模式UML图如下所示:
在这里插入图片描述
如上图所示,builder模式有4个角色。

Product:最终要生成的对象,例如 Computer实例。
Builder: 构建者的抽象基类(有时会使用接口代替)。其定义了构建Product的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法Product getProduct()
ConcreteBuilder: Builder的实现类。
Director:决定如何构建最终产品的算法. 其会包含一个负责组装的方法void Construct(Builder builder), 在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过builder的 getProduct() 方法获得最终的产品。

样例代码
第一步:构造Computer类作为产品

public class Computer {
    private String cpu; //构造时必选参数
    private String ram;  //必选
    private int usbCount; //可选
    private String keyboard; //可选
    private String display; //可选

    public Computer(String cpu,String ram){
        this.cpu = cpu;
        this.ram = ram;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public void setDisplay(String display) {
        this.display = display;
    }

    public void setRam(String ram) {
        this.ram = ram;
    }

    public void setKeyboard(String keyboard) {
        this.keyboard = keyboard;
    }

    public void setUsbCount(int usbCount) {
        this.usbCount = usbCount;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "cpu='" + cpu + '\'' +
                ", ram='" + ram + '\'' +
                ", usbCount=" + usbCount +
                ", keyboard='" + keyboard + '\'' +
                ", display='" + display + '\'' +
                '}';
    }
}

第二步:构造一个建造者(抽象类)

//建造者,用来使用可选参数构造产品
public abstract class ComputerBuilder {
    public abstract void setUsbCount(int count);
    public abstract void setKeyboard(String keyboard);
    public abstract void setDisplay(String display);

    public abstract Computer getComputer();
}

第三步:构造真正的建造者(构造者抽象类的实现类)

//戴尔电脑构造者
class DellComputerBuilder extends ComputerBuilder {

    private Computer computer;

    public DellComputerBuilder(String cpu,String ram){
        computer = new Computer(cpu,ram);
    }
    @Override
    public void setUsbCount(int count) {
        computer.setUsbCount(count);
    }

    @Override
    public void setKeyboard(String keyboard) {
        computer.setKeyboard(keyboard);
    }

    @Override
    public void setDisplay(String display) {
        computer.setDisplay(display);
    }

    @Override
    public Computer getComputer() {
        return computer;
    }
}
//苹果电脑构造者
public class AppleComputerBuilder extends ComputerBuilder {
    private Computer computer;
    public AppleComputerBuilder(String cpu,String ram){
        computer = new Computer(cpu,ram);
    }

    @Override
    public void setUsbCount(int count) {
        computer.setUsbCount(count); //设置默认值
    }

    @Override
    public void setKeyboard(String keyboard) {
        computer.setKeyboard(keyboard);
    }

    @Override
    public void setDisplay(String display) {
        computer.setDisplay(display);
    }

    @Override
    public Computer getComputer() {
        return computer;
    }
}

第三步:指导者类(Director),用来指导产品参数构造

public class Director {
    public void makComputer(ComputerBuilder builder){
        if(builder instanceof AppleComputerBuilder){
            builder.setUsbCount(2);
            builder.setDisplay("苹果显示器");
            builder.setKeyboard("苹果键盘");
        }else {
            builder.setUsbCount(2);
            builder.setDisplay("戴尔显示器");
            builder.setKeyboard("戴尔键盘");
        }
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        Director director = new Director();
        ComputerBuilder computerBuilder = new AppleComputerBuilder("i5处理器","4G内存");
        director.makComputer(computerBuilder);
        Computer computer1 = computerBuilder.getComputer();
        System.out.println(computer1.toString());

        ComputerBuilder computerBuilder1 = new DellComputerBuilder("i5处理器","6G内存");
        director.makComputer(computerBuilder1);
        Computer computer2 = computerBuilder1.getComputer();
        System.out.println(computer2.toString());
    }
}

/*结果;
Computer{cpu='i5处理器', ram='4G内存', usbCount=2, keyboard='苹果键盘', display='苹果显示器'}
Computer{cpu='i5处理器', ram='6G内存', usbCount=2, keyboard='戴尔键盘', display='戴尔显示器'}
*/

分析:使用构造者模式,对于客户获取产品,它只需要指定cpu和ram两个必须的参数即可,其他的参数直接由构造者来构建产品,同时让指挥者指挥其他可选参数。

4.2 改进的构造者模式

上面传统的构造者模式,使用了一个指挥者来指挥可选参数的选择,按照现实意义上,应该由客户来指挥。

如何实现:
第一步:在Computer 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
第二步:在Computer中创建一个private的构造函数,参数为Builder类型
第三步:在Builder中创建一个public的构造函数,参数为Computer中必填的那些参数,cpu 和ram。
第四步:在Builder中创建设置函数,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
第五步:在Builder中创建一个build()方法,在其中构建Computer的实例并返回

// 产品:电脑
public class Computer {
    private String cpu; //构造时必选参数
    private String ram;  //必选
    private int usbCount; //可选
    private String keyboard; //可选
    private String display; //可选

    private Computer(Builder builder){
        this.cpu=builder.cpu;
        this.ram=builder.ram;
        this.usbCount=builder.usbCount;
        this.keyboard=builder.keyboard;
        this.display=builder.display;
    }

    public static class Builder{
        private String cpu;//必须
        private String ram;//必须
        private int usbCount;//可选
        private String keyboard;//可选
        private String display;//可选

        public Builder(String cpu,String ram){
            this.cpu = cpu;
            this.ram = ram;
        }

        public Builder setUsbCount(int usbCount) {
            this.usbCount = usbCount;
            return this;
        }

        public Builder setKeyboard(String keyboard) {
            this.keyboard = keyboard;
            return this;
        }

        public Builder setDisplay(String display) {
            this.display = display;
            return this;
        }

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

    }

    @Override
    public String toString() {
        return "Computer{" +
                "cpu='" + cpu + '\'' +
                ", ram='" + ram + '\'' +
                ", usbCount=" + usbCount +
                ", keyboard='" + keyboard + '\'' +
                ", display='" + display + '\'' +
                '}';
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        //在客户端使用链式调用,一步一步的把对象构建出来。
        Computer computer = new Computer.Builder("新特尔","8G三星")
                .setDisplay("24寸三星")
                .setUsbCount(3)
                .setKeyboard("罗技键盘")
                .build();
        System.out.println(computer.toString());
    }
}
/*结果:
Computer{cpu='新特尔', ram='8G三星', usbCount=3, keyboard='罗技键盘', display='24寸三星'}
*/

不使用静态内部类的方式实现
第一步:创建产品类

public class Computer {
    private String cpu; //构造时必选参数
    private String ram;  //必选
    private int usbCount; //可选
    private String keyboard; //可选
    private String display; //可选


    public Computer(String cpu,String ram){
        this.cpu = cpu;
        this.ram = ram;
    }

    public void setUsbCount(int usbCount) {
        this.usbCount = usbCount;
    }

    public void setKeyboard(String keyboard) {
        this.keyboard = keyboard;
    }

    public void setDisplay(String display) {
        this.display = display;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "cpu='" + cpu + '\'' +
                ", ram='" + ram + '\'' +
                ", usbCount=" + usbCount +
                ", keyboard='" + keyboard + '\'' +
                ", display='" + display + '\'' +
                '}';
    }
}

第二步:抽象构造者

public abstract class Builder {
	//注意set的返回值不为void而是Builder
    abstract Builder setUsbCount(int usbCount);
    abstract Builder setKeyboard(String keyboard);
    abstract Builder setDisplay(String display);
    //返回构建的产品
    abstract Computer build();
}

第三步:具体的构造者

public class AppleBuilder extends Builder {

    Computer computer;

    public AppleBuilder(String cpu,String ram){
        computer = new Computer(cpu,ram);
    }
    @Override
    Builder setUsbCount(int usbCount) {
        computer.setUsbCount(usbCount);
        return this;
    }

    @Override
    Builder setKeyboard(String keyboard) {
        computer.setKeyboard(keyboard);
        return this;
    }

    @Override
    Builder setDisplay(String display) {
        computer.setDisplay(display);
        return this;
    }

    @Override
    Computer build() {
        return computer;
    }
}

第四步:测试

public class Test {
    public static void main(String[] args) {
    	//在客户端使用链式调用,一步一步的把对象构建出来。
        Builder builder = new AppleBuilder("i7CPU","12G三星内存");
        Computer computer = builder.setDisplay("苹果显示器")
                .setKeyboard("罗技键盘")
                .setUsbCount(4)
                .build();
        System.out.println(computer.toString());
    }
}

5. 原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

应用场景:创建一个复杂的对象的时候,如果每次都通过new的方式创建代价较高,于是可以选择使用拷贝的方式获取一个一样的克隆对象。这两个对象进行修改互不影响。

浅拷贝
第一步:创建原型类,并实现Cloneable接口,实现其中的clone()方法

public class Product implements Cloneable{
    private String name;
    private Date date;

    public Product(String name,Date date){
        this.name = name;
        this.date = date;
    }

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

    public void setDate(long date) {
        this.date.setTime(date);
    }

    @Override
    public String toString() {
        return "Product{" +
                "name='" + name + '\'' +
                ", date=" + date +
                '}';
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

第二步:在使用时利用原型对象拷贝克隆对象

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Product product = new Product("电脑",new Date());
        System.out.println(product.toString());
        System.out.println(product.hashCode());

        Product product1 = (Product) product.clone();
        product1.setName("手机");
        System.out.println(product1.toString());
        System.out.println(product1.hashCode());

        product1.setDate(1233543534);
        System.out.println(product.toString());
        System.out.println(product1.toString());
    }
}
/*结果:
Product{name='电脑', date=Wed Aug 31 11:10:43 CST 2022}
1625635731
Product{name='手机', date=Wed Aug 31 11:10:43 CST 2022}
1580066828
Product{name='电脑', date=Thu Jan 15 14:39:03 CST 1970}
Product{name='手机', date=Thu Jan 15 14:39:03 CST 1970}
*/

分析:两次哈希值不同,说明已经是不同的对象,然而当克隆对象修改了date值后,原型对象值也变了,说明这是一个浅拷贝,对于类成员变量,它们仍然指向了同一个对象!

深拷贝
将上述代码中的clone()方法改写为:(即手动将原型类中的成员变量对象逐一克隆)

@Override
    public Object clone() throws CloneNotSupportedException {
        Object obj = super.clone();
        Product product = (Product)obj;
        product.date = (Date) this.date.clone();
        return obj;
    }

测试:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Product product = new Product("电脑",new Date());
        System.out.println(product.toString());
        System.out.println(product.hashCode());

        Product product1 = (Product) product.clone();
        product1.setName("手机");
        System.out.println(product1.toString());
        System.out.println(product1.hashCode());

        product1.setDate(1233543534);
        System.out.println(product.toString());
        System.out.println(product1.toString());
    }
}
/*结果为:
Product{name='电脑', date=Wed Aug 31 11:20:36 CST 2022}
1625635731
Product{name='手机', date=Wed Aug 31 11:20:36 CST 2022}
1580066828
Product{name='电脑', date=Wed Aug 31 11:20:36 CST 2022}
Product{name='手机', date=Thu Jan 15 14:39:03 CST 1970}
*/

分析:副本修改了date,不影响原型中date对象,说明date对象不再是同一个。

6. 适配器模式

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。

这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。

应用场景: 客户需要的产品与提供商提供的产品没有很好匹配,就可以使用适配器模式,利用提供商提供的产品经过包装适配成客户需要的产品。

代码示例
第一步:创建用户需要的产品类。(一般是抽象类/接口)

//目标:需要一个能产生5v电压的产品
public interface Target {
    int outPut5V();
}

第二步:创建提供商提供的产品类(实体类)

//提供的商品:能产生220v电压的产品
public class Adaptee {
    int outPut220(){
        int a = 220;
        System.out.println(String.format("输出%s电压", a));
        return a;
    }
}

第三步:创建一个适配器,适配器实现目标产品接口。继承/组合提供的产品类。

public class Adapter implements Target{

    Adaptee adaptee; //组合一个产品类
    public Adapter(Adaptee adaptee){
        this.adaptee = adaptee;
    }
    @Override
    public int outPut5V() {
        int a = adaptee.outPut220(); //将产品类进过一定封装修改,成为用户需要的目标类
        a = a/44;
        System.out.println(String.format("工作输出%s电压", a));
        return a;
    }
}

第四步:测试

public static void main(String[] args) {
        Adaptee adaptee = new Adaptee();//创建产品
        Target target = new Adapter(adaptee);//适配产品
        target.outPut5V();
    }
/*结果:
输出220电压
工作输出5电压
*/

注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。

7. 桥接模式

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。 例如,不同的厂商制造不同的产品,每一种产品组合起来都是一个类,没增加一个厂商类就增加了很多。为了解决这种问题,引入桥接模式。

实例代码
第一步:抽象厂商类

public abstract class Band {
    abstract void info();
}

class Lenovo extends Band{

    @Override
    void info() {
        System.out.print("联想");
    }
}

class Apple extends Band{

    @Override
    void info() {
        System.out.print("苹果");
    }
}

class Dell extends Band{

    @Override
    void info() {
        System.out.print("戴尔");
    }
}

第二步:抽象产品类

public abstract class Computer {
    protected Band band;
    abstract void info();

    public Computer(Band band){
        this.band = band;
    }
}

class Desktop extends Computer{
    public Desktop(Band band) {
        super(band);
    }

    @Override
    void info() {
        band.info();
        System.out.print("台式机");
    }
}

class Laptop extends Computer{
    public Laptop(Band band) {
        super(band);
    }

    @Override
    void info() {
        band.info();
        System.out.print("笔记本");
    }
}

class Table extends Computer{
    public Table(Band band) {
        super(band);
    }

    @Override
    void info() {
        band.info();
        System.out.print("平板");
    }
}

第三步:测试,并两两组合

public class Test {
    public static void main(String[] args) {
        Computer computer = new Laptop(new Lenovo());
        computer.info();

        System.out.println();
        Computer computer1 = new Table(new Apple());
        computer1.info();
    }
}
/*
联想笔记本
苹果平板
*/

8. 代理模式

在代理模式(Proxy Pattern)中,一个类代理另一个类的功能。这种类型的设计模式属于结构型模式。

应用场景:由于职责单一原则,原本的对象可能只负责某一种功能,于是可以使用代理,在原来的基础上新增加一些功能。

代码示例
第一步:创建被代理的对象

public class Host {
    public void rent(){
        System.out.println("租房子");
    }
}

第二步:创建代理的对象

interface Rent {//定义一个行为接口
    void rent();
}

public class Proxy implements Rent{
    private Host host;

    public Proxy(Host host) {
        this.host = host;
    }

    @Override
    public void rent(){
        look();
        host.rent();
        HeTong();
    }

    public void look(){
        System.out.println("看房");
    }

    public void HeTong(){
        System.out.println("签合同");
    }
}

第三步:测试

public class Test {
    public static void main(String[] args) {
        Host host = new Host();
        Proxy proxy = new Proxy(host);
        proxy.rent();
    }
}
/*结果:
看房
租房子
签合同
*/

9. 装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

应用场景:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。在不想增加很多子类的情况下扩展类,可以使用装饰器模式。

实例
第一步:先创建一个现有对象的接口,方便现有对象的扩展

public interface shape {
    void draw();
}

第二步:创建现有对象的实体类,该类就是被装饰的对象

//基础的圆、长方形类
class Circle implements shape{
    @Override
    public void draw() {
        System.out.println("画圆形");
    }
}

class Rectangle implements shape{
    @Override
    public void draw() {
        System.out.println("画长方形");
    }
}

第三步:创建修饰器接口(抽象类),方便对修饰器进行扩展。在抽象类中组合一个被修饰对象。

public abstract class ShapeDecorator{
    protected shape decoratorShape;

    public ShapeDecorator(shape decoratorShape) {
        this.decoratorShape = decoratorShape;
    }

    public abstract void draw();
}

第四步:创建修饰器实体类

class RedShapeDecorator extends ShapeDecorator {
    public RedShapeDecorator(shape decoratorShape) {
        super(decoratorShape);
    }

    @Override
    public void draw() {
        decoratorShape.draw();
        setRedBorder(decoratorShape);//对原有方法进行修饰
    }

    private void setRedBorder(shape decoratedShape){
        System.out.println("边框颜色:红色");
    }
}

class YellowShapeDecorator extends ShapeDecorator {

    public YellowShapeDecorator(shape decoratorShape) {
        super(decoratorShape);
    }

    @Override
    public void draw() {
        decoratorShape.draw();
        setRedBorder(decoratorShape); //对原有方法进行修饰
    }

    private void setRedBorder(shape shape){
        System.out.println("画框颜色:黄色");
    }
}

第五步:测试

public class Test {
    public static void main(String[] args) {
        shape shape = new Circle();
        ShapeDecorator decorator = new RedShapeDecorator(shape);
        decorator.draw();

        System.out.println("=================");
        ShapeDecorator decorator1 = new RedShapeDecorator(new Rectangle());
        decorator1.draw();

        System.out.println("=================");
        ShapeDecorator decorator2 = new YellowShapeDecorator(new Rectangle());
        decorator2.draw();
    }
}
/*结果:
画圆形
边框颜色:红色
=================
画长方形
边框颜色:红色
=================
画长方形
画框颜色:黄色
*/
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
java设计模式大体上分为三大类: 创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。 设计模式遵循的原则有6个: 1、开闭原则(Open Close Principle)   对扩展开放,对修改关闭。 2、里氏代换原则(Liskov Substitution Principle)   只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。 3、依赖倒转原则(Dependence Inversion Principle)   这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。 4、接口隔离原则(Interface Segregation Principle)   使用多个隔离的借口来降低耦合度。 5、迪米特法则(最少知道原则)(Demeter Principle)   一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 6、合成复用原则(Composite Reuse Principle)   原则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值