《JAVA编程思想》中暗藏的设计模式


前言

很久没有看有关编程思想的书籍,近日重温《JAVA编程思想》,又颇受一些启发,本文章将从设计模式的角度介绍《JAVA编程思想》,下面将从如下几个设计模式进行介绍:

一、状态模式

1. 使用场景

状态的变化引起行为的变化

2. 代码引入

import static net.mindview.util.Print.*;
class Actor{
	public void act(){};
}
class HappyActor extends Actor{
	public void act(){
		print("HappyActor");
	}
}
class SadActor extends Actor{
	public void act(){
		print("SadActor");
	}
}
class Stage{
	private Actor actor = new HappyActor();
	public void change(){
		actor = new SadActor();
	}
	public void performPlay(){
		actor.act();
	}
}

public class Transmogrify(){
	public static void main(String[] args){
		Stage stage = new Stage();
		stage.performPlay();
		stage.change();
		stage.performPlay();
	}
}
/*
Output:
HappyActor
SadActor
*/

3. 代码解析

这段代码中,Actor根据状态的不同产生了两个不同的子类,HappyActor,SadActor。Stage包含了一个状态字段actor,初始化为HappyActor,当调用stage.change()改变状态为SadActor时,stage的performPlay()的行为随之改变。其模式的优点是将行为的转换进行了封装,但由于状态的新增需要对包含状态的类进行修改新增状态判断条件,使得对开闭原则支持的不是很友好。

二、策略模式

1. 使用场景

能根据参数对象的不同而具有不同的行为的能力

2. 代码引入

package interfaces.classprocessor;
import java.util.*;
import static net.mindview.util.Print.*;

class Processor{
	public String name(){
		return getClass().getSimpleName();
	}
	Object process(Object input){
		return input;
	}
}
class Upcase extends Processor{
	String process(Object input){
		return ((String)input).toUpperCase();
	}
}
class Downcase extends Processor{
	String process(Object input){
		return ((String)input).toLowerCase();
	}
}
class Splitter extends Processor{
	String process(Object input){
		return Arrays.toString(((String)input).split(" "));
	}
}
public class Apply{
	public static void process(Processor p, Object s){
		print("Using Processor " + p.name());
		print(p.process(s));
	}
	public static String s = "Disagreement with beliefs is by definition incorrect";
	public static void main(String[] args){
		process(new Upcase(), s);
		process(new Downcase(), s);
		process(new Splitter(), s);
	}
}
/*
Output:
Using Processor Upcase
DISAGREEMENT WITH BELIEFS IS BY DEFINITION INCORRECT
Using Processor Downcase
disagreement with beliefs is by definition incorrect
Using Processor Splitter
[Disagreement, with, beliefs, is, by, definition, incorrect]
*/

3. 代码解析

这段代码中Apply.process()方法可以接受任何类型的Processor,并将其应用到一个Object对象上,然后打印。这类方法包含所要执行的算法中固定不变的部分,而 “策略” 包含变化的部分。本段代码中的策略有三种类型:Upcase,Downcase,Splitter,根据传入的参数对象从而产生不同的行为。这种模式的优点是策略可以自由切换,扩展性好。但是代码维护后期相关的策略可能会膨胀,且若策略提取不当会导致代码混乱。此模式适合优化代码的时候采用。

三、工厂模式

1. 使用场景

能够将创建对象的处理逻辑进行封装,从而将创建的细节进行隐藏,不会对客户端暴露。

2. 代码引入

import static net.mindview.util.Print.*;

interface Service{
	void method1();
	void method2();
}
interface ServiceFactory{
	Service getService();
}
class Implementation1 implements Service{
	Implementation1(){} // Package access
	public void method1(){
		print("Implementation1 method1");
	}
	public void method2(){
		print("Implementation1 method2");
	}
}
class Implementation1Factory implements Service{
	public Service getService(){
		return new Implementation1();
	}
}
class Implementation2 implements Service{
	Implementation2(){} // Package access
	public void method1(){
		print("Implementation2 method1");
	}
	public void method2(){
		print("Implementation2 method2");
	}
}
class Implementation2Factory implements ServiceFactory{
	public Service getService(){
		return new Implementation2();
	}
}
public class Factories{
	public static void serviceConsumer(ServiceFactory fact){
		Service s = fact.getService();
		s.method1();
		s.method2();
	}
	public static void main(String[] args){
		serviceConsumer(new Implementation1Factory());
		//Implementations are completely interchangeable;
		serviceConsumer(new Implementation2Factory());
	}
}
/*
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
*/

3. 代码解析

这段代码中我们通过工厂模式将具体的Service创建进行封装,从而隐藏了各种具体不同的Service实现类。如果不是用工厂模式,代码就必须在某处指定要创建的Service确切类型,从而调用合适的构造器,增加了复杂度。作为一种创建类的模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。而简单的对象,如果用工厂模式则可能由于增加一个工厂类从而增加系统的复杂度。

四、适配器模式

1. 使用场景

作为两个不兼容接口之间的桥梁,结合了两个独立接口的功能,通常用于兼容古老的遗传代码。

2. 代码引入

import java.util.*;

class ReversibleArrayList<T> extends ArrayList<T>{
	public ReversibleArrayList(Collection<T> c){
		super(c);
	}
	public Iterable<T> reversed(){
		return new Iterable<T>(){
			public Iterator<T> iterator(){
				return new Iterator<T>(){
					int current = size() - 1;
					public boolean hasNext(){
						return current > -1;
					}
					public T next(){
						return get(current--);
					}
					public void remove(){
						throw new UnsupportedOperationException();
					}
				};
			}
		};
	}
}
public class AdapterMethodIdiom{
	public static void main(String[] args){
		ReversibleArrayList<String> ral = new ReversibleArrayList<String>(Arrays.asList("To be or not to be".split(" ")));
		for(String s : ral){
			System.out.print(s + " ");
		}
		System.out.println();
		for(String s : ral.reversed()){
			System.out.print(s + " ");
		}
	}
}
/* Output:
To be or not to be
be to not or be To
*/

3. 代码解析

这段代码中我们通过适配器模式将ArrayList的功能进行了扩展,添加了产生反向迭代器的能力,而并没有变动原始ArrayList的功能。适配器模式可以让两个关联度不高的类融合到一起,提高了类的复用,灵活性较好,但若使用得不恰当就如牛头对马嘴很是不协调。适配器模式的使用应尽量避免,如能重构代码尽量重构代码,实在不行再采用此模式。

五、代理模式

1. 使用场景

将类的职能细化,从原始类中剥离出另一职能,交给新的代理类进行处理,原始类处理核心部分功能,代理类处理附属功能,也称代理类扮演着中间人的角色。

2. 代码引入

/**
	静态代理
**/
import static net.mindview.util.Print.*;
interface Interface{
	void doSomething();
	void somethingElse(String arg);
}
class RealObject implements Interface{
	public void doSomething(){
		print("doSomething");
	}
	public void somethingElse(String arg){
		print("somethingElse " + arg);
	}
}
class SimpleProxy implements Interface{
	private Interface proxied;
	public SimpleProxy(Interface proxied){
		this.proxied = proxied;
	}
	public void doSomething(){
		print("SimpleProxy doSomething");
		proxied.doSomething();
	}
	public void somethingElse(String arg){
		print("SimpleProxy somethingElse " + arg);
		proxied.somethingElse(arg);
	}
}
class SimpleProxyDemo{
	public static void consumer(Interface iface){
		iface.doSomething();
		iface.somethingElse("bonobo");
	}
	public static void main(String[] args){
		consumer(new RealObject());
		consumer(new SimpleProxy(new RealObject()));
	}
}
/* Output:
doSomething
somethingElse bonobo
SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse bonobo
*/
/**
	动态代理
**/
import java.lang.reflect.*;
class DynamicProxyHandler implements InvocationHandler{
	private Object proxied;
	public DynamicProxyHandler(Object proxied){
		this.proxied = proxied;
	}
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
		System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args);
		if(args != null){
			for(Object arg : args){
				System.out.println(" " + arg);
			}
		}
		return method.invoke(proxied, args);
	}
}
class SimpleDynamicProxy{
	public static void consumer(Interface iface){
		iface.doSomething();
		iface.somethingElse("bonobo");
	}
	public static void main(String[] args){
		RealObject real = new RealObject();
		consumer(real);
		Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(real));
		consumer(proxy);
	}
}
/* Output:
doSomething
somethingElse bonobo
**** proxy: class $Proxy0, method: public abstract void
Interface.doSomething(), args: null
doSomething
**** proxy: class $Proxy0, method: public abstract void 
Interface.somethingElse(java.lang.String), args:
[Ljava.lang.Object;@42e816
  bonobo
somethingElse bonobo
*/

3. 代码解析

这段代码中consumer()接受Interface,所以它无法知道正在获取的到底是RealObject还是SimpleProxy,因为这二者都实现了Interface。但是SimpleProxy已经被插入到了客户端和RealObject之间,因此它会执行操作,然后调用RealObject上相同的方法。在任何时刻,只要你想要将额外的操作从“实际”对象中分离到不同的地方,代理类就显得非常有用,但可能会增加代码的复杂度。

六、装饰器模式

1. 使用场景

允许向一个现有的对象添加新的功能,同时又不改变其结构,在保持原有类方法签名完整性的前提下,提供了额外的功能。

2. 代码引入

package generics.decorator;
import java.util.*;

class Basic{
	private String value;
	public void set(String val){
		value = val;
	}
	public String get(){
		return value;
	}
}
class Decorator extends Basic{
	protected Basic basic;
	public Decorator(Basic basic){
		this.basic = basic;
	}
	public void set(String val){
		basic.set(val);
	}
	public String get(){
		return basic.get();
	}
}
class TimeStamped extends Decorator{
	private final long timeStamp;
	public TimeStamped(Basic basic){
		super(basic);
		timeStamp = new Date().getTime();
	}
	public long getStamp(){
		return timeStamp;
	}
}
class SerialNumbered extends Decorator{
	private static long counter = 1;
	private final long serialNumber = counter++;
	public SerialNumbered(Basic basic){
		super(basic);
	}
	public long getSerialNumber(){
		return serialNumber;
	}
}
public class Decoration{
	public static void main(String[] args){
		TimeStamped t = new TimeStamped(new Basic());
		TimeStamped t2 = new TimeStamped(new SerialNumbered(new Basic()));
		SerialNumbered s = new SerialNumbered(new Basic());
		SerialNumbered s2 = new SerialNumbered(new TimeStamped(new Basic()));
	}
}

3. 代码解析

这段代码中我们通过装饰器模式用两种不同的装饰器TimeStamped和SerialNumbered对Basic类型进行封装,为Basic动态透明地添加了新的责任。装饰器模式使用分层对象来动态透明地向单个对象添加责任,装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。但是多层装饰会导致代码变得复杂冗余。

七、命令模式

1. 使用场景

命令模式是一种数据驱动的设计模式,请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

2. 代码引入

package enumerated;
import java.util.*;
import static enumerated.AlarmPoints.*;
import static net.mindview.util.Print.*;
enum AlarmPoints {
  STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
  OFFICE4, BATHROOM, UTILITY, KITCHEN
}
interface Command{
	void action();
}
public class EnumMaps{
	public static void main(String[] args){
		EnumMap<AlarmPoints, Command> em = new EnumMap<AlarmPoints, Command>(AlarmPoints.class);
		em.put(KITCHEN, new Command(){
			public void action(){
				print("Kitchen fire!");
			}
		});
		em.put(BATHROOM, new Command(){
			public void action(){
				print("Bathroom alert!");
			}
		});
		for(Map.Entry<AlarmPoints, Command> e : em.entrySet()){
			printnb(e.getKey() + ": ");
			e.getValue().action();
		}
		try{
			em.get(UTILITY).action();
		}catch(Exception e){
			print(e);
		}
	}
}
/*
Output:
BATHROOM: Bathroom alert!
KITCHEN: Kitchen fire!
java.lang.NullPointerException
*/

3. 代码解析

这段代码中我们通过命令模式将不同的命令保存在EnumMap中,从而统一具有各个不同实现的命令,命令模式首先需要一个只有单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类,其模式的使用降低了系统的耦合度,从而新的命令可以很容易添加到系统中。

八、责任链模式

1. 使用场景

责任链模式为请求创建了一个接收者对象的链,这种模式给予请求的类型,对请求的发送者和接收者进行解耦,从而使客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递。

2. 代码引入

import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*;

class Mail{
	enum GeneralDelivery{
		YES, NO1, NO2, NO3, NO4, NO5
	}
	enum Scannability{
		UNSCANNABLE, YES1, YES2, YES3, YES4
	}
	enum Readability{
		ILLEGIBLE, YES1, YES2, YES3, YES4
	}
	enum Address{
		INCORRECT, OK1, OK2, OK3, OK4, OK5, OK6
	}
	enum ReturnAddress{
		MISSING, OK1, OK2, OK3, OK4, OK5
	}
	GeneralDelivery generalDelivery;
	Scannability readability;
	Address address;
	ReturnAddress returnAddress;
	static long counter = 0;
	long id = counter++;
	public String toString(){
		return "Mail " + id;
	}
	public String details(){
		return toString() + 
		", General Delivery: " + generalDelivery +
		", Address Scanability: " + scannability +
		", Address Readability: " + readability +
		", Address Address: " + address +
		", Return address: " + returnAddress;
	}
	public static Mail randomMail(){
		Mail m = new Mail();
		m.generalDelivery = Enums.random(GeneralDelivery.class);
		m.scannability = Enums.random(Scannability.class);
		m.readability = Enums.random(Readability.class);
		m.address = Enums.random(Address.class);
		m.returnAddress = Enums.random(ReturnAddress.class);
		return m;
	}
	public static Iterable<Mail> generator(final int count){
		return new Iterable<Mail>(){
			int n = count;
			public Iterator<Mail> iterator(){
				return new Iterator<Mail>(){
					public boolean hasNext(){
						return n-- > 0;
					}
					public Mail next(){
						return randomMail();
					}
					public void remove(){
						throw new UnsupportedOperationException();
					}
				};
			}
		};
	}
}
public class PostOffice{
	enum MailHandler{
		GENERAL_DELIVERY{
			boolean handle(Mail m){
				switch(m.generalDelivery){
					case YES:
						print("Using general delivery for " + m);
						return true;
					default:
						return false;
				}
			}
		},
		MACHINE_SCAN{
			boolean handle(Mail m){
				switch(m.scannability){
					case UNSCANNABLE:
						return false;
					default:
						switch(m.address){
							case INCORRECT:
								return false;
							default:
								print("Delivering " + m + " automatically");
								return true;
						}
				}
			}
		},
		VISUAL_INSPECTION{
			boolean handle(Mail m){
				switch(m.readability){
					case ILLEGIBLE: return false;
					default:
						switch(m.address){
							case INCORRECT: return false;
							default:
								print("Delivering " + m + " normally");
								return true;
						}
				}
			}
		},
		RETURN_TO_SENDER{
			boolean handle(Mail e){
				switch(m.returnAddress){
					case MISSING:
						return false;
					default:
						print("Returning " + m + " to sender");
						return true;
				}
			}
		};
		abstract boolean handle(Mail m);
	}
	static void handle(Mail m){
		for(MailHandler handler : MailHandler.values())
			if(handler.handle(m))
				return;
		print(m + " is a dead letter");
	}
	public static void main(String[] args){
		for(Mail mail : Mail.generator(3)){
			print(mail.details());
			handle(mail);
			print("*****");
		}
	}
}
/*
Output:
Mail 0, General Delivery: NO2, Address Scanability:
UNSCANNABLE, Address Readability: YES3, Address Address:
OK1, Return address: OK1
Delivering Mail 0 normally
*****
Mail 1, General Delivery: NO5, Address Scanability: YES3,
Address Readability: ILLEGIBLE, Address Address: OK5,
Return address: OK1
Delivering Mail 1 automatically
*****
Mail 2, General Delivery: YES, Address Scanability: YES3,
Address Readability: YES1, Address Address: OK1, Return address: OK5
Using general delivery for Mail 2
*/

3. 代码解析

这段代码中邮件的每个关键特征都可以用enum来表示,程序将随机地生成Mail对象,邮局则需要尽可能通过通用的方式来处理每一封邮件,并且要不断尝试处理邮件,直到该邮件最终被确定为一封死信,其中的每一次尝试可以看作为一个策略,而完整的处理方式列表就是一个责任链。其优点是封装了接收方的处理链,将发送方和接收方进行了解耦。但却不容易观察运行时特征,不容易调试。

九、生产者消费者模式

1. 使用场景

生产者用于生产物品,消费者用于消费物品。消费者必须等待生产者准备好物品,当生产者准备好时,会通知消费者进行消费,从而达成任务协作。

2. 代码引入

import java.util.concurrent.*;
import static net.mindview.util.Print.*;

class Meal{
	private final int orderNum;
	public Meal(int orderNum){
		this.orderNum = orderNum;
	}
	public String toString(){
		return "Meal " + orderNum;
	}
}
class WaitPerson implements Runnable{
	private Restaurant restaurant;
	public WaitPerson(Restaurant r){
		restaurant = r;
	}
	public void run(){
		try{
			while(!Thread.interrupted()){
				synchronized(this){
					while(restaurant.meal == null){
						wait(); // ... for the chef to produce a meal
					}
				}
				print("Waitperson got " + restaurant.meal);
				synchronized(restaurant.chef){
					restaurant.meal = null;
					restaurant.chef.notifyAll(); //ready for another
				}
			}
		}catch(InterruptedException e){
			print("WaitPerson interrupted");
		}
	}
}
class Chef implements Runnable{
	private Restaurant restaurant;
	private int count = 0;
	public Chef(Restaurant r){
		restaurant = r;
	}
	public void run(){
		try{
			while(!Thread.interrupted()){
				synchronized(this){
					while(restaurant.meal != null){
						wait(); // ... for the meal to be taken
					}
				}
				if(++count == 3){
					print("Out of food, closing");
					restaurant.exec.shutdownNow();
				}
				printnb("Order up! ");
				synchronized(restaurant.waitPerson){
					restaurant.meal = new Meal(count);
					restaurant.waitPerson.notifyAll();
				}
				TimeUnit.MILLISECONDS.sleep(100);
			}
		}catch(InterruptedException e){
			print("Chef interrupted");
		}
	}
}
public class Restaurant{
	Meal meal;
	ExecutorService exec = Executors.newCachedThreadPool();
	WaitPerson waitPerson = new WaitPerson(this);
	Chef chef = new Chef(this);
	public Restaurant(){
		exec.execute(chef);
		exec.execute(waitPerson);
	}
	public static void main(String[] args){
		new Restaurant();
	}
}
/*
Output:
Order up! Waitperson got Meal 1
Order up! Waitperson got Meal 2
Order up! Waitperson got Meal 3
Out of food, closing
WaitPerson interrupted
Order up! Chef interrupted
*/

3. 代码解析

这段代码中我们采用了生产者与消费者模式,一个饭店中有一个厨师和一个服务员,这个服务员必须等待厨师准备好膳食,当厨师准备好时,他会通知服务员,之后服务员上菜,然后返回继续等待,这样的模式可以简化规范代码,并因可用并发提高代码处理的效率,但模式本身实现抽象较复杂,且并发不容易调试,需要事前进行周密的考虑。

十、享元模式

1. 使用场景

享元模式,主要用于减少创建对象的数量,从而提高内存的占用率并提高性能,其中的对象池便是实现享元模式的方式之一。

2. 代码引入

import java.util.concurrent.*;
import java.util.*;

public class Pool<T>{
	private int size;
	private List<T> items = new ArrayList<T>();
	private volatile boolean[] checkedOut;
	private Semaphore available;
	public Pool(<Class<T> classObject, int size){
		this.size = size;
		checkedOut = new boolean[size];
		available = new Semaphore(size, true);
		//Load pool with objects that can be checked out:
		for(int i=0; i<size; ++i){
			try{
				//Assumes a default constructor:
				items.add(classObject.newInstance());
			}catch(Exception e){
				throw new RuntimeException(e);
			}
		}
	}
	public T checkOut() throws InterruptedException{
		available.acquire();
		return getItem();
	}
	public void checkIn(T x){
		if(releaseItem(x)){
			available.release();
		}
	}
	private synchronized T getItem(){
		for(int i=0; i<size; ++i){
			if(!checkedOut[i]){
				checkedOut[i] = true;
				return items.get(i);
			}
		}
		return null; //Semaphore prevents reaching here
	}
	private synchronized boolean releaseItem(T item){
		int index = items.indexOf(item);
		if(index == -1) return false; //Not in the list
		if(checkedOut[index]){
			checkedOut[index] = false;
			return true;
		}
		return false; // Wasn't checked out
	}
}
public class Fat{
	private volatile double d; // Prevent optimization
	private static int counter = 0;
	private final int id = counter++;
	public Fat(){
		// Expensive, interruptible operation:
		for(int i=1; i<10000; i++){
			d += (Math.PI + Math.E) / (double)i;
		}
	}
	public void operation(){
		System.out.println(this);
	}
	public String toString(){
		return "Fat id: " + id;
	}
}
class CheckoutTask<T> implements Runnable{
	private static int counter = 0;
	private final int id = counter++;
	private Pool<T> pool;
	public CheckoutTask(Pool<T> pool){
		this.pool = pool;
	}
	public void run(){
		try{
			T item = pool.checkOut();
			print(this + "checked out " + item);
			TimeUnit.SECONDS.sleep(1);
			print(this + "checking in " + item);
			pool.checkIn(item);
		}catch(InterruptedException e){
			//Acceptable way to terminate
		}
	}
	public String toString(){
		return "CheckoutTask " + id + " ";
	}
}
public class SemaphoreDemo{
	final static int SIZE = 25;
	public static void main(String[] args) throw Exception{
		final Pool<Fat> pool = new Pool<Fat>(Fat.class, SIZE);
		ExecutorService exec = Executors.newCachedThreadPool();
		for(int i=0; i<SIZE; i++){
			exec.execute(new CheckoutTask<Fat>(pool));
		}
		print("All CheckoutTasks created");
		List<Fat> list = new ArrayList<Fat>();
		for(int i=0; i<SIZE; i++){
			Fat f = pool.checkOut();
			printnb(i + ": main() thread checked out ");
			f.operation();
			list.add(f);
		}
		Future<?> blocked = exec.submit(new Runnable(){
			public void run(){
				try{
					//Semaphonre prevents additional checkout.
					//so call is blocked:
					pool.checkOut();
				}catch(InterruptedException e){
					print("checkOut() Interrupted");
				}
			}
		});
		TimeUnit.SECONDS.sleep(2);
		blocked.cancel(true); //Break out of blocked call
		print("Checking in objects in " + list);
		for(Fat f : list){
			pool.checkIn(f);
		}
		for(Fat f : list){
			pool.checkIn(f); //Second checkIn ignored
		}
		exec.shutdown();
	}
}

3. 代码解析

这段代码中我们采用了享元模式,创建了一个Fat的共享池,客户端的调用函数需依赖Pool进行,大大减少了对象的创建,降低了系统的内存的占用。若系统需要大量相似的对象,或者需要缓冲池时使用会很好,否则只会增加系统的复杂度。

总结

设计模式是属于高层的构造思想,如果说只要符合建筑基本结构,房子就能居住,代码就能运行。那么带有一定的设计模式将会给所构造的建筑带来相应的建筑美学,给所写的代码带来相应的构造美感。但切记设计的度量。否则就会增加系统的复杂度,从而增加系统的维护成本。有时朴素也许是最好的美。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值