设计模式总结(Java)

面向对象设计的基本原则:

  • OCP (开闭原则, Open-Closed Principle) : 一个软件的实体应当对扩展开放,对修改关闭。即当要添加一个新功能时,不要修改已有的类,而是重新写一个类实现这个功能。
  • DIP (依赖倒转原则, Dependence Inversion Principle ) : 要针对接口编程,不要针对实现编程。
  • LoD (迪米特法则, Law of Demeter) : 只与你直接的朋友通信,而避免和陌生人通信。

创建型模式




单例模式 (常用)

作用:
  保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。


常见应用场景:

  • Windows的Task Manager (任务管理器)就是很典型的单例模式
  • windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站-直维护着仅有的一个实例。
  • 项目中,读取配置文件的类, 一般也只有一个对象。 没有必要每次使用配置文件数据,每次new-个对象去读取。
  • 网站的计数器, 一般也是采用单例模式实现,否则难以同步。
  • 应用程序的日志应用, 一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态 ,因为只能有一个实例去操作,否则内容不好追加。
  • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
  • 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
  • Application也是单例的典型应用( Servlet编程中会涉及到)
  • 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
    在servlet编程中,每个Servlet也是单例
  • 在spring MVC框架/struts1框架中,控制器对象也是单例

单例模式的优点:

  • 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
  • 单例模式可以在系统设置全局的访问点 ,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理

常见的五种单例模式实现方式:

  • 主要:
    • 饿汉式(线程安全,调用效率高。但是,不能延时加载。)
    • 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。);在用的使用才加载。并发效率资源第。
  • 其他:
    • 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用);这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。
    • 静态内部类式(线程安全,调用效率高。但是,可以延时加载);也是一种懒加载的方式。兼备了并发高效调用和延迟加载的优势!
    • 枚举单例(线程安全,调用效率高,不能延时加载);实现简单,枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!

如何选用
单例对象占用资源少,不需要延时加载:

  • 枚举式好于饿汉式

单例对象占用资源大,需要延时加载:

  • 静态内部类式好于懒汉式

五种单例模式的代码实现:

饿汉模式:

/*
 * 饿汉式单例模式
 */
public class SingletonDemo1 {
	
	//初始化类时,立即加载这个对象(没有延时加载的优势,因为不一定要使用,立即加载可能会浪费资源)
	//加载类时,因为创建对象是线程安全的,不需要添加关键字synchronized(加锁),所以饿汉式是天然线程安全的
	private static SingletonDemo1 instance = new SingletonDemo1();
	
	//将构造方法私有化
	private SingletonDemo1(){
	}
	
	//提供一个获得对象的方法,方法没有同步,调用效率高
	public static SingletonDemo1 getInstance(){
		return instance;
	}
}

懒汉式:

/* 
 * 懒汉式单例模式
 */
public class SingletonDemo2 {
	
	//类初始化时,不立即初始化对象(延时加载,在要使用的时候才进行加载)
	private static SingletonDemo2 instance;
	
	//将构造方法私有化
	private SingletonDemo2(){
	}
	
	//方法同步,调用效率高
	public static synchronized SingletonDemo2 getInstance(){
		if(instance==null){
			instance = new SingletonDemo2();
		}
		return instance;
	}
}


双重检测锁式:

/* 
 * 双重检测锁式单例模式
 */
public class SingletonDemo3 {
	
	private static SingletonDemo3 instance = null;
	
	//将构造方法私有化
	private SingletonDemo3(){
	}
	
	//这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。
	public static  SingletonDemo3 getInstance(){
		if(instance==null){
			SingletonDemo3 sc;
			synchronized(SingletonDemo3.class){
				sc = instance;
				if(sc==null){
					synchronized(SingletonDemo3.class){
						if(sc==null){
							sc = new SingletonDemo3();
						}
					}
					instance = sc;
				}
			}
		}
		return instance;
	}
}


静态内部类式:

/* 
 * 静态内部类式单例模式
 */
public class SingletonDemo4 {
	/*
	 * 要点:
		-外部类没有static属性,则不会像饿汉式那样立即加载对象。
		-只有真正调用getInstance0,才会加载静态内部类。加载类时是线程安全的。 instance是static final
		类型,保证了内存中只有这样一个实例存在 ,而且只能被赋值一次,从而保证了线程安全性.
		-兼备了并发高效调用和延迟加载的优势!
	 */ 
	private static class SingletonClassInstance{
		private static final SingletonDemo4 instance = new SingletonDemo4();
	}
	
	public static SingletonDemo4 getinstance(){
		return SingletonClassInstance.instance;
	}
	
	//将构造方法私有化
	private SingletonDemo4(){
	}
	
}


枚举式:

/*
 * 枚举式单例模式
 */
public enum SingletonDemo5 {
	
	//这个枚举元素,本事就是单例; 缺点是不能延时加载
	INSTANCE;
	
	//添加自己需要的操作
	public void singletonOperarion(){
		
	}
}

  以上五种单例模式除了枚举式,其他的都有可能会被反射和反序列化破解;下面方式可以防止这种情况的发生。

import java.io.ObjectStreamException;

/* 
 * 懒汉式单例模式(防止通过反射和反序列化进行破解)
 */
public class SingletonDemo6 {
	
	//类初始化时,不立即初始化对象(延时加载,在要使用的时候才进行加载)
	private static SingletonDemo6 instance;
	
	//将构造方法私有化
	private SingletonDemo6(){
		//防止通过反射使用私有的构造器
		if(instance!=null){
			throw new RuntimeException();
		}
	}
	
	//方法同步,调用效率高
	public static synchronized SingletonDemo6 getInstance(){
		if(instance==null){
			instance = new SingletonDemo6();
		}
		return instance;
	}
	
	//防止通过反序列化破坏单例模式,如果定义了readResolve()则直接返回此方法指定的对象。不需要单独再创建新对象
	private Object readResolve() throws ObjectStreamException{
		return instance;
	}
}

五种单例模式在多线程下的效率情况:

在这里插入图片描述
注:数据在不同的环境下可能会有点偏差。




工厂模式 (常用)

作用:
  实现了创建者和调用者的分离。

  1. 解耦
    通过工厂模式可以把对象的创建和使用过程分割开来。比如说 Class A 想调用 Class B的方法,那么我们无需关心B是如何创建的,直接去工厂获取就行。

  2. 减少代码量,易于维护
    如果我们直接new一个对象时,如果需要的对象构造方法比较复杂,那么可能需要一连串的代码去创建对象,如果在别的类中又需要创建该对象,那么代码的重复度肯定不小。通过工厂模式的话,我们把对象创建的具体逻辑给隐藏起来了,交给工厂统一管理,这样不仅减少了代码量,以后如果想改代码的话,只需要改一处即可,也方便我们日常的维护。

核心本质:

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

工厂模式的分类:

  • 简单工厂模式:
    • 用来生产同一等级结构中的任意产品。 (对于增加新的产品,需要修改已有代码)
    • 虽然某种程度不符合设计原则,但实际使用最多
  • 方法模式:
    • 用来生产同一等级结构中的固定产品。( 支持增加任意产品);缺点:需要的类可能太多了。
    • 不修改已有类的前提下,通过增加新的工厂类实现扩展。
  • 抽象工厂模式:
    • 用来生产不同产品族的全部产品。( 对于增加新的产品,无能为力;支持增加产品族)
    • 不可以增加产品,可以增加产品族

应用场景

  • JDK中Calendar的getInstance方法
  • JDBC中Connection对象的获取
  • Hibernate中SessionFactory创建Session
  • spring中IOC容器创建管理bean对象
  • XML解析时的DocumentBuilderFactory创建解析器对象
  • 反射中Class对象的newInstance()
  • 对象实例创建的过程比较复杂,并且需要准备很多参数等等,比如说 spring的beanFactory

三种模式下的代码实现和UML图:

用于测试的类:

public interface Car {
	void run();
}
public class Audi implements Car{
	@Override
	public void run() {
		System.out.println("奥迪在跑");
	}
}
public class Byd implements Car{
	@Override
	public void run() {
		System.out.println("比亚迪在跑");
	}
}

简单工厂实现:

/*
 * 简单工厂类
 */
public class CarFactory1 {
	
	public static Car createAudi(){
		return new Audi();
	}
	
	public static Car createByd(){
		return new Byd();
	}
}

测试类:

/*
 * 简单工厂情况下
 */
public class Client02 {
	public static void main(String[] args) {
		Car c1 = CarFactory1.createAudi();
		Car c2 = CarFactory1.createByd();
		 
		c1.run();
		c2.run();
	}
}

简单工厂下的UML图:
在这里插入图片描述


方法模式工厂:

工厂方法模式要点:

  • 为了避免简单工厂模式的缺点,不完全满足OCP。
  • 工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。
  • 每一种产品都有一个工厂类对应,当需要添加产品时,只需要添加新的类即可,不需要修改原来的代码。

代码实现:

接口:

public interface CarFactory {
	Car createCar();
}

奥迪工厂:

public class AudiFactory implements CarFactory{

	@Override
	public Car createCar() {
		return new Audi();
	}
}

比亚迪工厂:

public class BydFactory implements CarFactory{

	@Override
	public Car createCar() {
		return new Byd();
	}
}

测试类:

/*
 * 使用方法工厂模式
 */
public class Client03 {
	public static void main(String[] args) {
		Car c1 = new AudiFactory().createCar();
		Car c2 = new BydFactory().createCar();
		
		c1.run();
		c2.run();
	}
}

方法工厂的UML图:
在这里插入图片描述


抽象工厂模式:

作用:

  • 用来生产不同产品族的全部产品。( 对于增加新的产品,无能为力;支持增加产品族)
  • 抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。

代码实现:

引擎产品:

package abstractFactory;
/*
 *引擎接口 
 */
public interface Engine {
	void run();
	void start();
}

//高端引擎
class LuxuryEngine implements Engine{

	@Override
	public void run() {
		System.out.println("转的快!");
	}

	@Override
	public void start() {
		System.out.println("启动快!可以自动暂停");
	}
}

//低端引擎
class LowEngine implements Engine{

	@Override
	public void run() {
		System.out.println("转的慢!");
	}

	@Override
	public void start() {
		System.out.println("启动慢!");
	}
}

座椅产品:

package abstractFactory;
/*
 * 座椅
 */
public interface Seat {
	void massage();
}

//高端座椅
class LuxurySeat implements Seat{

	@Override
	public void massage() {
		System.out.println("可以自动按摩");
	}
}

//低端座椅
class LowSeat implements Seat{

	@Override
	public void massage() {
		System.out.println("不能自动按摩");
	}
}

轮胎产品:

package abstractFactory;

//轮胎接口
public interface Tyre {
	void revolve();
}

//高端轮胎
class LuxuryTyre implements Tyre{

	@Override
	public void revolve() {
		System.out.println("旋转不磨损");
	}
}

//低端轮胎
class LowTyre implements Tyre{

	@Override
	public void revolve() {
		System.out.println("旋转磨损");
	}
}

工厂接口:

package abstractFactory;

public interface CarFactory {
	Engine CreateEngine();
	Seat CreateSeat();
	Tyre CreateTre();
}

高端产品族工厂:

package abstractFactory;
/*
 * 高端产品族工厂
 */
public class LuxuryCarFactory implements CarFactory{

	@Override
	public Engine CreateEngine() {
		return new LuxuryEngine();
	}

	@Override
	public Seat CreateSeat() {
		return new  LuxurySeat();
	}

	@Override
	public Tyre CreateTre() {
		return new LuxuryTyre();
	}
	
}

低端产品族工厂:

package abstractFactory;
/*
 * 低端产品族工厂
 */
public class LowCarFactory implements CarFactory{

	@Override
	public Engine CreateEngine() {
		return new LowEngine();
	}

	@Override
	public Seat CreateSeat() {
		return new  LowSeat();
	}

	@Override
	public Tyre CreateTre() {
		return new LowTyre();
	}
	
}

测试类:

package abstractFactory;

public class Client {
	public static void main(String[] args) {
		CarFactory factory = new LuxuryCarFactory();
		Engine e = factory.CreateEngine();
		e.run();
		e.start();
	}
}



建造者模式 (常用)

应用场景:

  • 我们要建造一个复杂的产品。比如:神州飞船,Iphone。这个复杂的产品的创建。有这样个问题需要处理:装配这些子组件是不是有个步骤问题?
  • 实际开发中,我们所需要的对象构建时,也非常复杂,有很多步骤需要处理时。

建造模式的本质:

  • 分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况下使用。
  • 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。

开发中应用场景:

  • StringBuilder类的append方法
  • SQL中的PreparedStatement
  • JDOM中, DomBuilder、SAXBuilder
代码实现和UML图

飞船和零件类:

package 建造者模式;
/*
 * 宇宙飞船
 */
public class AirShip {
	private OrbitalModule orbitalModule;   //轨道舱
	private Engine engine; 		//发动机
	private EscapeTower escapeTower;  //逃逸塔
	
	public void launch(){
		System.out.println("发射!");
	}
	
	public OrbitalModule getOrbitalModule() {
		return orbitalModule;
	}
	public void setOrbitalModule(OrbitalModule orbitalModule) {
		this.orbitalModule = orbitalModule;
	}
	public Engine getEngine() {
		return engine;
	}
	public void setEngine(Engine engine) {
		this.engine = engine;
	}
	public EscapeTower getEscapeTower() {
		return escapeTower;
	}
	public void setEscapeTower(EscapeTower escapeTower) {
		this.escapeTower = escapeTower;
	}
	
	
}

class OrbitalModule {
	private String name;

	public OrbitalModule(String name) {
		super();
		this.name = name;
	}
}

class Engine {
	private String name;

	public Engine(String naem) {
		super();
		this.name= naem;
	}

	public String getName() {
		return name;
	}
}

class EscapeTower {
	private String name;

	public EscapeTower(String name) {
		super();
		this.name = name;
	}
}

建造者接口:

/*
 * 建造者接口
 */
public interface AirShipBuilder {
	Engine builderEngine();
	OrbitalModule builderOrbitaModule();
	EscapeTower builderEscapeTower();
}

组装者接口:

public interface AirShipDirector {
	//组装飞船对象
	AirShip directAirShip();
}

构建者类:

package 建造者模式;
/*
 * 构建者类
 */
public class SxtAirShipBuilder implements AirShipBuilder{

	@Override
	public Engine builderEngine() {
		System.out.println("构建发动机");
		return new Engine("发动机");
	}

	@Override  
	public OrbitalModule builderOrbitaModule() {
		System.out.println("构建逃逸塔");
		return new OrbitalModule("逃逸塔");
	}

	@Override
	public EscapeTower builderEscapeTower() {
		System.out.println("构建轨道舱");
		return new EscapeTower("轨道舱");
	}
}

组装者类:

package 建造者模式;

/*
 * 组装者类,用于将零件组装成对象
 */
public class SxtAirshipDirector implements AirShipDirector{
	private AirShipBuilder builder;
	
	public SxtAirshipDirector(AirShipBuilder builder){
		this.builder = builder; 
	}

	@Override  //将组件组装成飞船
	public AirShip directAirShip() {
		Engine e = builder.builderEngine();
		OrbitalModule o = builder.builderOrbitaModule();
		EscapeTower et = builder.builderEscapeTower();
		
		//装配成飞船对象
		AirShip ship = new AirShip();
		ship.setEngine(e);
		ship.setEscapeTower(et);
		ship.setOrbitalModule(o);
		
		return ship;
	}
}

测试类:

public class Client {
	public static void main(String[] args) {
		AirShipDirector director = new SxtAirshipDirector(new SxtAirShipBuilder());
		AirShip ship = director.directAirShip();
		System.out.println(ship.getEngine().getName());
		ship.launch();
	}
}

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




原型模式

原型模式介绍:

  • 通过new产生一 个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式
  • 就是java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点
  • 优势有:效率高(直接克隆,避免了重新执行构造过程步骤)。
  • 克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的对象的属性值完全和原型对象相同。并且克隆出的新对象改变不会影响原型对象。然后,再修改克隆对象的值。

原型模式实现:

  • Cloneable接口和clone方法
  • Prototype(原型)模式中实现起来最困难的地方就是内存复制操作 ,所幸在Java中提供了clone()方法替我们做了绝大部分事情。

开发中的应用场景

  • 原型模式很少单独出现,-般是和工厂方法模式一起出现,通过clone的方法创建一个对象 ,然后由工厂方法提供给调用者。
  • spring中bean的创建实际就是两种:单例模式和原型模式。( 当然,原型模式需要和工厂模式搭配起来)

代码实现:

package 原型模式;

import java.sql.Date;

public class Sheep implements Cloneable{
	private String sname;
	private Date birthday;
	
	//重写克隆方法
	protected Object clone() throws CloneNotSupportedException{
		Object obj = super.clone();  //直接调用Object对象的clone()方法
		
		//添加如下代码实现深克隆(deep Clone);如果没有进行深坑,克隆出来的属性会指向同一个对象
		Sheep s = (Sheep) obj;
		s.birthday = (Date) this.birthday.clone();  //把属性也进行克隆
		
		return obj;
	}

	public String getSname() {
		return sname;
	}

	public void setSname(String sname) {
		this.sname = sname;
	}

	public Date getBirthday() {
		return birthday;
	}

	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}

	public Sheep() {
	}

	public Sheep(String sname, Date birthday) {
		super();
		this.sname = sname;
		this.birthday = birthday;
	}
}

测试类:

package 原型模式;

import java.sql.Date;
/**
 * 
 * 测试原型模式(深克隆)
 *
 */
public class Client {
	public static void main(String[] args) throws Exception {
		Date d = new Date(123456567L);
		Sheep s1 = new Sheep("shao利", d);
		System.out.println(s1);
		System.out.println(s1.getSname());
		System.out.println(s1.getBirthday());
		
		Sheep s2 = (Sheep)s1.clone();
		
		d.setTime(666666666666L);
		
		s2.setSname("多利");
		System.out.println(s2);
		System.out.println(s2.getSname());
		System.out.println(s2.getBirthday());
	}
}




创建型模式总结

创建型模式:都是用来帮助我们创建对象的!
单例模式:

  • 保证一个类只有一个实例,并且提供一 个访问该实例的全局访问点。

工厂模式:

  • 简单工厂模式:
       用来生产同一等级结构中的任意产品。( 对于增加新的产品,需要修改已有代码)
  • 工厂方法模式:
       用来生产同一等级结构中的固定产品。( 支持增加任意产品)
  • 抽象工厂模式:
       一用来生产不同产品族的全部产品。( 对于增加新的产品,无能为力;支持增加产品族)

建造者模式:

  • 分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。从而可以构造出复杂的对象。
  • 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式

原型模式:

  • 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式


结构型模式

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

结构型模式分类:

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



适配器(adapter)模式 (常用)



什么是适配器模式?

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

模式中的角色

  • 目标接口( Target) :客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
  • 需要适配的类( Adaptee) : 需要适配的类或适配者类。
  • 适配器( Adapter) :通过包装一个需要适配的对象,把原接口转换成目标接口。

使用场景:

  • 经常用来做旧系统改造和升级
  • java.io.InputStreamReader(InputStream)
  • java.io.OutputStreamWriter(OutputStream)

代码实现:

被适配的类:

/**
 * 被适配的类
 */
public class Adaptee {
	public void request(){
		System.out.println("可以完成客户请求需要的功能");
	}
}

接口:

public interface Target {
	void handleReq();
}

适配器:

/**
 * 适配器(用继承的方式实现,也可以用组合的方式实现)
 */
public class Adapter extends Adaptee implements Target{

	@Override
	public void handleReq() {
		super.request();
	}
}

客户端:

/**
 * 客户端
 */
public class Client {
	
	public void test1(Target t){
		t.handleReq();
	}
	
	public static void main(String[] args) {
		Client c = new Client();
		Adaptee a = new Adaptee();
		Target t = new Adapter();
		//通过适配器来使用其他类的方法
		c.test1(t);
	}
}



代理模式 (常用)



作用:

  • 通过代理,控制对对象的访问;可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。( 即: AOP的微观实现! )
  • AOP(Aspect Oriented Programming面向切面编程)的核心实现机制 !

核心角色:

  • 抽象角色:
    • 定义代理角色和真实角色的公共对外方法
  • 真实角色:
    • 实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。关注真正的业务逻辑!
  • 代理角色:
    • 实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
    • 将统一的流程控制放到代理角色中处理!

在这里插入图片描述

应用场景:

  • 安全代理:屏蔽对真实角色的直接访问。
  • 远程代理:通过代理类处理远程方法调用(RMI)
  • 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。(比如你要开发一个大文档查看软件,大文档中有大的图片,有可能个图片有100MB ,在打开文件时不可能将所有的图片都显示出来,这样就可以使用代理模式。当需要查看图片时。用proxy来进行大图片的打开。)

开发框架中应用场景:

  • struts2中拦截器的实现
  • 数据库连接池关闭处理
  • Hibernate中延时加载的实现
  • mybatis中实现拦截器插件
  • AspectJ的实现
  • spring中AOP的实现
    • 日志拦截
    • 声明式事务处理
  • web service
  • RMI远程方法调用

分类:

静态代理(静态定义代理类)

代码实现:

抽象类:

public interface Star {
	void confer();  		//面谈
	void signContract();    //签合同
	void bookTicket();		//订票
	void sing();		    //唱歌
	void collectMoney(); 	//收钱
}

真实角色:

/**
 * 真实角色
 */
public class RealStar implements Star{

	@Override
	public void confer() {
		System.out.println("RealStar.confer()");
	}

	@Override
	public void signContract() {
		System.out.println("RealStar.signContract()");
	}

	@Override
	public void bookTicket() {
		System.out.println("RealStar.bookTicket()");
	}

	@Override
	public void sing() {
		System.out.println("真实角色实现唱歌功能RealStar.sing()");
	}

	@Override
	public void collectMoney() {
		System.out.println("RealStar.collectMoney()");
	}
}

代理角色:

/**
 * 代理角色
 */
public class ProxyStar implements Star{

	private Star star;
	
	public ProxyStar(Star real) {
		super();
		this.star = real;
	}
	
	public ProxyStar(){
	}

	@Override
	public void confer() {
		System.out.println("ProxyStar.confer()");
	}

	@Override
	public void signContract() {
		System.out.println("ProxyStar.signContract()");
	}

	@Override
	public void bookTicket() {
		System.out.println("ProxyStar.bookTicket()");
	}

	//代理角色不能实现核心功能:唱歌,必须通过star对象调用
	public void sing() {
		star.sing();
	}

	@Override
	public void collectMoney() {
		System.out.println("ProxyStar.bookTicket()");
	}
}

测试类:

public class Client {
	public static void main(String[] args) {
		Star real = new RealStar();
		Star proxy = new ProxyStar(real);   //对真实角色进行代理
		
		proxy.confer();
		proxy.signContract();
		proxy.bookTicket();
		proxy.signContract();
		
		proxy.sing();
		
		proxy.collectMoney();
	}
}

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

动态代理(动态生成代理类):

1.JDK自带的动态代理

  • java.lang.reflest.Proxy.
    作用:动态生成代理类和对象
  • java,lang.reflect.InvocationHandler(处理器接口)
    • 可以通过invoke方法实现对真文角色的代理访问。
    • 每次通过Proxy生成代理类对象对象时都要指定对应的处理器对象

动态代理:在内存中形成代理类

  • 实现步骤:

    1. 代理对象和真实对象实现相同的接口
    2. 代理对象 = Proxy.newProxyInstance();
    3. 使用代理对象调用方法。
    4. 增强方法
  • 增强方式:

    1. 增强参数列表
    2. 增强返回值类型
    3. 增强方法体执行逻辑

下面用代理卖电脑的方式进行讲解:
在这里插入图片描述

接口:

public interface SaleComputer {

    public String sale(double money);

    public void show();
}

真实类:

package cn.itcast.proxy;

/**
 * 真实类
 */
public class Lenovo implements SaleComputer {
    @Override
    public String sale(double money) {

        System.out.println("花了"+money+"元买了一台联想电脑...");
        return "联想电脑";
    }

    @Override
    public void show() {
        System.out.println("展示电脑....");
    }
}

实现动态代理:

package cn.itcast.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest {

    public static void main(String[] args) {
        //1.创建真实对象
        Lenovo lenovo = new Lenovo();
        
        //2.动态代理增强lenovo对象
        /*
            三个参数:
                1. 类加载器:真实对象.getClass().getClassLoader()
                2. 接口数组:真实对象.getClass().getInterfaces()
                3. 处理器:new InvocationHandler()
         */
        SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {

            /*
                代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
                    参数:
                        1. proxy:代理对象
                        2. method:代理对象调用的方法,被封装为的对象
                        3. args:代理对象调用的方法时,传递的实际参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /*System.out.println("该方法执行了....");
                System.out.println(method.getName());
                System.out.println(args[0]);




*/
                //判断是否是sale方法
                if(method.getName().equals("sale")){
                    //1.增强参数
                    double money = (double) args[0];
                    money = money * 0.85;
                    System.out.println("专车接你....");
                    //使用真实对象调用该方法
                    String obj = (String) method.invoke(lenovo, money);
                    System.out.println("免费送货...");
                    //2.增强返回值
                    return obj+"_鼠标垫";
                }else{
                    Object obj = method.invoke(lenovo, args);
                    return obj;
                }



            }
        });

        //3.调用方法

       /* String computer = proxy_lenovo.sale(8000);
        System.out.println(computer);*/

        proxy_lenovo.show();
    }
}

2.javaassist字节码操作库实现
3.CGLIB
4.ASM(底层使用指令,可维护性较差)




桥接模式(bridge)



为了方便理解桥接模式解决的问题,下面引入一个场景:
  商城系统中常见的商品分类.以电脑为类,如何良好的处理商品分类销售的问题?

我们可以用多层继承结构实现下图的关系。
在这里插入图片描述

多层继承结构带来的问题:

  • 扩展性问题(类个数膨胀问题) :
    • 如果要增加一个新的电脑类型:智能手机,则要增加各个品牌下面的类。如果要增加一个新的品牌,也要增加各种电脑类型的类。
  • 违反单一职责原则:
    • 一个类:联想笔记本,有两个引起这个类变化的原因(品牌和机型)

场景分析:

  • 商城系统中常见的商品分类,以电脑为类,如何良好的处理商品分类销售的问题?
  • 这个场景中有两个变化的维度:电脑类型、电脑品牌。
  • 我们可以按照这两个维度作为划分,每一个维度建立一系列的类;然后将两个维度结构桥接起来(用组合代替继承)。当需要添加品牌和机型时,在对应的维度添加类即可。
    在这里插入图片描述

桥接模式核心要点:

  • 处理多层继承结构,处理多维度变化的场景,将各个维度设计成独立的继承结构,使各个维度可以独立的扩展在抽象层建立关联。

桥接模式实际开发中应用场景:

  • JDBC驱动程序。

  • AWT中的Peer架构

  • 银行日志管理:

    • 格式分类:操作日志、交易日志、异常日志
    • 距离分类:本地记录日志、异地记录日志
  • 人力资源系统中的奖金计算模块:

    • 奖金分类:个人奖金、团体奖金、激励奖金。
    • 部门分类:人事部门、销售部门、研发部门。
  • OA系统中的消息处理:

    • 业务类型:普通消息、加急消息、特急消息
    • 发送消息方式:系统内消息、手机短信、邮件

代码实现:

品牌维度:

package 桥接模式;

/**
 * 品牌类型维度
 * @author Administrator
 *
 */
public interface Brand {
	void sale();
}

class Lenovo implements Brand{

	@Override
	public void sale() {
		System.out.println("销售联想电脑");
	}
}

class Dell implements Brand{

	@Override
	public void sale() {
		System.out.println("销售Dell电脑");
	}
}

class Shenzhou implements Brand{

	@Override
	public void sale() {
		System.out.println("销售Shenzhou电脑");
	}
}

机型维度:

package 桥接模式;

/*
 * 电脑类型维度
 */ 
public class Computer {
	
	protected Brand brand;
	
	public Computer(Brand b){
		this.brand = b;
	}
	
	public void sale(){
		brand.sale();
	}
}

class Desktop extends Computer{

	public Desktop(Brand b) {
		super(b);
	}
	
	public void sale(){
		super.sale(); 
		System.out.println("销售台式电脑");
	}
}

class Laptop extends Computer{

	public Laptop(Brand b) {
		super(b);
	}
	
	public void sale(){
		super.sale();
		System.out.println("销售笔记本电脑");
	}
}

测试类:

public class Client {
	public static void main(String[] args) {
		//销售联想的笔记本电脑
		Computer c = new Laptop(new Lenovo());
		c.sale();
		
		//销售神舟的台式机
		Computer c2 = new Desktop(new Shenzhou());
		c2.sale();
	}
}

桥接模式总结:

  • 桥接模式可以取代多层继承的方案。多层继承违背了单一职责原则,复用性较差,类的个数也非常多。桥接模式可以极大的减少子类的个数,从而降低管理和维护的成本。
  • 桥接模式极大的提高了系统可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有的系统,符合开闭原则。



组合模式(composite)



使用组合模式的场景:

  • 把部分和整体的关系用树形结构来表示,从而使客户端可以使用统一的方式处理部分对象和整体对象。(比如吧文件夹看做一个特殊的文件)

组合模式核心:

  • 抽象构件(Component)角色:定义了叶子和容器构件的共同点
  • 叶子(Leaf)构件角色:无子节点
  • 容器(Composite)构件角色:有容器特征,可以包含子节点

开发中的应用场景:

  • 操作系统的资源管理器
  • GUI中的容器层次图
  • XML文件解析
  • 0A系统中,组织结构的处理
  • Junit单元测试框架
    底层设计就是典型的组合模式, TestCase(叶子)、TestUnite(容器)、Test接口(抽象)

常见代码形式:

package 组合模式;
/**
 * 抽象组件
 */
public interface Component {
	void operation();
}

//叶子组件
interface Leaf extends Component{
}

//容器组件
interface Composite extends Component{
	void add(Component c);
	void remove(Component c);
	Component getChild(int index);
}

下面模拟一个杀毒软件,文件作为叶子,文件夹作为容器。代码如下:

package 组合模式;

import java.util.ArrayList;
import java.util.List;

//抽象构建
public interface AbstractFile {
	void killVirus();  //杀毒
}

//图片文件
class ImageFile implements AbstractFile{
	private String name;

	public ImageFile(String name) {
		super();
		this.name = name;
	}

	@Override
	public void killVirus() {
		System.out.println("-----图像文件"+name+"进行查杀");
	}
}

//文本文件
class TextFile implements AbstractFile{
	private String name;

	public TextFile(String name) {
		super();
		this.name = name;
	}

	@Override
	public void killVirus() {
		System.out.println("-----文本文件"+name+"进行查杀");
	}
}

//视频文件
class VideoFile implements AbstractFile{
	private String name;

	public VideoFile(String name) {
		super();
		this.name = name;
	}

	@Override
	public void killVirus() {
		System.out.println("-----视频文件"+name+"进行查杀");
	}
}

//文件夹,
class Folder implements AbstractFile{
	private String name;
	//定义容器,用来存放本容器下的子节点 
	private List<AbstractFile> list = new ArrayList<AbstractFile>();
	
	public Folder(String name) {
		super();
		this.name = name;
	}
	
	public void add(AbstractFile file){
		list.add(file);
	}
	
	public void remove(AbstractFile file){
		list.remove(file);
	}
	
	public AbstractFile getChild(int index){
		return list.get(index);
	}

	@Override
	public void killVirus() {
		System.out.println("---文件夹:"+name+"进行查杀");
		
		for(AbstractFile file:list){
			file.killVirus();
		}
	}
}

测试类:

package 组合模式;

public class Client {
	public static void main(String[] args) {
		AbstractFile f2, f3, f4, f5;
		Folder f1 = new Folder("我的收藏");
		
		f2 = new ImageFile("大头照.jpg");
		f3 = new TextFile("Hello.txt");
		f1.add(f2);
		f1.add(f3);
		
		Folder f11 = new Folder("电影");
		f4 = new VideoFile("笑傲江湖.avi");
		f5 = new VideoFile("神雕侠侣.avi");
		f11.add(f4);
		f11.add(f5);
		f1.add(f11);
		
		f1.killVirus();
	}
}

测试结果:

---文件夹:我的收藏进行查杀
-----图像文件大头照.jpg进行查杀
-----文本文件Hello.txt进行查杀
---文件夹:电影进行查杀
-----视频文件笑傲江湖.avi进行查杀
-----视频文件神雕侠侣.avi进行查杀



装饰器模式(decorator) (常用)



作用:

  • 动态的为一个对象增加新的功能。
  • 装饰模式是一种用于代替继承的技术,无须通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀

开发中使用的场景:

  • IO中输入流和输出流的设计
  • Swing包中图形界面构件功能
  • Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper ,HttpServletRequestWrapper类,增强了request对象的功能。
  • Struts2中 ,request,response,session对象的处理

实现细节:

  • Component抽象构件角色:
    真实对象和装饰对象有相同的接口。这样,客户端对象就能够以与真实对象相同的方式同装饰对象交互。
  • ConcreteComponent 具体构件角色(真实对象) :
    io流中的FileInputStream、 FileOutputStream
  • Decorator装饰角色 :
    持有一个抽象构件的引用。装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象。这样,就能在真实对象调用前后增加新的功能。
  • ConcreteDecorator具体装饰角色:
    负责给构件对象增加新的责任。

优点

  • 扩展对象功能,比继承灵活,不会导致类个数急剧增加
  • 可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更
    加强大的对象
  • 具体构建类和具体装饰类可以独立变化,用户可以根据需要自己增加新的具体构件子类和具体装饰子类。

缺点:

  • 产生很多小对象。大量小对象占据内存, 一定程度上影响性能。
  • 装饰模式易于出错,调试排查比较麻烦。

装饰模式和桥接模式的区别:

  • 两个模式都是为了解决过多子类对象问题。但他们的诱因不一样。桥模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定。装饰模式是为了增加新的功能。

下面通过给汽车添加新功能,体现装饰模式:

代码实现:

package 装饰模式;

/*
 * 抽象构建
 */
public interface ICar {
	void move();
}

// ConcreteComponent 具体构件角色(真实对象)
class Car implements ICar {

	@Override
	public void move() {
		System.out.println("陆地上跑!");
	}
}

//Decorator装饰角色
class SuperCar implements ICar{
	private ICar car;

	public SuperCar(ICar car) {
		super();
		this.car = car;
	}

	@Override
	public void move() {
		car.move();
	}
}

//ConcreteDecorator具体装饰角色
class FlyCar implements ICar{
	private ICar car;

	public FlyCar(ICar car) {
		super();
		this.car = car;
	}

	public void fly(){
		System.out.println("天上飞");
	}
	@Override
	public void move() {
		car.move();
		fly();
	}
}

//ConcreteDecorator具体装饰角色
class WaterCar implements ICar{
	private ICar car;

	public WaterCar(ICar car) {
		super();
		this.car = car;
	}

	public void wsim(){
		System.out.println("水上游! ");
	}
	@Override
	public void move() {
		car.move();
		wsim();
	}
}

//ConcreteDecorator具体装饰角色
class AICar implements ICar{
	private ICar car;

	public AICar(ICar car) {
		super();
		this.car = car;
	}

	public void autoMove(){
		System.out.println("自动驾驶");
	}
	@Override
	public void move() {
		car.move();
		autoMove();
	}
}

测试类:

public class Client {
	public static void main(String[] args) {
		Car car = new Car();
		car.move();
		
		System.out.println("增加新的功能,飞行----------"	);
		FlyCar flycar = new FlyCar(car);
		flycar.move();
		
		System.out.println("增加新的功能,水里游--------");
		WaterCar  waterCar = new WaterCar(car);
		waterCar.move();
		
		System.out.println("增加两个新的功能,飞行,水里游----------");
		WaterCar waterCar2 = new WaterCar(new FlyCar(car));
		waterCar2.move();
		
	}
}

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




外观模式



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

  • 一个软件实体应当尽可能少的与其他实体发生相互作用。
    在这里插入图片描述

外观模式核心:

  • 为子系统提供统一的入口。封装子系统的复杂性,便于客户端调用。

开发中常见的场景:
频率很高。哪里都会遇到。各种技术和框架中,都
有外观模式的使用。如:

  • JDBC封装后的, commons提供的DBUtils类,
  • Hibernate提供的工具类、Spring JDBC工具类等

下面以注册公司流程进行举例:

不使用外观模式的情况:
在这里插入图片描述

使用外观模式后:
在这里插入图片描述




享元模式(FlyWeight)



使用场景:

  • 内存属于稀缺资源,不要随便浪费。如果有很多个完全相同或相似的对象,我们可以通过享元模式,节省内存(时间换空间)。

核心:

  • 享元模式以共享的方式高效地支持大量细粒度对象的重用。
  • 享元对象能做到共享的关键是区分了内部状态和外部状态。
    • 内部状态:可以共享,不会随环境变化而改变
    • 外部状态:不可以共享,会随环境变化而改变
  • 比如围棋中的每一个棋子都是一个对象,它们的具有相同的属性:颜色、形状、大小,这些事可以共享的,称之为内部状态;但是每一个棋子的位置是不一样的,这是不可以共享的,称之为外部状态。

享元模式实现:

  • FlyweightFactory享元工厂 类
    创建并管理享元对象,享元池一般设计成键值对
  • FlyWeight抽象享元类
    通常是一个接口或抽象类,声明公共方法,这些方法可以向外界提供对象的内部状态,设置外部状态。
  • ConcreteFlyWeight具体享元类
    , 为为部状态提供成员变量进行存储
  • UnsharedConcreteFlyWeight非共享享元类
    不能被共享的子类可以设计为非共享享元类

享元模式开发中应用的场景:

  • 元模式由于其共享的特性,可以在任何“池”中操作
    比如:线程池、数据库连接池。
  • String类的设计也是享元模式

优点:

  • 极大减少内存中对象的数量
  • 相同或相似对象内存中只存一-份 ,极大的节约资源,提高系统性能
  • 外部状态相对独立,不影响内部状态

缺点:

  • 模式较复杂,使程序逻辑复杂化
  • 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态使运行时间变长。用时间换取了空间。

下面按照棋子关系展示代码的实现:

package 享元模式;
/**
 * 享元类
 */
public interface ChessFlyWeight {
	void setColor();
	String getColor();
	void display(Coordinate c);
}

//具体享元类, 为为部状态提供成员变量进行存储
class ConcreateChess implements ChessFlyWeight{

	private String color;
	
	public ConcreateChess(String color) {
		super();
		this.color = color;
	}

	@Override
	public void setColor() {
	}

	@Override
	public String getColor() {
		return color;
	}

	@Override
	public void display(Coordinate c) {
	}
	
}

坐标类:

package 享元模式;
/**
 * 坐标类
 */
public class Coordinate {
	private int x, y;

	public Coordinate(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}
}

享元工厂:

package 享元模式;

import java.util.HashMap;
import java.util.Map;

/**
 * 享元工厂类
 */
public class ChessFlyWeightFactory {
	//享元池
	private static Map<String, ChessFlyWeight> map = new HashMap<String, ChessFlyWeight>();
	
	//获得棋子
	public static ChessFlyWeight getChess(String color){
		if(map.get(color)!=null){
			return map.get(color);
		}else{
			ChessFlyWeight cfw = new ConcreateChess(color);
			map.put(color, cfw);
			return cfw;
		}
	}
}

测试类:


public class Client {
	public static void main(String[] args) {
		ChessFlyWeight chess1 = ChessFlyWeightFactory.getChess("黑色");
		ChessFlyWeight chess2 = ChessFlyWeightFactory.getChess("黑色");
		System.out.println(chess1);
		System.out.println(chess2);
		
		System.out.println("增加外部状态的处理===============");
		chess1.display(new Coordinate(10, 10));
		chess2.display(new Coordinate(10, 10));
		
	}
}

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

结构型模式总结:

  • 结构型模式关注对象和类的组织。

行为型模式


行为型模式关注系统中对象之间的相互交互,研究系统在运行时对象之间的相互通信和协作进一步明确对象的职责,共有11种模式:

  1. 责任链模式 chain of responsibility
  2. 命令模式 command(不常用)
  3. 解释器模式 interpreter(不常用)
  4. 迭代器模式 iterator
  5. 中介者模式 mediator
  6. 备忘录模式 memento
  7. 观察者模式 observer
  8. 状态模式 state
  9. 策略模式 strategy
  10. 模板方法模式 template method
  11. 访问者模式 visitor(不常用)



责任链模式chain of responsibility (常用)



定义:

  • 将能够处理同一类请求的对象连成一条链,所提交的请求沿着链传递,链上的对象逐个判断是否有能力处理该请求,如果能则处理,如果不能则传递给链上的下一个对象。

开发中常见的场景:

  • Java中,异常机制就是一种责任链模式。一个try可以对应多 个catch ,当第一个catch不匹配类型,则自动跳到第二个catch.
  • Javascript语言中, 事件的冒泡和捕获机制。Java语言中,事件的处理采用观察者模式。
  • Servlet开发中,过滤器的链式处理
  • Struts2中 ,拦截器的调用也是典型的责任链模式

模拟场景:
公司里面,请假条的审批过程:

  • 如果请假天数小于3天,主任审批
  • 如果请假天数大于等手3天,小于10天,经理审批
  • 如果大于等于10天,小于30天,总经理审批
  • 如果大于等于30天,提示拒绝

代码实现:

请假条类:

package 责任链模式;
/**
 * 封装请假的基本信息
 */
public class LeaveRequest {
	private String empName;
	private int leaveDays;
	private String reason;
	
	public LeaveRequest(String empName, int leaveDays, String reason) {
		super();
		this.empName = empName;
		this.leaveDays = leaveDays;
		this.reason = reason;
	}

	public String getEmpName() {
		return empName;
	}

	public void setEmpName(String empName) {
		this.empName = empName;
	}

	public int getLeaveDays() {
		return leaveDays;
	}

	public void setLeaveDays(int leaveDays) {
		this.leaveDays = leaveDays;
	}

	public String getReason() {
		return reason;
	}

	public void setReason(String reason) {
		this.reason = reason;
	}
}

抽象类:

package 责任链模式;
/**
 * 抽象类:领导
 */
public abstract class Leader {
	protected String name;
	protected Leader nextLeader; //责任链的后继
	
	public Leader(String name) {
		super();
		this.name = name;
	}

	//设定责任链上的后继对象
	public void setNextLeader(Leader nextLeader) {
		this.nextLeader = nextLeader;
	}
	
	//处理请求的核心业务方法
	public abstract void handleRequest(LeaveRequest request);
}

主任类:

/**
 * 主任类
 */
public class Director extends Leader{

	public Director(String name) {
		super(name);
	}

	@Override
	public void handleRequest(LeaveRequest request) {
		if(request.getLeaveDays()<3){
			System.out.println("员工:"+request.getEmpName()+"请假,天数:"+request.getLeaveDays()+",理由:"+request.getReason());
			System.out.println("主任:"+name+" 审批通过!");
		}else{
			if(this.nextLeader!=null){
				this.nextLeader.handleRequest(request);
			}
		}
	}
}

经理类:

/**
 * 经理类
 */
public class Manager extends Leader{

	public Manager(String name) {
		super(name);
	}

	@Override
	public void handleRequest(LeaveRequest request) {
		if(request.getLeaveDays()<10){
			System.out.println("员工:"+request.getEmpName()+"请假,天数:"+request.getLeaveDays()+",理由:"+request.getReason());
			System.out.println("经理:"+name+" 审批通过!");
		}else{
			if(this.nextLeader!=null){
				this.nextLeader.handleRequest(request);
			}
		}
	}
}

总经理类:

/**
 * 总经理类
 */
public class GeneralManager extends Leader{

	public GeneralManager(String name) {
		super(name);
	}

	@Override
	public void handleRequest(LeaveRequest request) {
		if(request.getLeaveDays()<30){
			System.out.println("员工:"+request.getEmpName()+"请假,天数:"+request.getLeaveDays()+",理由:"+request.getReason());
			System.out.println("总经理:"+name+" 审批通过!");
		}else{
			System.out.println("请假天数超过30天,拒绝审批!");
		}
	}
}

测试类:

/**
 * 测试类
 */
public class Client {
	public static void main(String[] args) {
		Leader a = new Director("张三");
		Leader b = new Manager("李四");
		Leader c = new GeneralManager("王五");
		
		//组织责任链对象的关系
		a.setNextLeader(b);
		a.setNextLeader(c);
		
		//开始请假操作
		LeaveRequest req1 = new LeaveRequest("TOM", 1, "回家探亲!");
		a.handleRequest(req1);
		LeaveRequest req2 = new LeaveRequest("TOM", 5, "回家探亲!");
		a.handleRequest(req2);
		LeaveRequest req3 = new LeaveRequest("TOM", 15, "回家探亲!");
		a.handleRequest(req3);
	}
}

测试结果:
员工:TOM请假,天数:1,理由:回家探亲!
主任:张三 审批通过!
员工:TOM请假,天数:5,理由:回家探亲!
总经理:王五 审批通过!
员工:TOM请假,天数:15,理由:回家探亲!
总经理:王五 审批通过!




迭代器模式(iterator)



场景:

  • 提供一种可以遍历聚合对象的方式。又称为:游标cursor模式
  • 聚合对象:存储数据
  • 迭代器:遍历数据

结构:

  • 聚合对象:存储数据
  • 迭代器:遍历数据

开发中常见的场景:

  • JDK内置的迭代器(List/Set)

代码实现:

接口定义:

package 迭代器模式;
/**
 * 自定义的迭代器接口
 */
public interface MyIterator {
	void first();   // 将游标指向第一个元素
	void next();	// 将游标指向下一个元素
	boolean hasNext();	// 判断是否存在下一个元素
	
	boolean isFirst();
	boolean isLast();
	
	Object getCurrentObje();	//获取当前游标指向的对象
}

用内部类实现迭代器:

package 迭代器模式;

import java.util.ArrayList;
import java.util.List;

public class ConcreteMyAggregate {
	private List<Object> list = new ArrayList<Object>();


	
	public void addObject(Object obj){
		this.list.add(obj);
	}
	
	public void removeObject(Object obj){
		this.list.remove(obj);
	}

	public List<Object> getList() {
		return list;
	}

	public void setList(List<Object> list) {
		this.list = list;
	}
	
	//获得迭代器
	public MyIterator createIterator(){
		return new ConcreteIterator();
	}
	
	//使用内部类定义迭代器,可以直接使用外部类的属性
	private class ConcreteIterator implements MyIterator{

		private int cursor;  //定义游标用于记录遍历时的位置
		
		@Override
		public void first() {
			cursor = 0;
		}

		@Override
		public void next() {
			 if(cursor<list.size()){
				 cursor++;
			 }
		}

		@Override
		public boolean hasNext() {
			if(cursor<list.size()){
				return true;
			}
			return false;
		}

		@Override
		public boolean isFirst() {
			return cursor==0?true:false;
		}

		@Override
		public boolean isLast() {
			return ( cursor==(list.size()-1) )?true:false;
		}

		@Override
		public Object getCurrentObje() {
			return list.get(cursor);
		}
	}
}

测试类:

public class client {
	public static void main(String[] args) {
		ConcreteMyAggregate cma = new ConcreteMyAggregate();
		cma.addObject("aa");
		cma.addObject("bb");
		cma.addObject("cc");
		
		MyIterator iter = cma.createIterator();
		while(iter.hasNext()){
			System.out.println(iter.getCurrentObje());
			iter.next();
		}
	}
}

测试结果:
aa
bb
cc




中介者模式(Mediator)


核心:

  • 如果一个系统中对象之间的联系呈现为网状结构,对象之间存在大量多对多关系,将导致关系及其复杂,这些对象称为“同事对象”
  • 我们可以引入一个中介者对象,使各个同事对象只跟中介者对象打交道,将复杂的网络结构化解为如下的星形结构。
    在这里插入图片描述

结构图:
在这里插入图片描述
中介者模式的本质:

  • 解耦多个同事对象之间的交互关系。每个对象都持有中介者对象的引用,只跟中介者对象打交道。我们通过中介者对象统一管理这些交互关系

开发中常见的场景:

  • MVC模式(其中的C ,控制器就是一个中介者对象。M和V都和他打交道)

  • 窗口游戏程序,窗口软件开发中窗口对象也是一个中介者对象

  • 图形界面开发GUI中,多个组件之间的交互,可以通过引入一个中介者对象来解决,可以是整体的窗口对象或者DOM对象

  • Java.lang.reflect.Method#invoke()

下面以总经理和各部门的关系进行举例:

代码实现:

部门类的接口:

//部门类的接口
public interface Department {
	void selfAction();  //做本部门的事情
	void outAction();   //向总经理发出申请
}

中介者接口:

//中介者接口
public interface Mediator {
	void register(String dname, Department d);  //注册部门
	void command(String dname);  //发布命令
}

研发部:

//研发部
public class Development implements Department {

	private Mediator m;  //持有中介者(总经理)的引用
	
	public Development(Mediator m) {
		super();
		this.m = m;
		m.register("development", this); //将当前对象注册到中介者那
	}

	@Override
	public void selfAction() {
		System.out.println("汇报工作,申请资金");
		m.command("Finacial"); //由总经理和财务部沟通
	}

	@Override
	public void outAction() {
		System.out.println("专心科研,开发项目");
	}	
}

财务部:

//财务部
public class Finacial implements Department {

	private Mediator m;  //持有中介者(总经理)的引用
	
	public Finacial(Mediator m) {
		super();
		this.m = m;
		m.register("Finacial", this); //将当前对象注册到中介者那
	}

	@Override
	public void selfAction() {
		System.out.println("汇报资金情况");
	}

	@Override
	public void outAction() {
		System.out.println("数钱");
	}
	
}
//市场部
public class Market implements Department {

	private Mediator m;  //持有中介者(总经理)的引用
	
	public Market(Mediator m) {
		super();
		this.m = m;
		m.register("Market", this); //将当前对象注册到中介者那
	}

	@Override
	public void selfAction() {
		System.out.println("汇报市场调查情况");
	}

	@Override
	public void outAction() {
		System.out.println("做市场调查,接项目,需要资金");
		m.command("Finacial"); //由总经理和财务部沟通
	}	
}

总经理类(中介者):

import java.util.HashMap;
import java.util.Map;

//总经理类(中介者)
public class President implements Mediator{
	private Map<String, Department> map = new HashMap<String, Department>();

	@Override
	public void register(String dname, Department d) {
		map.put(dname, d);
	}

	@Override
	public void command(String dname) {
		map.get(dname).selfAction();
	}
}

测试类:

//测试类
public class client {
	public static void main(String[] args) {
		Mediator m = new President();
		
		Market market = new Market(m);
		Development devp = new Development(m);
		Finacial f = new Finacial(m);
		
		market.selfAction();
		market.outAction();
	}
}

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




命令模式(command)(不常用)


介绍:

  • 命令模式:将-一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。也称之为:动作Action模式、事务transaction模式

结构:

  • Command抽象命令类
  • ConcreteCommand具体命令类
  • Invoker调用者/请求者
    ●请求的发送者,它通过命令对象来执行请求。一一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联。在程序运行时,将调用命令对象的execute0 ,间接调用接收者的相关操作。
  • Receiver接收者
    ●接收者执行与请求相关的操作,具体实现对请求的业务处理。
    ●未抽象前,实际执行操作内容的对象。
  • Client客户类
    ●在客户类中需要创建调用者对象、具体命令类对象,在创建具体命令对象时指定对应的接收者。发送者和接收者之间没有直接关系,都通过命令对象间接调用。

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

开发中常见的场景:

  • Struts2中 , action的整个调用过程中就有命令模式。
  • 数据库事务机制的底层实现
  • 命令的撤销和恢复



解释器模式(interpreter)(不常用)


介绍:

  • 是一种不常用的设计模式
  • 用于描述如何构成一 个简单的语言解释器,主要用于使用面向对象语言开发的编译器和解释器设计。
  • 当我们需要开发一种新的语言时,可以考虑使用解释器模式。
  • 尽量不要使用解释器模式,后期维护会有很大麻烦。在项目中,可以使用Jruby , Groovy、java的js引擎来替代解释器的作用,弥补java语言的不足。

应用的场景:

  • EL表达式式的处理
  • 正则表达式解释器
  • SQL语法的解释器
  • 数学表达式解析器



访问者模式 (visitor)(不常用)


模式动机:

  • 对于存储在一个集合中的对象,他们可能具有不同的类型(即使有一个公共的接口) ,对于该集合中的对象,可以接受一类称为访问者的对象来访问 ,不同的访问者其访问方式也有所不同。

定义:

  • 表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变个元素的类的前提下定义作用于这些元素的新操作。

开发中的场景(应用范围非常窄,了 解即可) :

  • XML文档解析器设计
  • 编译器的设计
  • 复杂集合对象的处理



策略模式 (strategy)(常用)


介绍:

  • 策略模式对应于解决某一个问题的一 个算法族,允许用户从该算法族中任选一个算法解决某一问题 ,同时可以方便的更换算法或者增加新的算法。并且由客户端决定调用哪个算法。

场景:

  • 某个市场人员接到单后的报价策略(CRM系统中常见问题)。报价策略很复杂,可以简单作如下分类:
    • 普通客户小批量报价
    • 普通客户大批量报价
    • 老客户小批量报价
    • 老客户大批量报价
  • 具体选用哪个报价策略,这需要根据实际情况来确定。这时候,我们采用策略模式即可。

对于上面这个场景,不采用策略模式的情况下,我们可以使用条件语句(if else)判断处理。但是,假如,类型特别多,算法比较复杂时,整个条件控制代码会变得很长,难于维护。

开发中常见的场景:

  • JAVASE中GUI编程中,布局管理
  • Spring框架中, Resource接口,资源访问策略
  • javax.servlet.http.HttpServlet#service()

实现报价场景的代码:

//策略接口
public interface Strategy {
	public double getPrice(double standardPrice);
}

新客户小批量策略:

//新客户小批量策略
public class NewCustomerFewStrategy implements Strategy{

	@Override
	public double getPrice(double standardPrice) {
		System.out.println("不打折,原价");
		return standardPrice;
	}
}

新客户大批量策略:

//新客户大批量策略
public class NewCustomerManyStrategy implements Strategy{

	@Override
	public double getPrice(double standardPrice) {
		System.out.println("九折");
		return standardPrice*0.9;
	}
}

老客户小批量策略:

//老客户小批量策略
public class OldCustomerFewStrategy implements Strategy{

	@Override
	public double getPrice(double standardPrice) {
		System.out.println("八五折");
		return standardPrice*0.85;
	}
}

老客户大批量策略:

//老客户大批量策略
public class OldCustomerManyStrategy implements Strategy{

	@Override
	public double getPrice(double standardPrice) {
		System.out.println("八折");
		return standardPrice*0.8;
	}
}

实现交换的类:

/**
 * 负责和具体的策略类交互
 * 这样的话,具体的算法和直接的客户端调用分离了,使得算法可以独立于客户端独立的变化
 */
public class Context {
	private Strategy strategy;  //当前采用的算法对象

	//可以通过构造器注入
	public Context(Strategy strategy) {
		super();
		this.strategy = strategy;
	}

	//通过set方法注入
	public void setStrategy(Strategy strategy) {
		this.strategy = strategy;
	}
	
	public void pringPrice(double s){
		System.out.println("您的报价为:"+strategy.getPrice(s));
	}
}

测试类:

//测试类
public class Client {
	public static void main(String[] args) {
		Strategy s1 = new OldCustomerManyStrategy();//老客户
		Context ctx = new Context(s1);
		
		ctx.pringPrice(100);
	}
}

测试结果:
八折
您的报价为:80.0




模板方法模式 template method (常用)


模板方法模式介绍:

  • 模板方法模式是编程中经常用得到模式。它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。这样,新的子类可以在不改变一个算法结构的前提下重新定义该算法的某些特定步骤。

核心:

  • 处理某个流程的代码已经都具备,但是其中某个节点的代码暂时不能确定。因此,我们采用工厂方法模式,将这个节点的代码实现转移给子类完成。即:处理步骤父类中定义好,具体实现延迟到子类中定义

案例代码:
在这里插入图片描述

什么时候用到模板方法模式:

  • 实现一个算法时,整体步骤很固定。但是,某些部分易变。易变部分可以抽象成出来,供子类实现。

开发中常见的场景:

  • 非常频繁。各个框架、类库中都有他的影子。比如常见的有:
    • 数据库访问的封装
    • Junit单元测试
    • servlet中关 于doGet/doPost方法调用
    • Hibernate中模板程序
    • spring中JDBCTemplate、 HibernateTemplate等。

代码实现:

public abstract class BankTempLateMethod {
	//具体方法
	public void takeNumber(){
		System.out.println("区号排队");
	}
	
	public abstract void transact();//办理具体的业务
	
	public void evaluate(){
		System.out.println("反馈评分");
	}
	
	//因为所有业务的处理步骤是相同的,所以final修饰,之后的子类都不能修改
	public final void process(){  //模板方法
		this.takeNumber();
		this.transact();
		this.evaluate();
	}
}

测试类:

public class Client {
	public static void main(String[] args) {
		BankTempLateMethod btm = new DrawMoney();
		btm.process();
		
		System.out.println("-----------------");  //分隔符
		
		//采用匿名内部类实现(和使用匿名内部类重写run()方法建立线程类似)
		BankTempLateMethod btm2 = new BankTempLateMethod(){

			@Override
			public void transact() {
				System.out.println("我要贷款");
			}
		};
		btm.process();
	}
}

class DrawMoney extends BankTempLateMethod {
	@Override
	public void transact() {
		System.out.println("我要取款");
	}
}

测试结果:

区号排队
我要取款
反馈评分
-----------------
区号排队
我要取款
反馈评分



状态模式 (state)


场景:
酒店系统中,房间的状态变化:

  • 已预订
  • 已入住
  • 空闲

在这里插入图片描述

当遇到这种需要频繁修改状态时,可以考虑状态模式。

核心:

  • 用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题

结构:

  • Context环境类
    环境类中维护一个State对象 ,他是定义了当前的状态。
  • State抽象状态类
  • ConcreteState具体状态类
    每一个类封装了-个状态对应的行为

开发中常见的场景:

  • 银行系统中账号状态的管理
  • OA系统中公文状态的管理
  • 酒店系统中,房间状态的管理
  • 线程对象各状态之间的切换

代码实现:

//状态接口
public interface State {
	void handle();
}

空闲状态:

/**
 * 空闲状态
 */
public class FreeState implements State{

	@Override
	public void handle() {
		System.out.println("空闲状态,可以入住");
	}
}

已预订状态:

/**
 * 已预订状态
 */
public class BookedState implements State{

	@Override
	public void handle() {
		System.out.println("已预订状态,不可入住");
	}
}

已入住状态:

/**
 * 已入住状态
 */
public class CheckedInState implements State{

	@Override
	public void handle() {
		System.out.println("房间已入住,请勿打扰");
	}
}

房间类:

/**
 * 房间类
 */
public class Context {
	//如果是银行系统,这个Context类就是账号,根据金额的不同,切换不同的状态
	
	private State state;
	
	public void setState(State s){
		System.out.println("修改状态!");
		state = s;
		state.handle();
	}
}

测试类:

/**
 * 测试类
 */
public class Client {
	public static void main(String[] args) {
		Context ctx = new Context();
		
		ctx.setState(new FreeState());
		ctx.setState(new BookedState());
	}
}

测试结果:

修改状态!
空闲状态,可以入住
修改状态!
已预订状态,不可入住

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




观察者模式 (observer)(常用)


场景:

  • 聊天室程序的创建。服务器创建好后, A,B,C3三个客户端连上来公开聊.天。A向服务器发送数据,服务器端聊天数据改变。我们希望将这些聊天数据分别发给其他在线的客户。也就是说,每个客户端需要更新服务器端得数据。
  • 网站上,很多人订阅了”java主题”的新闻。当有这个主题新闻时,就会将这些新闻发给所有订阅的人。
  • 大家一起玩CS游戏时,服务器需要将每个人的方位变化发给所有的客户。

  上面这些场景,我们都可以使用观察者模式来处理。我们可以把多个订阅者、客户称之为观察者;需要同步给多个订阅者的数据封装到对象中,称之为目标

核心:

  • 观察者模式主要用于1 : N的通知。当一个对象(目标对象Subject或Objservable)的状态变化时,他需要及时告知一系列对象(观察者对象,Observer) ,令他们做出响应
  • 通知观察者的方式:
    • 推:每次都会把通知以广播方式发送给所有观察者,所有观察者只能被动接收。
    • 拉:观察者只要知道有情况即可。至于什么时候获取内容,获取什么内容,都可以自己决定。

开发中常见的场景:

  • 聊天室程序的,服务器转发给所有客户端
  • 网络游戏(多 人联机对战)场景中,服务器将客户端的状态进行分发
  • 邮件订阅
  • Servlet中 ,监听器的实现
  • Android中 ,广播机制
  • JDK的AWT中事件处理模型,基于观察者模式的委派事件模型(Delegation EventModel)
    事件-----------目标对象
    事件监听--------观察者
  • 京东商城中,群发某商品打折信息

代码实现:

/**
 * 观察者接口
 */
public interface Observer {
	void update(Subject subject);
}
/**
 * 目标类(相当于广播)
 */
public class Subject {
	protected List<Observer>  list = new ArrayList<Observer>();
	
	//注册观察者
	public void registerObserver(Observer obs){
		list.add(obs);
	}
	
	//删除观察者
	public void removeObserver(Observer obs){
		list.remove(obs);
	}
	
	//通知所有的观察者更新状态
	public void notifyAllObservers(){
		for (Observer objs : list) {
			objs.update(this);
		}
	}
}

目标对象:

public class ConcreteSubject extends Subject{
	private int state;
	
	public int getState(){
		return state;
	}
	
	public void setState(int state){
		this.state = state;
		//主题对象(目标对象)值发生了变化,通知所有观察者
		this.notifyAllObservers();
	}
}
/**
 * 观察者
 */
public class ObserverA implements Observer{

	private int myState;  //需要跟目标对象的state值保持一致
	
	public int getMyState() {
		return myState;
	}

	public void setMyState(int myState) {
		this.myState = myState;
	}

	@Override
	public void update(Subject subject) {
		myState = ((ConcreteSubject)subject).getState();
	}
}

测试类:

//测试类
public class Client {
	public static void main(String[] args) {
		//目标对象
		ConcreteSubject subject = new ConcreteSubject();
		
		//创建多个观察者
		ObserverA obs1 = new ObserverA();
		ObserverA obs2 = new ObserverA();
		ObserverA obs3 = new ObserverA();
		
		//将这三个观察者添加到subject对象的观察者队伍中
		subject.registerObserver(obs1);
		subject.registerObserver(obs2);
		subject.registerObserver(obs3);
		
		//改变subject的状态
		subject.setState(3000);
		
		//查看观察者的状态是否发生变化
		System.out.println(obs1.getMyState());
		System.out.println(obs2.getMyState());
		System.out.println(obs3.getMyState());
	}
}

测试结果:
3000
3000
3000

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

  其实,JAVASE提供了java.util.Observablejava.util.Observer来实现观察者模式。不需要我们再创建。
  下面用JDK提供的java.util.Observablejava.util.Observer来实现观察者模式:

目标类:

import java.util.Observable;

public class ConcreteSubject extends Observable{
	
	private int state;
	
	public void set(int s){
		state = s;    //目标对象的状态发生了改变
		
		setChanged();  //表示目标对象已经做了更改
		notifyObservers(state);  //通知所有的观察者
	}
	
	public int getState() {
		return state;
	}

	public void setState(int state) {
		this.state = state;
	}
	
}

观察者类:

import java.util.Observable;
import java.util.Observer;

public class ObserverA implements Observer{

	private int myState;
	
	@Override
	public void update(Observable o, Object arg) {
		myState = ((ConcreteSubject)o).getState();
	}

	public int getMyState() {
		return myState;
	}

	public void setMyState(int myState) {
		this.myState = myState;
	}
	
}

测试类:

public class Client {
	public static void main(String[] args) {
		//创建目标对象
		ConcreteSubject subject = new ConcreteSubject();
		
		//创建观察者
		ObserverA obs1 = new ObserverA();
		ObserverA obs2 = new ObserverA();
		ObserverA obs3 = new ObserverA();
		
		//将上面三个观察者添加到目标对象subject的观察者队列中
		subject.addObserver(obs1);
		subject.addObserver(obs2);
		subject.addObserver(obs3);
		
		//改变subject对象的状态
		subject.set(300);
		
		//观察者的状态发生了变化
		System.out.println(obs1.getMyState());
		System.out.println(obs2.getMyState());
		System.out.println(obs3.getMyState());
	}
}

测试结果:
300
300
300




备忘录模式(memento)


场景:

  • 录入大批人员资料。正在录入当前人资料时,发现上-一个人录错了,此时需要恢复上一个人的资料,再进行修改。
  • Word文档编辑时,忽然电脑死机或断电,再打开时,可以看到word提示你恢复到以前的文档
  • 管理系统中,公文撤回功能。公文发送出去后,想撤回来。

核心:

  • 就是保存某个对象内部状态的拷贝,这样以后就可以将该对象恢复到原先的状态。

结构:

  • 源发器类Originator
  • 备忘录类Memento
  • 负责人类CareTake

开发中常见的应用场景:

  • 棋类游戏中的,悔棋
  • 普通软件中的,撤销操作
  • 数据库软件中的, 事务管理中的,回滚操作
  • Rhotoshop软件中的,历史记录

代码实现:

源发器类:

package 备忘录模式;
/**
 * 源发器类
 */
public class Emp {
	private String ename;
	private int age;
	private double salary;
	
	//进行数据恢复,并返回备忘录对象
	public EmpMemento memento(){
		return new EmpMemento(this);
	}
	
	//进行数据恢复,恢复成指定备忘录对象的值
	public void recover(EmpMemento mmt){
		this.ename = mmt.getEname();
		this.age = mmt.getAge();
		this.salary = mmt.getSalary();
	}
	
	public Emp(String ename, int age, double salary) {
		super();
		this.ename = ename;
		this.age = age;
		this.salary = salary;
	}
	public String getEname() {
		return ename;
	}
	public void setEname(String ename) {
		this.ename = ename;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
}
package 备忘录模式;
/**
 * 备忘录类
 */
public class EmpMemento {
	private String ename;
	private int age;
	private double salary;
	
	public EmpMemento(Emp e){
		this.ename = e.getEname();
		this.age = e.getAge();
		this.salary = e.getSalary();
	}
	public String getEname() {
		return ename;
	}
	public void setEname(String ename) {
		this.ename = ename;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
}
/**
 * 负责人类
 * 负责管理备忘录对象
 */
public class CareTaker {
	private EmpMemento memento;

	//备忘录比较多时,可以使用容器进行存储,或者是用栈进行存储
//	private List<EmpMemento> list = new ArrayList<EmpMemento>();
	
	public EmpMemento getMemento() {
		return memento;
	}

	public void setMemento(EmpMemento memento) {
		this.memento = memento;
	}
}

测试类:

public class Client {
	public static void main(String[] args) {
		CareTaker taker = new CareTaker();
		
		Emp emp = new Emp("王一", 18, 900);
		System.out.println("第一次打印对象:"+emp.getEname()+"---"+emp.getAge()+"---"+emp.getSalary());
		
		taker.setMemento(emp.memento());  //备忘一次
		
		emp.setAge(38);
		emp.setEname("王二");
		emp.setSalary(9000);
		System.out.println("第二次打印对象:"+emp.getEname()+"---"+emp.getAge()+"---"+emp.getSalary());
		
		emp.recover(taker.getMemento());  //恢复到备忘录对象保存的状态
		
		System.out.println("第三次打印对象:"+emp.getEname()+"---"+emp.getAge()+"---"+emp.getSalary());
	}
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥自在”

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值