EBU6304 - Software Engineering W4

W4-16 Design Patterns

The Design Patterns we have covered

  1. Wrapper:

    • Decorator : Inside it a variable of the same interface type whose value is set in the constructor

    • Adapter: adapts” an object of one class to fit into an interface when the object is not of a type which implements the interface

    • Composite : a collection of objects of a type is wrapped

    • Immutable View : for methods that do make changes throwing an exception instead

  2. Factory method: normal methods which work like constructors, they return new objects

    • Factory object: An object which is designed to have factory methods

    • Singleton : but factory methods can return an existing object instead of a new object

    • Object Pool: like Singleton, However, instead of one object it keeps a list of objects

  3. Observer: a link between two classes such that an action on an object of one of the classes is “observed” by objects of the other class

  4. Strategy: Generalising code by turning an action into a parameter, we can generalise by using the idea of

  5. a function object, which is an object whose only job is to carry out an action.

  6. State: State design pattern is used when we want to change the way we represent a type of object dynamically

  7. Bridge: This is where there are two separate “crosscutting”横切的 class hierarchies

  8. Flyweight: where objects have an immutable part which can be shared.

The Decorator Design pattern

This is an example of the Decorator design pattern

 class QuackCounter implements Quackable {
     private Quackable myQuacker; //a variable of the same interface type
     private int count;
     
     public QuackCounter(Quackable q) { 
         myQuacker=q;
     }
     public int getCount() {
         return count;
     }
     public void quack()
     {
         count++;
         myQuacker.quack();
     }
 }
 ​
     Quackable q1= new Duck("Donald");
     Quackable q2= new QuackCounter(q1);
     Value v = t.process(…,q2,…);
     int quackcount = q2.getCount();

The Decorator design pattern involves

– A class which implements an interface

– Inside it a variable of the same interface type whose value is set in the constructor

– Each method from the interface has code which calls the same method with the same arguments on that variable, plus some extra work (count++)

The effect is to “decorate” the methods of an object of the interface type with the code that does the extra work

Wrapper design patterns

  • A wrapper pattern is any pattern where an object (called the inner object) is referred to by a variable inside another object (the outer object), and each method call on the outer object results in a method call on the inner object

  • In the Decorator pattern, the inner object and outer object must both be of a class which implements the same interface, in this case Quackable

  • The Decorator design pattern is an example of a wrapper pattern

  • Another wrapper pattern is the Adapter pattern, which involves an outer object which implements an interface wrapping an inner object which is of a class that does not implement the interface

The Adapter design pattern

  • The Adapter design pattern “adapts” an object of one class to fit into an interface when the object is not of a type which implements the interface

  • 将一个没有implements the interface的object 适应成 fit into an interface

  • works by “wrapping” the object as an inner object in an outer object which is of a class that does implement the interface

  • Each method in the interface has to be implemented by code which performs what is regarded as the “equivalent” in the class of the inner object

 class GooseAdapter implements Quackable {
     private Goose myGoose;
     public GooseAdapter(Goose aGoose) {
         myGoose=aGoose;
     }
     public void quack() {
         myGoose.honk();
     }
 }
 ​
 // …
 interface Quackable {
     public void quack();
 }
 // Example: class Goose has a method 
 public void honk() …
 which is judged equivalent to the quack() method of interface Quackable
    

Adapter using Inheritance

 class QuackingGoose extends Goose implements Quackable{
     public QuackingGoose(String name) {
         super(name);
     }
     public void quack() {
         honk();
     }
 }

  • This is another sort of Adapter, this time using inheritance rather than delegation. Here a QuackingGoose object IS-A Goose object, whereas previously a GooseAdapter HAS-A Goose object

  • Passing a QuackingGoose object to other code passes access to all Goose methods,

    • whereas passing a GooseAdapter object only passes access to honk called indirectly through quack

Why not just change the code?

  • we cannot change the code for them without having to check whether the change causes that code to stop working properly (OCP)

  • If we added code to count the number of calls to the method quack made in the call to the method process, we are making that method do two separate tasks. The QuackCounter wrapper separates out those two separate tasks (SRP)

  • If we wanted to count the number of calls to the method quack made in a call to another method which takes an argument of type Quackable, we can just use the wrapper we already have (DRY)

  • We may not want to pass full access to Goose objects to other code, passing the GooseAdapter object passes only the ability to call the method honk on a Goose object (DIP)

Design patterns

• Design patterns are general, we can re-use the same pattern in many different circumstances

•Design patterns go beyond what is provided directly in the programming language

• Design patterns often involve a use of code which a novice programmer probably would not think of

• Design patterns provide us with a “vocabulary”, here we can talk to other programmers about using a “decorator” or an “adapter” and they will know what we mean

The Composite Design Pattern

Another example of a wrapper pattern, this time a collection of objects of a type is wrapped and given the behaviour of a single object of that type

 class QuackComposite implements Quackable {
     private List<Quackable> quackers;// a collection of objects
     private int count;
     
     public QuackComposite() { 
         quackers = new ArrayList<Quackable>();
     }
 ​
     public void addQuacker(Quackable q) {
         quackers.add(q);
     }
     
     public void quack() {
         for(Quackable aQuacker : quackers)
         aQuacker.quack();
     }
 }

Immutable View (a wrapper design pattern)

problems can be caused by aliasing: two different variables that refer to the same object

A method call made on one of the variables that causes a change of state to the object it refers to means the object the other variable refers to also changes state as it is the same object 对其中一个变量进行的导致对象状态改变的方法调用意味着另一个变量引用的对象也会改变状态,因为它是相同的对象

This problem can be avoided by passing an “immutable view” of an object rather than a direct reference

This is another example of the general principle of a wrapper design pattern. It involves calling the same method on the inner object if it does not make a change, but for methods that do make changes throwing an exception instead

 class ImmutableRectangle implements Rectangle { 
     private Rectangle myRectangle;
     
     public ImmutableRectangle(Rectangle r) { 
         myRectangle=r;
     }
     public int getWidth() {
         return myRectangle.getWidth();
     }
     public int getHeight() {
         return myRectangle.getHeight();
     }
     public int area() {
         return myRectangle.area();
     }
     //**throwing an exception instead** 
     public void setWidth(int w) {
         throw new       UnsupportedOperationException();
     }
     public void setHeight(int h) {
         throw new UnsupportedOperationException();
     }
 }

The Observer Pattern

The Observer design pattern is intended to cover cases where there is a link between two classes such that an action on an object of one of the classes is “observed” by objects of the other class

Its most common use is to connect objects which store data or perform actions to other objects which deal with “input/output” 它最常见的用途是将存储数据或执行操作的对象连接到其他处理“输入/输出”的对象

Notes on Java’s Observer code

  • It is flexible, an Observable object can have any number of Observer objects, an Observer object can observe any number of Observable objects

  • The code for the Observable object passes whatever information it likes to the Observer objects (“push”)

  • The code for the Observer object can get additional information by calling public methods on the Observable objects (“pull”)

  • The code which extends Observable cannot access the Observer objects directly, an object of a class which extends Observable does not “know” what its observers are

Factory Methods

 public static DogBot(这是返回类型 makeDogBot(String nm,
     int h, int t, boolean greedy) 
     {
     if(greedy)
         return new GreedyDogBot(nm,h,t);
     else
         return new PlainDogBot(nm,h,t);
 }
  • Factory methods are normal methods which work like constructors, they return new objects

  • Unlike constructors, factory methods can have an interface type as their return type

 public static Quackable makeQuacker(String nm, boolean fake)
 {
     if(fake) return new DuckCall(nm);
     else return new Duck(nm);
 }
  • They can be used to hide the actual type of the new object

Factory Methods for Wrapping Objects

A factory method could produce a new wrapped object, or an object which puts a wrapper around an existing object

 static Quackable makeCountedDuck(String name)
 {
     Duck wrappedDuck = new Duck(name);
     return new GloballyCountedQuacker(wrappedDuck);
 }

Factory objects

  • An object which is designed to have factory methods called on it is called a Factory Object

  • A factory object is used when we want to delegate委托 the construction of new objects

  • A factory object may be used to hide the real type of the objects it returns

 class PlainDogFactory implements DogFactory {
     private int hungry,tired;
     
     public PlainDogFactory(int h, int t) {
         hungry=h;
         tired=t;
     }
     // factory methods
     public DogBot makeDogBot(String name) {
      return new PlainDogBot(name,hungry,tired);
     }
 }
 ​
 /*This is a class of objects which make DogBots with fixed initial hungry and tired values: */
 DogFactory factory = new PlainDogFactory(4,2);
 DogBot dog1 = factory.makeDogBot("Patch");
 DogBot dog2 = factory.makeDogBot("Rover");

The Singleton Design Pattern单一设计模式

Another reason for using factory methods is that a constructor must always return a new object, but factory methods can return an existing object instead of a new object. 这就是The Singleton Design Pattern

Here is an example

 class SingleDogFactory implements DogFactory 
 {
     private int hungry,tired;
     private DogBot theOnlyDogBot;
     
     public SingleDogFactory(int h,int t,String name) 
     {
         theOnlyDogBot = new PlainDogBot(name,h,t);
     }
     public DogBot makeDogBot(String name) 
     {
         return theOnlyDogBot;// return an existing object
     }
 }

The Object Pool Design Pattern

like Singleton, can returns an object that was created before,However, instead of one object it keeps a list of objects

It is used when the objects returned are immutable

 class ColourMaker {
     private Map<Colour,Colour> colours;
     
     public ColourMaker {
         colours = new TreeMap<Colour,Colour>();
     }
     
     public Colour makeColour(int red, int green, int blue){
         Colour col1 = new Colour(red,green,blue);
         Colour col2 = colours.get(col1);
         if(col2==null) {
             colours.put(col1,col1);
             return col1;
         } 
         else return col2; //** return a list of objects** 
     }
 }

The Strategy Design Pattern

Generalising code by turning an action into a parameter is called the Strategy Design Pattern

Suppose we want to call the various operations possible on a List of DogBots. Instead of writing a separate method for each we can generalise by using the idea of

a function object, which is an object whose only job is to carry out an action.

 //Here is the interface type for such an object:
 interface DogAction {
     public void action(DogBot d);
 }
 ​
 //Here is the generalised code that uses it:
 void doIt(List<? extends DogBot> list, DogAction act//turning an action into a parameter)
 {
     for(DogBot dog : list)
         act.action(dog)
 }
           
 ​

Function Objects

an object whose only job is to carry out an action.

Here is the class for the function object for calling play on a DogBot:

 class MakePlay implements DogAction 
 {
     public void action(DogBot d) 
     {
         d.play();
     }
 }
 //Here is a call which results in play being called on every object in the list dogList:
 doIt(dogList, new MakePlay())

Function Objects with State 状态

Here is a class which describes a function object which repeats an action:

 class MultiAction implements DogAction {
     private DogAction myAction;
     private int ntimes;
     
     public MultiAction(DogAction act, int n) {
         myAction=act;
         ntimes=n;
     }
     
     public action(DogBot d) {
         for(int i=0; i<ntimes; i++)
             myAction.action(d);
     }
 }

So act = new MultiAction(new MakePlay(),3) sets act to refer to a function object where act.action(dog) will cause the method play to be called on the DogBot referenced by dog three times

The State Design Pattern

  • The State design pattern is used when we want to change the way we represent a type of object dynamically

  • Consider the idea of a set, it is a collection in which each possible element is either in the collection or it is not, there is no concept of elements occurring multiple numbers of times or having a position in the collection.

  • A mutable set is one where elements can be added or removed. Here is an interface type for a mutable set of integers:

 interface IntSet 
 {
     public void add(int n);
     public void remove(int n);
     public boolean contains(int n);
 }
  • The set remains unchanged if an integer is added when it is already a member, or if it is removed when it is not a member - the definition in this interface does not report an error

  • An efficient implementation for sets of integers which have a fixed range of possible members uses an array of booleans

     class BoolIntSet implements IntSet {
         private boolean[] arr; 
         
         public BoolIntSet(int range) {
             arr = new boolean[range];
         }
         
         public void add(int n) {
             arr[n]=true;
         }
         
         public void remove(int n) {
             arr[n]=false;
         }
         public boolean contains(int n) {
             return arr[n];
         }
         
         public int getRange() {
             return arr.length;
         }
     }

    Suppose we have another implementation of IntSet called OtherIntSet which can cope with

    integers in any range, then we can have an implementation which switches:

     class FlexibleIntSet implements IntSet {
         private boolean usesbool;
         private IntSet state;
         protected int limit, origrange;
         
         public FlexibleIntSet(int range) {
             state = new BoolIntSet(range);
             usesbool=true;
             limit=range;
             origrange=range;
         }
         
         public void add(int n) {
             if(n>limit)
                 limit=n;
             if(usesbool&&n==limit) {
                 IntSet state1 = new OtherIntSet();
                 for(int i=0; i<limit; i++)
                     if(state.contains(i)) 
                         state1.add(i);
             state = state1;
             usesbool=false;
         }
         state.add(n);
         }
         
         public void remove(int n) {
             state.remove(n);
         }
         
         public boolean contains(int n){
             return state.contains(n);
         }
     }
     class OtherIntSet implements IntSet {
         private int[] array;
         private int count; 
         private static final int INITSIZE=1000;
         
         public OtherIntSet() {
             array = new int[INITSIZE];
         }
        
         public void add(int n) {
             if(!contains(n)) {
                 if(count==array.length) {
                     int[] replacearray = new int[array.length*2);
                     for(int i=0; i<count; i++)
                         replacearray[i]=array[i];
                     array=replacearray;
                     }
                 array[count++]=n;
                 }
         }
                                                  
         public void remove(int n) {
             for(int i=0; i<count; i++)
                 if(array[i]==n) {
                     array[i]=array[--count];
                     break;
                     }
         }
                                                  
         public boolean contains(int n) {
             for(int i=0; i<count; i++)
                 if(array[i]==n) return true;
         return false;
         }
     }

The Bridge Design Pattern

Suppose FlexibleIntSet had subclasses:

 class ExtendedIntSet extends FlexibleIntSet
 {
         public ExtendedIntSet(int range) 
     {
         super(range);
     }
     … // Extra operations
 }
  • This is an example of the Bridge design pattern

  • This is where there are two separate “crosscutting”横切的 class hierarchies

    两个独立的“横切”类层次结构

  • If there was just one hierarchy, there would need to be two separate classes for ExtendedIntSet, one for where it is implemented using an array of booleans, the other for the other set implementation

 class ExtendedIntSet extends FlexibleIntSet
 {
     … 
     IntSet intersection(IntSet set)
     {
         IntSet result = new FlexibleIntSet(origrange);
         for(int i=0 i<limit; i++)
             if(set.contains(i)&&this.contains(i))
                 result.add(i);
         return result;
     }
     … 
 }

The Flyweight轻量级 Pattern

The Flyweight design pattern is where objects have an immutable part which can be shared.

The ColouredRectangle example we saw previously would be an example of the Flyweight pattern if the Colour part of a ColouredRectangle was produced using a ColourMaker object as given previously

 class ColouredRectangle extends PlainRectangle { 
     private static ColourMaker maker = new ColourMaker();
     private Colour colour;
     
     public ColouredRectangle(int h, int w, int red, int green, int blue){
         super(h,w);
         colour=make.makeColour(red,blue,green);
     }
     
     public Colour getColour() {
         return colour;
     }
     
     public void setColour(Colour c) {
         colour=c;
     }
 }

Summary-Why are design patterns important?

Design Patterns Represent Experience

Design patterns are “tricks” which experienced programmers have come to use 设计模式是经验丰富的程序员开始使用的“技巧”

Each design pattern is a way of putting together code to solve a particular sort of problem which it has been noted is common enough to set it down as a general technique 每个设计模式都是一种将代码组合在一起以解决一种特定问题的方法,人们注意到这个问题很常见,可以将其设置为一种通用技术

Design patterns are applicable in any programming language, but the idea developed in the field of objectoriented programming, and the common design patterns are most easily implemented in object-oriented languages 设计模式适用于任何编程语言,但是在面向对象编程领域开发的思想和常见的设计模式最容易在面向对象语言中实现

Design patterns are often a way to help keep programs in line with main principles of good program design 设计模式通常是一种帮助程序保持良好程序设计的主要原则的方法

Design Patterns Structure Code 设计模式规范结构

SRP

Design patterns give ways of structuring code into parts with clear separate responsibilities, in line with the Single Responsibility Principle 设计模式根据单一责任原则(SRP)将代码结构成具有明确单独责任的部分

For example, we have seen how various tasks associated with a method call can be divided into parts using wrapper techniques so each wrapper is responsible for one task

OCP

Design patterns enable code to be written in a generalised way, in line with the Open Closed Principle

For example, we have seen how aspects of a method can be delegated to a separate object passed in through a parameter, with different objects giving different effects, we saw this with factory objects and function objects

Design Patterns Reduce Dependency

DIP

Design patterns patterns enable details connected to implementation to be hidden from code which uses objects, in line with Dependency Inversion Principle

For example, with the Object Pool and Flyweight design patterns, the code which uses objects does not have to manage when to allow aliasing to save space

With the State design pattern, the code which uses objects does not have to manage the transition between one representation and another when for efficiency reasons it is a good idea to change

With the Observer design pattern the code which uses objects does not have to manage the details of communicating the effects of method calls on objects to code which displays or reports those effects

Design Patterns as a Vocabulary

Design patterns give a vocabulary which experienced programmers can use to communicate with each other when designing programs

Be Cautious with Design Patterns

Over-use of design pattern techniques can lead to code which is more complex than necessary 设计模式技术的过度使用会导致更复杂的代码

It is often better to start with simple code and “refactor” it using one of the design patterns when more requirements are added later 通常最好从简单的代码开始,并在稍后添加更多需求时使用其中一种设计模式来“重构”它

W4-17 Open Source Software

Topics:

• Free Software

• Open Source Software

• E-voting

Free Software

The phrase “open source” derives from the idea of the source code being publicly available,

but the phrase “Open Source Software”(OSS) is usually used to mean software which is free of charge, and free of legal restrictions on how it can be used

“开源”一词源于源代码可以公开使用的想法,但“开源软件”(OSS)一词通常用来指免费的软件,对如何使用它没有法律限制

Software Freedom -four freedoms:

free software” insists on the following four freedoms:

– The freedom to run the program, for any purpose

– The freedom to study how the program works, and change it so it does your computing as you wish. Access to the source code is a precondition for this.

– The freedom to redistribute copies so you can help your neighbour.

– The freedom to distribute copies of your modified versions to others. By doing this you can give the whole community a chance to benefit from your changes. Access to the source code is a precondition for this.

-运行程序的自由,为任何目的

-自由研究程序如何工作,并改变它,使它做你的计算,如你的愿望。访问源代码是这样做的先决条件。

–可以自由地重新分发副本,所以你可以帮助你的邻居。

–将修改后的副本分发给他人。通过这样做,你可以让整个社区都有机会从你的变化中受益。访问源代码是这样做的先决条件。

Copyright

Copyright is a legal term meaning legislation which gives protection to creators of an original work

版权是一个法律术语,意思是指保护原创作品的创作者的立法

The idea is that only the producers of the original work have the full right to produce copies, and create new work based on it, but they may grant permission for others to make copies of the work and adapt it

其想法是,只有原创作品的制作人才有完全的权利进行复制,并在此基础上创作新的作品,但他们可以允许其他人对该作品进行复制并进行改编

The idea is that people and organisations will be more willing to produce new work, if they are guaranteed to keep profits made from it 其想法是,如果人们和组织能够保证从中保持盈利,他们将更愿意创造新的工作

Copyleft 非盈利版权

The supporters of Free Software have developed a concept they call copyleft to mean

using the copyright laws in a way that guarantees the freedom of others to copy and use their work rather than stopping unauthorised or unpaid copying 以保证他人自由地复制和使用他们的作品,而不是停止未经授权或无偿的复制

It involves a free software license, which is associated with free software, use of that software is granted only to those who agree to the terms of the free software license 它涉及一个与自由软件相关的自由软件许可,该软件的使用只授予那些同意自由软件许可条款的人

The license will include the requirement that the source code is made available, and also state the extent to which the code may be modified and how the modifications should be acknowledged 许可证将包括源代码可用的要求,并说明代码可能被修改的程度以及应该如何承认修改

e-voting

The reason why software experts tend to oppose e-voting is that they are particularly aware of how software can perform incorrectly, either through unintentional error or through deliberate manipulation by those who wish to gain unfair or illegal advantage 软件专家倾向于反对电子投票的原因是,他们特别知道软件是如何执行错误的,要么通过无意的错误,要么通过那些希望获得不公平或非法优势的人的故意操纵

This is an example of how new technology is not necessarily an improvement on established old-fashioned techniques 这是一个例子,说明新技术不一定是对现有传统技术的改进

How open source is Android?

Android” showed the least openness of the major OSS projects they investigated.

State three reasons for “Vision Mobile” giving this result.

18年考题 绷 这也考啊

  1. Vision Mobile claims that Android is closely controlled by Google, which gives priority access to specific developer groups and organisations, and does not make public its internal decision-making processes 视觉移动公司声称,安卓系统受到谷歌的密切控制,谷歌允许谷歌优先访问特定的开发者团队和组织,并不公开其内部决策过程

  2. Google does not provide public information regarding meetings held and decisions made on the development of Android 谷歌不提供有关开发安卓系统的会议和决策的公共信息

  3. Google owns the Android trademark, and will not permit it to be used for devices which use Android code but do not pass a set of tests, the Compatibility Test Suite (CTS) which it controls 谷歌拥有安卓商标,不允许它用于使用安卓代码但不通过一组测试的设备,也就是它控制的兼容性测试套件(CTS)

Summary

  • The key to OSS success is that the community of users of the code also provides a community of testers and debuggers 它成功的关键在于,代码的用户社区还提供了一个包含测试者和调试者的社区

  • OSS products avoid the risk of control of code that is vital for a business being in another business’s hands OSS产品避免了代码控制的风险,这对企业在另一个企业手中至关重要

  • OSS products maintain their OSS nature through a use of copyright legislation to enforce free distribution rather than prevent it OSS产品通过使用版权立法来执行免费分发,而不是防止免费分发来保持其OSS性质

W4-18 Software Development Tools

Topics:

• Software Craftsmanship

• Clean Code

• Best Practices

• Tools

Microsoft’s Best Practices 微软的最佳实践

The following is a list of “best practices” for software development projects which was put together by Microsoft: 以下是由微软编制的软件开发项目的“最佳实践”列表:

• Revision Control System

• Daily Builds

• Build Verification Tests

• Bug Database

• Code Reviews

• Coding and Engineering Guidelines

• Code Analysis Tools

• Globalization and Localization

Revision Control System

A Revision Control System (also called “Version Control”) keeps a record of all the changes that are made to the software of a project 一个修订控制系统(也称为“版本控制”)保存了对一个项目的软件所做的所有更改的记录

  • • It will generally incorporate roll-back features, enabling a return to any previous version of the system 它通常会包含回滚功能,使其能够返回到系统的任何以前版本

  • A check-out is when a developer obtains the current version of a file, to have a local copy to work with 签出是指当开发人员获得一个文件的当前版本时,以便使用一个本地副本

  • • A check-in (or “commit”) is when a developer enters a modified version of the file into the system to become the current version (“master copy”) of that file for the system 签入(或“提交”)是指开发人员在系统中输入一个修改版本的文件,从而成为该文件的系统的当前版本(“主副本”)

Daily Build

Having a daily build means having a practice of doing this once a day, usually overnight so the team starts work with the latest version of the software 每天进行构建意味着每天做一次,通常是一夜之间,所以团队开始使用最新版本的软件

A build will generally contain at least some verification tests for correct working, sometimes referred to as “smoke tests” 一个构建通常将包含至少一些正确工作的验证测试,有时被称为“烟雾测试”

A build break is when the build or its tests fail, it should be considered a high priority problem that must be fixed immediately 构建中断是指当构建或其测试失败时,它应该被认为是一个必须立即修复的高优先级问题

Assert statements

An assert statement in source code is a statement involving an expression which is always meant to evaluate to true, if it does not an exception is thrown 源代码中的断言语句是一个涉及表达式的语句,如果它不抛出异常,那么它总是用来计算为true

Code reviews and coding guidelines

A code review is a systematic examination of source code, it may involve a formal meeting with a structured format to an informal agreement for team members to check over each other’s code 代码审查是对源代码的系统检查,它可能包括一个具有结构化格式的正式会议,以及让团队成员检查彼此的代码的非正式协议

Automated code review involves tools which analyse code to check for compliance with predefined sets of coding guideline rules or best practices 自动代码审查涉及到分析代码的工具,以检查是否符合预定义的编码指导规则或最佳实践集

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值