JAVA设计模式笔记整理(一)

针对《Head First 设计模式》做些简要的记录

1.策略模式

-----跟不同类型的MM约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目的都是为了得到MM的芳心,我的追MM锦囊中有好多Strategy哦。

定义


策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。


示例:模拟鸭子游戏,要求不同的鸭子具有不同的行为。

*行为不宜采用继承,继承会造成:

A、代码在多个子类中重复

B、运行时的行为不容易改变

C、很难知道所有鸭子的全部行为

D、改变会牵一发动全身,造成其他要不想要的改变


采用策略模式,每个鸭子的行为可以在运行时动态地决定,更加灵活也更加易于维护。

需要新的行为,只需在算法族中增加新的算法即可。

代码:

fly算法族:

public interface FlyBehavior {
	public void fly();
}

class FlyWithWings implements FlyBehavior {

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("I'm flying!!");
	}

}

class FlyNoWay implements FlyBehavior {

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("I can't fly");
	}

}

class FlyROcketBehavior implements FlyBehavior{

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("I'm flying with a rocket!");
	}
	
}

quack算法族:

public interface QuakeBehavior {
    public void quake();
}

class Quake implements QuakeBehavior{

	@Override
	public void quake() {
		// TODO Auto-generated method stub
		System.out.println("Quake");
	}
}

class MuteQuake implements QuakeBehavior{

	@Override
	public void quake() {
		// TODO Auto-generated method stub
		System.out.println("<<Silence>>");
	}
}

class Squeak implements QuakeBehavior{

	@Override
	public void quake() {
		// TODO Auto-generated method stub
		System.out.println("Squeak");
	}
	
}

抽象鸭子类:

abstract class Duck {
	FlyBehavior flyBehavior;
	QuakeBehavior quakeBehavior;

	public Duck() {
	}

	public abstract void display();

	public void performFly() {
		flyBehavior.fly();
	}

	public void performQuake() {
		quakeBehavior.quake();
	}

	public void swim() {
		System.out.println("All ducks float,even decoys!");
	}

	public void setFlyBehavior(FlyBehavior fb) {
		flyBehavior = fb; //放入fly策略
	}

	public void setQuakeBehavior(QuakeBehavior qb) {
		quakeBehavior = qb; //放入quack策略
	}
}

模型鸭:
public class ModelDuck extends Duck {
	public ModelDuck() {
		flyBehavior = new FlyNoWay();
		quakeBehavior=new Quake();
	}
	public void display(){
		System.out.println("I'm a model duck");
	}
}
绿头鸭:

public class MallardDuck extends Duck {

	public MallardDuck() {
		quakeBehavior = new Quake();
		flyBehavior = new FlyWithWings();
	}

	public void display() {
		System.out.println("I'm a real Mallard duck");
	}

}

测试:

public static void main(String[] args) {
		// TODO Auto-generated method stub
		Duck mallard = new MallardDuck();
		mallard.performQuake();
		mallard.performFly();
		Duck model=new ModelDuck();
		model.performFly();
		model.setFlyBehavior(new FlyROcketBehavior());
		model.performFly();
	}
结果:
Quake
I'm flying!!
I can't fly
I'm flying with a rocket!

总结:策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模式把行为和环境分开。环境类负责维持和查询行为类,各种算法在具体的策略类中提供。由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。


要点:

知道OO基础,并不足以让你设计出良好的OO系统。

良好的OO设计必须具备可复用、可扩充、可维护三个特性。

模式可以让我们建造出具有良好OO设计质量的系统。

模式被认为是历经验证的OO设计经验。

模式不是代码,而是针对设计问题的通用解决方案。可把他们应用到特定的应用中。

模式不是被发明,而是被发现。

大多数的模式和原则,都着眼于软件变化的主题。

大多数的模式都允许系统局部改变独立于其他部分。

我们常把系统中会变化的部分抽出来封装。

模式让开发人员之间有共享的语言,能够最大化沟通的价值。



自说自话:

好吧,这个是《Head First 设计模式》介绍的第一种模式,确实是很有意思的设计模式,把算法的实现独立开来,更加符合了松耦合的原则。不过如果要更进一步解耦,模型鸭绿皮鸭等的构造函数应该直接让具体的行为类作为参数传进去,不必new一个具体的类吧,


2.观察者模式

----想知道咱们公司最新MM情报吗?加入公司的MM情报邮件组就行了,tom负责搜集情报,他发现的新情报不用一个一个通知我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦


观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

示例:根据变动的气象数据,自动更新若干个布告板(目前状况布告板、气象统计布告板)

*若当天气数据变更时,依次更新每个布告板,会造成:

currentConditionDisplay.update(temp,humidity,pressure);
statisticsDisplay(temp,humidity,pressure);
A.、针对具体的实现编程,而非针对接口编程

B、对于每个新的布告板,我们都得修改代码

C、无法在运行时动态地增加或删除布告板

D、尚未封装改变的部分


采用观察者模式,将布告板看成一个个观察者,气象数据作为观察者订阅的主题。当主体改变时,会主动地向订阅了主题的观察者发布信息。有新的观察者时只要申请订阅主题即可收到主题的更新信息。观察者和主题之间实现了松耦合。


代码:

public interface Subject {//主题接口
	public void registerObserver(Observer o);//注册观察者
	public void removeObserver(Observer o);//移除观察者
	public void notifyObservers();//通知观察者
}
public interface DisplayElement {//为便于观察者的展示,定义此接口
	public void display();
}

}
public interface Observer {//观察者接口
	public void update(float temp,float humidity,float pressure);
}

public class WeatherData implements Subject {//实现了主题接口的气象数据
	private ArrayList<Observer> observers;
	private float temperature;
	private float humidity;
	private float pressure;

	
	public WeatherData() {
		super();
		observers=new ArrayList<Observer>();
	}
	@Override
	public void registerObserver(Observer o) {
		// TODO Auto-generated method stub
		observers.add(o);
	}
	@Override
	public void removeObserver(Observer o) {
		// TODO Auto-generated method stub
		int i=observers.indexOf(o);
		if(i>=0){
			observers.remove(i);
		}
	}
	@Override
	public void notifyObservers() {
		// TODO Auto-generated method stub
		for(Observer o:observers){
			o.update(temperature, humidity, pressure);
		}
	}
	public void measurementsChanged(){
		notifyObservers();
	}
	public void setMeasurements(float temperature,float humidity,float pressure){
		this.temperature=temperature;
		this.humidity=humidity;
		this.pressure=pressure;
		measurementsChanged();
	}
}
public class CurrentConditionsDisplay implements Observer,DisplayElement {//观察者目前状况布告板
    private float temperature;
    private float humidity;
    private Subject weatherData;
    
	public CurrentConditionsDisplay(Subject weatherData) {
		super();
		this.weatherData=weatherData;
		weatherData.registerObserver(this);
	}

	@Override
	public void update(float temp, float humidity, float pressure) {
		// TODO Auto-generated method stub
		this.temperature=temp;
		this.humidity=humidity;
		display();
	}

   
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("Current conditions: "+temperature+"F degree and"+humidity+"% humidity");
	}   
}
public class StatisticsDisplay implements DisplayElement, Observer {//观察者气象统计布告板

	private ArrayList<Float> temperatures;
	private Subject weatherData;

	public StatisticsDisplay(Subject weatherData) {
		super();
		temperatures=new ArrayList<Float>();
		this.weatherData = weatherData;
		weatherData.registerObserver(this);
	}

	@Override
	public void update(float temp, float humidity, float pressure) {
		// TODO Auto-generated method stub
		this.temperatures.add(temp);
		display();
	}
    private float avg(){
    	float sum=0;
    	for(float f:temperatures){
    		sum+=f;
    	}
    	return sum/temperatures.size();
    }
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("Avg/Max/Min temperature="+avg()+"/"+Collections.max(temperatures)+"/"+Collections.min(temperatures));
	}

}

测试:

public class WeatherStation {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
         WeatherData weatherData=new WeatherData();
         CurrentConditionsDisplay currentDisplay=new CurrentConditionsDisplay(weatherData);
         StatisticsDisplay statisticsDisplay=new StatisticsDisplay(weatherData);
         weatherData.setMeasurements(80, 65, (float)30.4);
         weatherData.setMeasurements(82,70,(float)29.2);
         weatherData.setMeasurements(78,90,(float)29.2);
	}

}

结果:

Current conditions: 80.0F degree and65.0% humidity
Avg/Max/Min temperature=80.0/80.0/80.0
Current conditions: 82.0F degree and70.0% humidity
Avg/Max/Min temperature=81.0/82.0/80.0
Current conditions: 78.0F degree and90.0% humidity
Avg/Max/Min temperature=80.0/82.0/78.0

java内置的观察者模式:java.util包中
 观察者实现观察者接口Observer,然后调用任何Observable对象的addObserver()方法。不想再当观察者时,调用deleteObserver()方法就可以。
 主题继承需要继承Observable类。状态改变后需要通知观察者时,调用setChanged()方法,标记状态已经改变的事实,然后调用notifyObservers(),或notifyObservers(Object args),参数args为传递的数据;
 观察者必须实现更新方法update(Observable o,Object args);o为通知观察者的主题,args为主题传递的数据,同notifyObservers(Object args)中的参数args

应用java内置观察者模式,重写之前代码:

public class WeatherData extends Observable {
     private float temperature;
     private float humidity;
     private float pressure;
     public WeatherData(){}
     public void measurementsChanged(){
    	 setChanged();
    	 notifyObservers();
     }
     public void setMeasurements(float temp,float humi,float pres){
    	 this.temperature=temp;
    	 this.humidity=humi;
    	 this.pressure=pres;
    	 measurementsChanged();
     }
	public float getTemperature() {
		return temperature;
	}
	public float getHumidity() {
		return humidity;
	}
	public float getPressure() {
		return pressure;
	}
     
}

public interface DisplayElement {//为便于观察者的展示,定义此接口
	public void display();
}

public class CurrentConditionsDisplay implements Observer, DisplayElement {
	private Observable observable;
	private float temperature;
	private float humidity;

	public CurrentConditionsDisplay(Observable observable) {
		this.observable = observable;
		observable.addObserver(this);
	}

	@Override
	public void update(Observable obs, Object arg) {
		// TODO Auto-generated method stub
		if (obs instanceof WeatherData) {
			WeatherData weatherData = (WeatherData) obs;
			this.temperature = weatherData.getTemperature();
			this.humidity = weatherData.getHumidity();
			display();
		}
	}
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("Current conditions: "+temperature+"F degree and"+humidity+"% humidity");
	}
}

public class StatisticsDisplay implements Observer, DisplayElement {
	private ArrayList<Float> temperatures;
	private Observable observable;

	public StatisticsDisplay(Observable observable) {
		super();
		temperatures = new ArrayList<Float>();
		this.observable = observable;
		observable.addObserver(this);
	}

	@Override
	public void update(Observable obs, Object arg) {
		// TODO Auto-generated method stub
		if (obs instanceof WeatherData) {
			WeatherData weatherData = (WeatherData) obs;
			this.temperatures.add(weatherData.getTemperature());
			display();
		}

	}

	private float avg() {
		float sum = 0;
		for (float f : temperatures) {
			sum += f;
		}
		return sum / temperatures.size();
	}

	public void display() {
		// TODO Auto-generated method stub
		System.out.println("Avg/Max/Min temperature=" + avg() + "/"
				+ Collections.max(temperatures) + "/"
				+ Collections.min(temperatures));
	}

}
public class WeatherStation {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		     WeatherData weatherData=new WeatherData();
	         CurrentConditionsDisplay currentDisplay=new CurrentConditionsDisplay(weatherData);
	         StatisticsDisplay statisticsDisplay=new StatisticsDisplay(weatherData);
	         weatherData.setMeasurements(80, 65, (float)30.4);
	         weatherData.setMeasurements(82,70,(float)29.2);
	         weatherData.setMeasurements(78,90,(float)29.2);
	}

}
*java内置的Observable类不够灵活,若代码依赖于观察者被通知的顺序,则Obervable无法实现此功能
*观察者可从主题中推数据或拉数据,第一次代码实现的是推数据,第二次代码实现的是拉数据

java中的其他观察者模式:
java的JavaBeans和Swing中也实现了观察者模式。例如Swing中的JButton采用了观察者模式,JButton是主题,在它上添加的监听器Listener是观察者。
 测试代码:

public class TestJAVA {

	private TestJAVA d=new TestJAVA();
	
	public static void main(String[] args) throws Exception {
		JFrame frame=new JFrame();
		JButton button=new JButton("Should I do");button.addActionListener(new DevilListener());
		button.addActionListener(new AngelListener());
		frame.getContentPane().add(BorderLayout.CENTER,button);
		frame.setVisible(true);	}
		

}


class AngelListener implements ActionListener{
	public void actionPerformed(ActionEvent event){
		System.out.println("Don't");
	}
}
class DevilListener implements ActionListener{
	public void actionPerformed(ActionEvent event){
		System.out.println("Come");
	}
}
*此处的观察者模式貌似也不能应用于依赖特定次序的代码。


总结:观察者模式定义了一种一队多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。

要点:

观察者模式定义了对象之间一对多的关系。

主题(也就是可观察者)用一个共同的接口来更新观察者。

观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。

使用此模式时,你可从被观察者处推(Push)或拉(Pull)数据(然而,推的方式被认为更“正确”)。

有多个观察者时,不可以依赖特定的通知顺序。

Java有多种观察者模式的实现,包括了通用的java.util.Observable。

要注意java.util。Observable实现上所带来的一些问题。

如果有必要的话,可以实现自己的Observable,这并不难。

Swing大量使用观察者模式,许多GUI框架也是如此。

此模式也被应用在许多地方,如:JavaBeans、RMI。


自说自话:

嗯,这个观察者模式可以让主题因为数据改变需要通知观察者时自动地发出推送消息,观察者也可以自主地选择要不要继续接受主题发来的信息,十分的灵活。不过还真没注意到像JButton就是一个观察者模式的体现,监听器还可以添加多个。为什么不把Observable弄成接口呢?


装饰者模式

----Mary过完轮到Sarly过生日,还是不要叫她自己挑了,不然这个月伙食费肯定玩完,拿出我去年在华山顶上照的照片,在背面写上“最好的的礼物,就是爱你的Fita”,再到街上礼品店买了个像框(卖礼品的MM也很漂亮哦),再找隔壁搞美术设计的Mike设计了一个漂亮的盒子装起来……,我们都是Decorator,最终都在修饰我这个人呀,怎么样,看懂了吗?


定义:

动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。


示例:

星巴兹(StarBuzz)咖啡连锁店准备更新订单系统

原订单系统:存在一抽象类Beverage,各种不同咖啡均由Beverage类派生

*咖啡种类有HouseBlend、DarkRoast、Decaf、Espresso

*调料种类有蒸奶SteamedMilk、豆浆Soy、摩卡Mocha、覆盖奶泡Whip

类图如下:

(简单起见只画出几个类)

可以看到这种设计方法会使类的数目大大增多。

由于类的数量太多,将其作修改

改进版:一共五个类


虽然类的数目减少了,但是这种设计方法存在这一些问题:

一旦出现新的调料,我们就要加上新的方法,并改变超类中的cost()方法;

以后可能会开发出新饮料(如Tes)。对这些饮料而言,某些调料可能并不适合,但是在这个设计方式中,Tea子类仍将继承那些不适合的方法。例如:hasWhip()


应用装饰者模式可以解决此问题。

装饰者模式的一般类图:

将上一步的类图继续加以改进,可以得到用装饰者模式进行设计的新的订单系统

类图:


代码:

Beverage:

public abstract class Beverage {
	String description="Unknown Beverage";

	
	public String getDescription(){
		return description;
	}
	public abstract double cost();
}
CondimentDecorator:

public abstract class CondimentDecorator extends Beverage {
	public abstract String getDescription();
}
Espresso:
public class Espresso extends Beverage {
	public Espresso(){
		description="Espresson";
	}
	public double cost(){
		return 1.99;
	}
}
HouseBlend:
public class HouseBlend extends Beverage {
	public HouseBlend(){
		description="House Blend Coffee";
	}
	public double cost(){
		return .89;
	}
}
DarkRoast:

public class DarkRoast extends Beverage {
	public DarkRoast(){
		description="Dark Roast";
	}
	public double cost(){
		return 0.99;
	} 
}
Mocha:
public class Mocha extends CondimentDecorator {
	Beverage beverage;
	public Mocha(Beverage beverage){
		this.beverage=beverage;
	}
	public String getDescription(){
		return beverage.getDescription()+",Mocha";
	}
	public double cost(){
		return 0.20+beverage.cost();
	}
}
Soy:
public class Soy extends CondimentDecorator {
	Beverage beverage;
	public Soy(Beverage beverage){
		this.beverage=beverage;
	}
	public String getDescription(){
		return beverage.getDescription()+",Soy";
	}
	public double cost(){
		return .15+beverage.cost();
	}
}
Whip:
public class Whip extends CondimentDecorator {
	Beverage beverage;
	public Whip(Beverage beverage){
		this.beverage=beverage;
	}
	public String getDescription(){
		return beverage.getDescription()+",Whip";
	}
	public double cost(){
		return .10+beverage.cost();
	}
}

测试:

public class StarbuzzCoffee {
	public static void main(String[] args){
	Beverage beverage=new Espresso();
	System.out.println(beverage.getDescription()+" $"+beverage.cost());
//	System.out.println(beverage+" $"+beverage.cost());
	Beverage beverage2= new DarkRoast(){};
	beverage2=new Mocha(beverage2);
	beverage2=new Mocha(beverage2);
	beverage2=new Whip(beverage2);
	beverage2=new Mocha(beverage2);
	System.out.println(beverage2.getDescription()+" $"+beverage2.cost());
//	System.out.println(beverage2+" $"+beverage2.cost());
	Beverage beverage3=new HouseBlend();
	beverage3=new Soy(beverage3);
	beverage3=new Mocha(beverage3);
	beverage3=new Whip(beverage3);
	System.out.println(beverage3.getDescription()+" $"+beverage3.cost());
	}
}
结果:
Espresson $1.99
Dark Roast,Mocha,Mocha,Whip,Mocha $1.69
House Blend Coffee,Soy,Mocha,Whip $1.34

虽然整体目标已经达到,但是通过实例代码会发现有些小问题。当客人点的咖啡为DarkRoast加2倍摩卡1倍奶泡,后来又要增加一倍的摩卡时,打印出的结果为Mocha,Mocha,Whip,Mocha。实际上应要求输出结果为Mocha,Mocha,Mocha,Whip。为此,需要采取方法让装饰者知道一连串装饰链条中其他装饰者的存在。要达到这目的,自己将父类Beverage做了些修改。

Beverage:

public abstract class Beverage {
	String description="Unknown Beverage";

	
	public String getDescription(){
		return description;
	}
	public abstract double cost();
	
	public String toString(){
		
		String[] data=getDescription().split(",");
		Arrays.sort(data,1,data.length);
		StringBuilder sb=new StringBuilder();
		for(int i=1;i<data.length;i++){
			if(i!=data.length-1){
				sb.append(data[i]+",");
			}
			else{
				sb.append(data[i]);
			}
		}
		return data[0]+","+sb.toString();
	}
}
其他类不变,测试代码如下:
public class StarbuzzCoffee {
	public static void main(String[] args){
	Beverage beverage=new Espresso();
//	System.out.println(beverage.getDescription()+" $"+beverage.cost());
	System.out.println(beverage+" $"+beverage.cost());
	Beverage beverage2= new DarkRoast(){};
	beverage2=new Mocha(beverage2);
	beverage2=new Mocha(beverage2);
	beverage2=new Whip(beverage2);
	beverage2=new Mocha(beverage2);
//	System.out.println(beverage2.getDescription()+" $"+beverage2.cost());
	System.out.println(beverage2+" $"+beverage2.cost());
	Beverage beverage3=new HouseBlend();
	beverage3=new Soy(beverage3);
	beverage3=new Mocha(beverage3);
	beverage3=new Whip(beverage3);
	System.out.println(beverage3.getDescription()+" $"+beverage3.cost());
	}
}
结果:
Espresson, $1.99
Dark Roast,Mocha,Mocha,Mocha,Whip $1.69
House Blend Coffee,Soy,Mocha,Whip $1.34

java中的其他装饰者模式:

java的io包中大量用到了装饰者模式。除了基本的节点流外,过滤流全部继承自FilterInput/OutPutStream或者FilterReader/Writer

下面自己编写一个过滤流,用以实现将字符串中的大/小写字母改为小/大写字母

public class LowerCaseInpuStream extends FilterInputStream {
	public LowerCaseInpuStream(InputStream in){
		super(in);
	}
	public int read() throws IOException{
		int c=super.read();
		if(c!=-1){
			if(Character.isLowerCase((char)c)){
				c=Character.toUpperCase((char)c);
			}
			else if(Character.isUpperCase((char)c)){
				c=Character.toLowerCase((char)c);
			}
		}
		return c;
//		return c==-1?c:Character.toLowerCase((char)c);
	}
	public int read(byte[]b,int offset,int len)throws IOException{
		int result=super.read(b,offset,len);
		for(int i=offset;i<offset+result;i++){
			b[i]=(byte)Character.toLowerCase((char)b[i]);
		}
		return result;
	}
}
测试:

public class InputTest {
	public static void main(String[] args)throws IOException{
		int c;
		try{
			InputStream in=new LowerCaseInpuStream(new StringBufferInputStream("abcABCDEFGabcdefg"));
			while((c=in.read())>=0){
				System.out.print((char)c);
			}
		}catch(IOException e){
			e.printStackTrace();
		}
	}
}
结果:
ABCabcdefgABCDEFG

总结:装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性。动态给一个对象增加功能,这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。


要点:

继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式。

在设计中,应该允许行为可以被扩展,而无须修改现有的代码。

组合和委托可用于在运行时动态地加上新的行为。

除了继承,装饰者模式也可以让我们扩展性为。

装饰者模式意味着一群装饰着类,这些类用来包装具体组件。

装饰者类反映出被装饰的组件类型(事实上,他们具有相同的类型,都经过接口或继承实现)。

装饰者可以在被装饰者的行为前面或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。

你可以用无数个装饰者包装一个组件。

装饰者一般对组建的客户是透明的,除非客户程序依赖于组件的具体类型。

装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。


自说自话:学习了装饰者模式后,Java的IO包的组织方式一下子明白了很多。装饰者模式可以让代码更具弹性,在运行时再根据需要组装一个对象的功能,比起继承来真是更加的好用。要说缺点的话,装饰者模式会在程序中加入许多小类(这叫碎片化吗?哈哈)。不过终归是瑕不掩瑜。








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值