67、Java 命令模式与员工管理系统实现

Java 命令模式与员工管理系统实现

1. 命令模式概述

命令模式主要有两个作用:一是将特定操作的知识与需要执行该操作的对象解耦;二是将操作封装成对象。在一定程度上,组件(如 JButton )与 ActionListener 的关系就体现了命令模式。当按钮被点击时,会创建一个 ActionEvent 对象,并将其作为参数传递给 ActionListener actionPerformed() 方法。若只有一个 ActionListener 来处理所有按钮点击事件,那么 actionPerformed() 方法就需要确定源组件并执行相应操作。在大型应用中,这可能导致 actionPerformed() 方法变得庞大。

另一种在 Java 中实现命令模式的常见方法是扩展 JButton JMenuItem 等组件,使其成为命令。但这种方法违反了 Coad 准则,因为命令并不等同于按钮。

更推荐的命令模式实现策略是将动态工厂模式与单独的命令类层次结构相结合。该层次结构的根是一个抽象类 BaseCommand ,如下所示:

package com.pulpfreepress.commands;

import com.pulpfreepress.interfaces.*;

public abstract class BaseCommand {
    protected static iModel   its_model = null;
    protected static iView    its_view = null;

    public void setModel(iModel model){
        if(its_model == null){
            its_model = model;
        }
    }

    public void setView(iView view){
        if(its_view == null){
            its_view = view;
        }
    }

    public abstract void execute(); // must be implemented in derived classes
}

BaseCommand 类包含两个受保护的静态字段 its_model its_view ,子类可以直接访问这些字段。 setModel() setView() 方法分别用于初始化 its_model its_view 引用。 execute() 方法是抽象的,必须由派生类实现,具体的命令类将实现每个命令独特的操作。

2. 控制器类实现

这种命令模式的实现方式保留了早期介绍的 MVC 组件之间的关系,同时大大简化了控制器的 actionPerformed() 方法,示例如下:

package com.pulpfreepress.controller;

import com.pulpfreepress.utils.*;
import com.pulpfreepress.commands.*;
import com.pulpfreepress.exceptions.*;
import com.pulpfreepress.model.*;
import com.pulpfreepress.view.*;
import com.pulpfreepress.interfaces.*;
import java.awt.event.*;

public class Controller implements ActionListener {
    private CommandFactory command_factory = null;
    private iModel its_model;
    private iView its_view;

    public Controller(){
        command_factory = CommandFactory.getInstance();
        its_model = new Model();
        its_view = new View(this);
    }

    public void actionPerformed(ActionEvent ae){
        try{
            BaseCommand command = command_factory.getCommand(ae.getActionCommand());
            command.setModel(its_model);
            command.setView(its_view);
            command.execute();
        }catch(CommandNotFoundException cnfe){
            System.out.println("Command not found!");
        }
    }

    public static void main(String[] args){
        new Controller();
    }
}

Controller 类中, actionPerformed() 方法从第 24 行开始。 command_factory 引用用于根据被点击组件的操作命令名称动态加载并创建命令实例。一旦命令创建完成,就会调用 setModel() setView() execute() 方法。只要命令类存在于系统类路径中,一切就会按预期工作。

3. 命令工厂类实现

命令工厂类 CommandFactory 实现了单例模式,并利用了 CommandProperties 类的服务。组件操作命令字符串在 Command.properties 文件中映射到各自的命令类。 CommandFactory 类的大部分工作在 getCommand() 方法中完成,代码如下:

package com.pulpfreepress.utils;

import com.pulpfreepress.commands.*;
import com.pulpfreepress.exceptions.*;

public class CommandFactory {
    private static CommandFactory command_factory_instance = null;
    private static CommandProperties command_properties = null;

    static {
        command_properties = CommandProperties.getInstance();
    }

    private CommandFactory(){ }

    public static CommandFactory getInstance(){
        if(command_factory_instance == null){
            command_factory_instance = new CommandFactory();
        }
        return command_factory_instance;
    }

    public BaseCommand getCommand(String command_string) throws CommandNotFoundException {
        BaseCommand command = null;
        if(command_string == null){
            throw new CommandNotFoundException( command_string + " command class not found!");
        }  else{ 
            try{
                String command_classname = command_properties.getProperty(command_string);
                Class command_class = Class.forName(command_classname);
                Object command_object = command_class.newInstance();
                command = (BaseCommand) command_object;
            }catch(Throwable t){
                t.printStackTrace();
                throw new CommandNotFoundException(t.toString(), t);
            }
        } 
        return command;
    }
}

CommandFactory 类的 getCommand() 方法使用 command_string 参数查找实际执行工作的命令类的类名。如果类存在,则加载该类;如果加载成功,则创建该类的实例并返回。

4. 命令属性文件

Command.properties 文件将命令名称映射到各自的类处理程序,示例内容如下:

#CommandProperties File - Edit Carefully
#Sat Jun 18 12:24:38 EDT 2005
PROPERTIES_FILE=Command.properties
Load=com.pulpfreepress.commands.LoadEmployeesCommand
Exit=com.pulpfreepress.commands.ApplicationExitCommand
NewSalariedEmployee=com.pulpfreepress.commands.NewSalariedEmployeeCommand
EditEmployee=com.pulpfreepress.commands.EditEmployeeCommand
Sort=com.pulpfreepress.commands.SortEmployeesCommand
DeleteEmployee=com.pulpfreepress.commands.DeleteEmployeeCommand
List=com.pulpfreepress.commands.ListEmployeesCommand
NewHourlyEmployee=com.pulpfreepress.commands.NewHourlyEmployeeCommand
Save=com.pulpfreepress.commands.SaveEmployeesCommand
5. 命令模式流程总结

以下是命令模式的主要操作流程:
1. 用户操作触发 ActionListener actionPerformed() 方法。
2. actionPerformed() 方法通过 CommandFactory 获取相应的命令实例。
3. 设置命令的模型和视图。
4. 执行命令的 execute() 方法。

可以用 mermaid 流程图表示如下:

graph LR
    A[用户操作] --> B[触发 actionPerformed 方法]
    B --> C[CommandFactory 获取命令实例]
    C --> D[设置命令的模型和视图]
    D --> E[执行命令的 execute 方法]
6. 综合示例:员工管理系统

接下来介绍一个综合示例应用程序,该应用程序允许创建、编辑和删除小时工和 salaried 员工,还可以对员工进行排序,并将员工数据保存到磁盘和从磁盘检索。以下是按包名列出的完整代码清单:

6.1 com.pulpfreepress.commands

该包包含各种命令类,如 BaseCommand ApplicationExitCommand DeleteEmployeeCommand 等。以下是部分命令类的代码示例:

// BaseCommand.java
package com.pulpfreepress.commands;

import com.pulpfreepress.interfaces.*;

public abstract class BaseCommand {
    protected static iModel its_model = null;
    protected static iView  its_view = null;

    public void setModel(iModel model){
        if(its_model == null){
            its_model = model;
        }
    }

    public void setView(iView view){
        if(its_view == null){
            its_view = view;
        }
    }

    public abstract void execute(); // must be implemented in derived classes
}

// ApplicationExitCommand.java
package com.pulpfreepress.commands;

public class ApplicationExitCommand extends BaseCommand {
    public void execute(){
        System.exit(0);
    }
}

// DeleteEmployeeCommand.java
package com.pulpfreepress.commands;

public class DeleteEmployeeCommand extends BaseCommand {
    public void execute(){
        if((its_model != null) && (its_view != null)){
            String employee_number = its_view.getDeleteEmployeeInfo();
            if(employee_number != null) {
                its_model.deleteEmployee(its_view.getDeleteEmployeeInfo());
            }
        }
        its_view.displayEmployeeInfo(its_model.getAllEmployeesInfo());
    }
}
6.2 com.pulpfreepress.controller

该包包含 Controller 类,负责处理用户操作并调用相应的命令,代码前面已给出。

6.3 com.pulpfreepress.exceptions

该包包含 CommandNotFoundException 异常类,用于处理命令未找到的情况:

package com.pulpfreepress.exceptions;

public class CommandNotFoundException extends Exception {
    public CommandNotFoundException(String message, Throwable ex){
        super(message, ex);
    }

    public CommandNotFoundException(String message){
        super(message);
    }

    public CommandNotFoundException(){
        this("Command Not Found Exception");
    }
}
6.4 com.pulpfreepress.interfaces

该包包含 iModel iView 接口,定义了模型和视图的操作方法:

// iModel.java
package com.pulpfreepress.interfaces;

import java.util.*;
import java.io.*;

public interface iModel {
    public void createHourlyEmployee(String fname, String mname, String lname,
                                     int dob_year, int dob_month, int dob_day,
                                     String gender, String employee_number,
                                     double hours_worked, double hourly_rate);

    public void createSalariedEmployee(String fname, String mname, String lname,
                                       int dob_year, int dob_month, int dob_day,
                                       String gender, String employee_number,
                                       double salary);

    public void editEmployeeInfo(String fname, String mname, String lname,
                                 int dob_year, int dob_month, int dob_day,
                                 String gender, String employee_number);

    public String[] getAllEmployeesInfo();
    public String getEmployeeInfo(String employee_number);
    public void sortEmployees();
    public void deleteEmployee(String employee_number);
    public void saveEmployeesToFile(File file);
    public void loadEmployeesFromFile(File file);
}

// iView.java
package com.pulpfreepress.interfaces;
import java.io.*;

public interface iView {
    public void displayEmployeeInfo(String[] employees_info);
    public String[] getNewHourlyEmployeeInfo();
    public String[] getNewSalariedEmployeeInfo();
    public String[] getEditEmployeeInfo();
    public String getDeleteEmployeeInfo();
    public File getSaveFile();
    public File getLoadFile();
}
6.5 com.pulpfreepress.model

该包包含员工相关的类,如 IEmployee Employee Person HourlyEmployee SalariedEmployee 等,以及员工工厂类和模型类:

// IEmployee.java
package com.pulpfreepress.model;

import java.util.*;

public interface IEmployee {
    int getAge();
    String getFullName();
    String getNameAndAge();
    String getFirstName();
    String getMiddleName();
    String getLastName();
    String getGender();
    String getEmployeeNumber();
    void setBirthday(int year, int month, int day);
    Calendar getBirthday();
    void setFirstName(String f_name);
    void setMiddleName(String m_name);
    void setLastName(String l_name);
    void setGender(String gender);
    void setEmployeeNumber(String emp_no);
    void setPayInfo(PayInfo pi);
    double getPay();
    String toString();
}

// Employee.java
package com.pulpfreepress.model;

import java.util.*;
import java.io.*;

public abstract class Employee implements IEmployee, Cloneable, Comparable, Serializable {
    private Person _person = null;
    private String  _employee_number = null;

    protected Employee(String f_name, String m_name, String l_name, int dob_year, int dob_month,
                       int dob_day, String gender, String employee_number){
        _person = new Person(f_name, m_name, l_name, dob_year, dob_month, dob_day, gender);
        _employee_number = employee_number;
    }

    public int getAge() { return _person.getAge(); }
    public String getFullName() { return _person.getFullName(); }
    public String getNameAndAge() { return _person.getNameAndAge(); }
    public String getFirstName() { return _person.getFirstName(); }
    public String getMiddleName() { return _person.getMiddleName(); }
    public String getLastName() { return _person.getLastName(); }
    public String getGender() { return _person.getGender(); }
    public String getEmployeeNumber() { return _employee_number; }
    public void setBirthday(int year, int month, int day) { _person.setBirthday(year, month, day); }
    public Calendar getBirthday() { return _person.getBirthday(); }
    public void setFirstName(String f_name) { _person.setFirstName(f_name); }
    public void setMiddleName(String m_name) { _person.setMiddleName(m_name); }
    public void setLastName(String l_name) { _person.setLastName(l_name); }
    public void setGender(String gender) { _person.setGender(gender); }
    public void setEmployeeNumber(String emp_no) { _employee_number = emp_no; }

    public String toString(){ return _person.toString() + " " + _employee_number; }

    public boolean equals(Object o){
        if(o == null) return false;
        boolean is_equal = false;
        if(o instanceof Employee){
            if(this.toString().equals(o.toString())){
                is_equal = true;
            }
        }
        return is_equal;
    }

    public int hashCode(){
        return this.toString().hashCode();
    }

    public int compareTo(Object o){
        return this.toString().compareTo(o.toString());
    }

    public abstract void setPayInfo(PayInfo pi); // defer implementation
    public abstract double getPay(); // defer implementation
}

// Person.java
package com.pulpfreepress.model;

import java.util.*;
import java.io.*;

public class Person implements Cloneable, Comparable, Serializable {
    private String first_name = null;
    private String middle_name = null;
    private String last_name = null;
    private Calendar birthday = null;
    private String gender = null;

    public static final String MALE = "Male";
    public static final String FEMALE = "Female";

    public Person(String f_name, String m_name, String l_name, int dob_year, int dob_month,
                  int dob_day, String gender){
        first_name = f_name;
        middle_name = m_name;
        last_name = l_name;
        this.gender = gender;

        birthday = Calendar.getInstance();
        birthday.set(dob_year, dob_month, dob_day);
    }

    public int getAge(){
        Calendar today = Calendar.getInstance();
        int now = today.get(Calendar.YEAR);
        int then = birthday.get(Calendar.YEAR);
        return (now - then);
    }

    public String getFullName(){ return (first_name + " " + middle_name + " " + last_name); }

    public String getFirstName(){ return first_name; }
    public void setFirstName(String f_name) { first_name = f_name; }

    public String getMiddleName(){ return middle_name; }
    public void setMiddleName(String m_name){ middle_name = m_name; }

    public String getLastName(){ return last_name; }
    public void setLastName(String l_name){ last_name = l_name; }

    public String getNameAndAge(){ return (getFullName() + " " + getAge()); }

    public String getGender(){ return gender; }
    public void setGender(String gender){ this.gender = gender; }

    public void setBirthday(int year, int month, int day){ birthday.set(year, month, day); }
    public Calendar getBirthday(){ return birthday; }

    public String toString(){
        return this.getFullName() + " " + gender + " " + birthday.get(Calendar.DATE) + "/"
               + birthday.get(Calendar.MONTH) + "/" + birthday.get(Calendar.YEAR);
    }

    public boolean equals(Object o){
        if(o == null) return false;
        boolean is_equal = false;
        if(o instanceof Person){
            if(this.first_name.equals(((Person)o).first_name) &&
               this.middle_name.equals(((Person)o).middle_name) &&
               this.last_name.equals(((Person)o).last_name) && this.gender.equals(((Person)o).gender) &&
               (this.birthday.get(Calendar.YEAR) == ((Person)o).birthday.get(Calendar.YEAR)) &&
               (this.birthday.get(Calendar.MONTH) == ((Person)o).birthday.get(Calendar.MONTH)) &&
               (this.birthday.get(Calendar.DATE) == ((Person)o).birthday.get(Calendar.DATE)) ){
                is_equal = true;
            }
        }
        return is_equal;
    }

    public int hashCode(){
        return this.toString().hashCode();
    }

    public Object clone() throws CloneNotSupportedException {
        super.clone();
        return new Person(new String(first_name), new String(middle_name), new String(last_name),
                          birthday.get(Calendar.YEAR), birthday.get(Calendar.MONTH),
                          birthday.get(Calendar.DATE), new String(gender));
    }

    public int compareTo(Object o){
        return this.toString().compareTo(o.toString());
    }
}

// HourlyEmployee.java
package com.pulpfreepress.model;

import java.util.*;
import java.io.*;
import java.text.*;

public class HourlyEmployee extends Employee implements Cloneable, Comparable, Serializable{
    private double _hours_worked = 0;
    private double _hourly_rate = 0.0;

    public HourlyEmployee(String f_name, String m_name, String l_name, int dob_year, int dob_month,
                          int dob_day, String gender, String employee_number){
        super(f_name, m_name, l_name, dob_year, dob_month, dob_day, gender, employee_number);
    }

    public void setPayInfo(PayInfo pi){
        _hours_worked = pi.getHoursWorked();
        _hourly_rate = pi.getHourlyRate();
    }

    public double getPay() { return _hours_worked * _hourly_rate; }

    public String toString() {
        NumberFormat pay_format = NumberFormat.getInstance();
        return super.toString() + " $" + pay_format.format(getPay());
    }

    public Object clone() throws CloneNotSupportedException {
        super.clone();
        return new HourlyEmployee(new String(getFirstName()), new String(getMiddleName()),
                                  new String(getLastName()), getBirthday().get(Calendar.YEAR),
                                  getBirthday().get(Calendar.MONTH), getBirthday().get(Calendar.DATE),
                                  new String(getGender()), new String(getEmployeeNumber()));
    }

    public boolean equals(Object o){
        if(o == null) return false;
        boolean is_equal = false;
        if(o instanceof HourlyEmployee){
            if(this.toString().equals(o.toString())){
                is_equal = true;
            }
        }
        return is_equal;
    }

    public int hashCode(){
        return this.toString().hashCode();
    }

    public int compareTo(Object o){
        return this.toString().compareTo(o.toString());
    }
}

// SalariedEmployee.java
package com.pulpfreepress.model;

import java.util.*;
import java.io.*;
import java.text.*;

public class SalariedEmployee extends Employee implements Cloneable, Comparable, Serializable {
    private double _salary = 0;

    public SalariedEmployee(String f_name, String m_name, String l_name, int dob_year, int dob_month,
                            int dob_day, String gender, String employee_number){
        super(f_name, m_name, l_name, dob_year, dob_month, dob_day, gender, employee_number);
    }

    public void setPayInfo(PayInfo pi){
        _salary = pi.getSalary();
    }

    public double getPay() { return (_salary/12.0)/2.0; }

    public String toString() {
        NumberFormat pay_format = NumberFormat.getInstance();
        return super.toString() + "                                                            $" + 
               pay_format.format(getPay());
    }

    public Object clone() throws CloneNotSupportedException {
        super.clone();
        return new SalariedEmployee(new String(getFirstName()), new String(getMiddleName()),
                                    new String(getLastName()), getBirthday().get(Calendar.YEAR),
                                    getBirthday().get(Calendar.MONTH), getBirthday().get(Calendar.DATE),
                                    new String(getGender()), new String(getEmployeeNumber()));
    }

    public boolean equals(Object o){
        if(o == null) return false;
        boolean is_equal = false;
        if(o instanceof SalariedEmployee){
            if(this.toString().equals(o.toString())){
                is_equal = true;
            }
        }
        return is_equal;
    }

    public int hashCode(){
        return this.toString().hashCode();
    }

    public int compareTo(Object o){
        return this.toString().compareTo(o.toString());
    }
}

// PayInfo.java
package com.pulpfreepress.model;

public class PayInfo {
    private double _salary = 0;
    private double _hours_worked = 0;
    private double _hourly_rate = 0;

    public PayInfo(double salary){
        _salary = salary;
    }

    public PayInfo(double hours_worked, double hourly_rate){
        _hours_worked = hours_worked;
        _hourly_rate = hourly_rate;
    }

    public PayInfo(){ }

    public double getHoursWorked(){ return _hours_worked; }
    public double getHourlyRate(){ return _hourly_rate; }
    public double getSalary(){ return _salary; }
}

// IEmployeeFactory.java
package com.pulpfreepress.model;

public interface IEmployeeFactory {
    IEmployee getNewSalariedEmployee(String f_name, String m_name, String l_name, int dob_year,
                                     int dob_month, int dob_day, String gender, String employee_number);
    IEmployee getNewHourlyEmployee(String f_name, String m_name, String l_name, int dob_year,
                                   int dob_month, int dob_day, String gender, String employee_number);
}

// EmployeeFactory.java
package com.pulpfreepress.model;

public class EmployeeFactory implements IEmployeeFactory {
    public IEmployee getNewSalariedEmployee(String f_name, String m_name, String l_name,
                                            int dob_year, int dob_month, int dob_day, String gender,
                                            String employee_number){
        return new SalariedEmployee(f_name, m_name, l_name, dob_year, dob_month, dob_day, gender,
                                    employee_number);
    }

    public IEmployee getNewHourlyEmployee(String f_name, String m_name, String l_name, int dob_year,
                                          int dob_month, int dob_day, String gender,
                                          String employee_number){
        return new HourlyEmployee(f_name, m_name, l_name, dob_year, dob_month, dob_day, gender,
                                  employee_number);
    }
}

// Model.java
package com.pulpfreepress.model;

import com.pulpfreepress.interfaces.*;
import java.util.*;
import java.io.*;

public class Model implements iModel {
    private List employee_list = null;
    private IEmployeeFactory employee_factory = null;

    public Model(){
        employee_list = new LinkedList();
        employee_factory = new EmployeeFactory();
    }

    public void createHourlyEmployee(String fname, String mname, String lname,
                                     int dob_year, int dob_month, int dob_day,
                                     String gender, String employee_number,
                                     double hours_worked, double hourly_rate){
        PayInfo pay_info = new PayInfo(hours_worked, hourly_rate);
        IEmployee employee = employee_factory.getNewHourlyEmployee(fname, mname, lname, dob_year,
                                                                 dob_month, dob_day, gender,
                                                                 employee_number);
        employee.setPayInfo(pay_info);
        employee_list.add(employee);
    }

    public void createSalariedEmployee(String fname, String mname, String lname,
                                       int dob_year, int dob_month, int dob_day,
                                       String gender, String employee_number,
                                       double salary){
        PayInfo pay_info = new PayInfo(salary);
        IEmployee employee = employee_factory.getNewSalariedEmployee(fname, mname, lname, dob_year,
                                                                     dob_month, dob_day, gender,
                                                                     employee_number);
        employee.setPayInfo(pay_info);
        employee_list.add(employee);
    }

    public void editEmployeeInfo(String fname, String mname, String lname,
                                 int dob_year, int dob_month, int dob_day,
                                 String gender, String employee_number){
        IEmployee employee = null;
        for(Iterator it = employee_list.iterator(); it.hasNext();){
            employee = (IEmployee)it.next();
            if(employee.getEmployeeNumber().equals(employee_number)) break;
        }

        employee.setFirstName(fname);
        employee.setMiddleName(mname);
        employee.setLastName(lname);
        employee.setBirthday(dob_year, dob_month, dob_day);
    }

    public String[] getAllEmployeesInfo(){
        String[] emp_info = new String[employee_list.size()];
        Iterator it = employee_list.iterator();
        for(int i = 0; it.hasNext();){
            emp_info[i++] = it.next().toString();
        }
        return emp_info;
    }

    public String getEmployeeInfo(String employee_number){
        IEmployee employee = null;
        for(Iterator it = employee_list.iterator(); it.hasNext();){
            employee = (IEmployee)it.next();
            if(employee.getEmployeeNumber().equals(employee_number)) break;
        }
        return employee.toString();
    }

    public void sortEmployees(){
        Collections.sort(employee_list);
    }

    public void deleteEmployee(String employee_number){
        IEmployee employee = null;
        for(Iterator it = employee_list.iterator(); it.hasNext();){
            employee = (IEmployee)it.next();
            if(employee.getEmployeeNumber().equals(employee_number)){
                employee_list.remove(employee);
                break;
            }
        }
    }

    public void saveEmployeesToFile(File file){
        if(file == null){
            file = new File("employees.dat");
        }

        FileOutputStream fos = null;
        ObjectOutputStream oos = null;

        try{
            fos = new FileOutputStream(file);
            oos = new ObjectOutputStream(fos);
            oos.writeObject(employee_list);
            oos.close();
        }catch(Exception e){
            System.out.println("Problem saving employees file to disk!");
        }
    }

    public void loadEmployeesFromFile(File file){
        if(file == null){
            file = new File("employees.dat");
        }

        FileInputStream fis = null;
        ObjectInputStream ois = null;

        try{
            fis = new FileInputStream(file);
            ois = new ObjectInputStream(fis);
            employee_list = (LinkedList)ois.readObject();
            ois.close();
        }catch(Exception e){
            System.out.println("Problem saving employees file to disk!");
        }
    }
}
6.6 com.pulpfreepress.utils

该包包含 CommandFactory CommandProperties 类,代码前面已给出。

6.7 com.pulpfreepress.view

该包包含 View EditEmployeeDialog 类,用于实现用户界面:

// View.java
package com.pulpfreepress.view;

import com.pulpfreepress.interfaces.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.io.*;

public class View extends JFrame implements iView {
    private JPanel panel1 = null;
    private JTextArea textarea1 = null;
    private int initial_frame_position_x = 50;
    private int initial_frame_position_y = 50;
    private int initial_frame_width = 400;
    private int initial_frame_height = 400;
    private int initial_textarea_rows = 20;
    private int initial_textarea_columns = 20;
    private EditEmployeeDialog edit_employee_dialog = null;
    private String[] current_employee_list = null;
    private JMenuItem delete_employee_menuitem = null;
    private JMenuItem edit_employee_menuitem = null;

    public View(ActionListener al){
        this.setupGUI(al);
    }

    public void displayEmployeeInfo(String[] employees_info){
        textarea1.setText("");
        for(int i = 0; i<employees_info.length; i++){
            textarea1.append(employees_info[i] + "\n");
        }
        current_employee_list = employees_info;
        if(current_employee_list.length == 0){
            this.delete_employee_menuitem.setEnabled(false);
            this.edit_employee_menuitem.setEnabled(false);
        }else{
            this.delete_employee_menuitem.setEnabled(true);
            this.edit_employee_menuitem.setEnabled(true);
        }
    }

    public String[] getNewHourlyEmployeeInfo(){
        edit_employee_dialog.setTitle("New Hourly Employee");
        edit_employee_dialog.clearTextFields();
        edit_employee_dialog.showHourlyInfo();
        edit_employee_dialog.hideSalaryInfo();
        edit_employee_dialog.enableEmployeeNumberTextField();
        edit_employee_dialog.setVisible(true);

        return edit_employee_dialog.getEmployeeInfo();
    }

    public String[] getNewSalariedEmployeeInfo(){
        edit_employee_dialog.setTitle("New Salaried Employee");
        edit_employee_dialog.clearTextFields();
        edit_employee_dialog.showSalaryInfo();
        edit_employee_dialog.hideHourlyInfo();
        edit_employee_dialog.enableEmployeeNumberTextField();
        edit_employee_dialog.setVisible(true);
        return edit_employee_dialog.getEmployeeInfo();
    }

    public String[] getEditEmployeeInfo(){
        edit_employee_dialog.setTitle("Edit Employee Information");
        edit_employee_dialog.clearTextFields();
        edit_employee_dialog.hideHourlyAndSalaryInfo();
        edit_employee_dialog.disableEmployeeNumberTextField();
        String emp_info_string = current_employee_list[getSelectedLineNumber()];
        String[] emp_string_array = employeeInfoStringToStringArray(emp_info_string);
        edit_employee_dialog.populateTextFields(emp_string_array);
        edit_employee_dialog.setVisible(true);
        return edit_employee_dialog.getEditedEmployeeInfo();
    }

    public String getDeleteEmployeeInfo(){
        String emp_number = "";
        if(current_employee_list != null){
            String emp_info_string = current_employee_list[getSelectedLineNumber()];
            String[] emp_string_array = employeeInfoStringToStringArray(emp_info_string);
            if(emp_string_array != null){
                emp_number = emp_string_array[7];
            }
        }
        return emp_number;
    }

    private JMenuBar setupMenuBar(ActionListener al){
        JMenuBar menubar = new JMenuBar();

        JMenu file_menu = new JMenu("File");
        JMenuItem file_loadEmployees_menuitem = new JMenuItem("Load Employees...");
        file_loadEmployees_menuitem.setActionCommand("Load");
        file_loadEmployees_menuitem.addActionListener(al);
        JMenuItem file_saveEmployees_menuitem = new JMenuItem("Save Employees...");
        file_saveEmployees_menuitem.setActionCommand("Save");
        file_saveEmployees_menuitem.addActionListener(al);
        JMenuItem file_exit_menuitem = new JMenuItem("Exit");
        file_exit_menuitem.addActionListener(al);

        file_menu.add(file_loadEmployees_menuitem);
        file_menu.add(file_saveEmployees_menuitem);
        file_menu.add(file_exit_menuitem);

        menubar.add(file_menu);

        JMenu employees_menu = new JMenu("Employees");
        JMenuItem employees_list_menuitem = new JMenuItem("List");
        employees_list_menuitem.addActionListener(al);
        JMenuItem employees_sort_menuitem = new JMenuItem("Sort");
        employees_sort_menuitem.addActionListener(al);
        JMenuItem employees_newHourlyEmployee_menuitem = new JMenuItem("New Hourly...");
        employees_newHourlyEmployee_menuitem.setActionCommand("NewHourlyEmployee");
        employees_newHourlyEmployee_menuitem.addActionListener(al);
        JMenuItem employees_newSalariedEmployee_menuitem = new JMenuItem("New Salaried...");
        employees_newSalariedEmployee_menuitem.setActionCommand("NewSalariedEmployee");
        employees_newSalariedEmployee_menuitem.addActionListener(al);
        JMenuItem employees_editEmployee_menuitem = new JMenuItem("Edit Employee...");
        employees_editEmployee_menuitem.setActionCommand("EditEmployee");
        employees_editEmployee_menuitem.addActionListener(al);
        JMenuItem employees_deleteEmployee_menuitem = new JMenuItem("Delete Employee");
        employees_deleteEmployee_menuitem.setActionCommand("DeleteEmployee");
        employees_deleteEmployee_menuitem.addActionListener(al);
        this.delete_employee_menuitem = employees_deleteEmployee_menuitem;
        this.edit_employee_menuitem = employees_editEmployee_menuitem;
        if(current_employee_list == null){
            this.delete_employee_menuitem.setEnabled(false);
            this.edit_employee_menuitem.setEnabled(false);
        }

        employees_menu.add(employees_list_menuitem);
        employees_menu.add(employees_sort_menuitem);
        employees_menu.add(employees_newHourlyEmployee_menuitem);
        employees_menu.add(employees_newSalariedEmployee_menuitem);
        employees_menu.add(employees_editEmployee_menuitem);
        employees_menu.add(employees_deleteEmployee_menuitem);

        menubar.add(employees_menu);

        return menubar;
    }

    private void setupGUI(ActionListener al){
        edit_employee_dialog = new EditEmployeeDialog(this);
        textarea1 = new JTextArea(initial_textarea_rows, initial_textarea_columns);
        JScrollPane scrollpane = new JScrollPane(textarea1);
        this.getContentPane().add(scrollpane);
        this.setJMenuBar(this.setupMenuBar(al));
        this.setSize(initial_frame_height, initial_frame_width);
        this.setLocation(initial_frame_position_x, initial_frame_position_y);
        this.pack();
        this.show();
    }

    private int getSelectedLineNumber(){
        int caret_position = textarea1.getCaretPosition();
        int line_number = 0;
        int string_length = 0;
        try{
            StringTokenizer st = new StringTokenizer(textarea1.getText(), "\n");
            while(caret_position >= (string_length += st.nextToken().length()) ){
                line_number++;
            }
        }catch(NoSuchElementException ignored){ }

        if(line_number >= current_employee_list.length){
            line_number = current_employee_list.length-1;
        }
        return line_number;
    }

    private String[] employeeInfoStringToStringArray(String emp_string){
        StringTokenizer st = new StringTokenizer(emp_string);
        String fname = st.nextToken();
        String mname = st.nextToken();
        String lname = st.nextToken();
        String gender = st.nextToken();
        String bday = st.nextToken();
        String emp_no = st.nextToken();

        StringTokenizer st2 = new StringTokenizer(bday,"/");
        String day = st2.nextToken();
        String month = st2.nextToken();
        String year = st2.nextToken();

        String[] string_array = {fname, mname, lname, gender, year, month, day, emp_no};
        return string_array;
    }

    public File getSaveFile(){
        File file = null;
        JFileChooser file_chooser = new JFileChooser();
        int result = file_chooser.showSaveDialog(this);
        switch(result){
            case JFileChooser.APPROVE_OPTION: { file = file_chooser.getSelectedFile();
                                                break;
                                              }
            case JFileChooser.CANCEL_OPTION: break;
        }
        return file;
    }

    public File getLoadFile(){
        File file = null;
        JFileChooser file_chooser = new JFileChooser();
        int result = file_chooser.showOpenDialog(this);
        switch(result){
            case JFileChooser.APPROVE_OPTION: { file = file_chooser.getSelectedFile();
                                                break;
                                              }
            case JFileChooser.CANCEL_OPTION: break;
        }
        return file;
    }
}

// EditEmployeeDialog.java
package com.pulpfreepress.view;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.regex.*;

public class EditEmployeeDialog extends JDialog implements ActionListener {
    private JLabel label1 = null;
    private JLabel label2 = null;
    private JLabel label3 = null;
    private JLabel label4 = null;
    private JLabel label5 = null;
    private JLabel label6 = null;
    private JLabel label7 = null;
    private JLabel label8 = null;
    private JLabel label9 = null;
    private JLabel label10 = null;
    private JLabel label11 = null;

    private JTextField textfield1 = null;
    private JTextField textfield2 = null;
    private JTextField textfield3 = null;
    private JTextField textfield4 = null;
    private JTextField textfield5 = null;
    private JTextField textfield6 = null;
    private JTextField textfield7 = null;
    private JTextField textfield8 = null;
    private JTextField textfield9 = null;
    private JTextField textfield10 = null;
    private JTextField textfield11 = null;

    public EditEmployeeDialog(JFrame frame){
        super(frame, "Employee Information Dialog", true); // modal dialog
        this.setupGUI();
        this.setVisible(false);
    }

    public void actionPerformed(ActionEvent ae){
        if(ae.getActionCommand().equals("Submit")){
            boolean bool_val = verifyFieldValues();
            if(bool_val){
                this.setVisible(false);
            }
        }
    }

    private void setupGUI(){
        label1 = new JLabel("First Name:");
        label2 = new JLabel("Middle Name:");
        label3 = new JLabel("Last Name:");
        label4 = new JLabel("Gender:");
        label5 = new JLabel("Birth Year:");
        label6 = new JLabel("Birth Month:");
        label7 = new JLabel("Birth Day:");
        label8 = new JLabel("Employee Number:");
        label9 = new JLabel("Hours Worked:");
        label10 = new JLabel("Hourly Rate");
        label11 = new JLabel("Salary:");

        textfield1 = new JTextField(20);
        textfield2 = new JTextField(20);
        textfield3 = new JTextField(20);
        textfield4 = new JTextField(20);
        textfield5 = new JTextField(20);
        textfield6 = new JTextField(20);
        textfield7 = new JTextField(20);
        textfield8 = new JTextField(20);
        textfield9 = new JTextField(20);
        textfield10 = new JTextField(20);
        textfield11 = new JTextField(20);

        JButton button1 = new JButton("Submit");
        button1.addActionListener(this);

        this.getContentPane().setLayout(new GridLayout(12, 2, 0, 0));
        this.getContentPane().add(label1);
        this.getContentPane().add(textfield1);
        this.getContentPane().add(label2);
        this.getContentPane().add(textfield2);
        this.getContentPane().add(label3);
        this.getContentPane().add(textfield3);
        this.getContentPane().add(label4);
        this.getContentPane().add(textfield4);
        this.getContentPane().add(label5);
        this.getContentPane().add(textfield5);
        this.getContentPane().add(label6);
        this.getContentPane().add(textfield6);
        this.getContentPane().add(label7);
        this.getContentPane().add(textfield7);
        this.getContentPane().add(label8);
        this.getContentPane().add(textfield8);
        this.getContentPane().add(label9);
        this.getContentPane().add(textfield9);
        this.getContentPane().add(label10);
        this.getContentPane().add(textfield10);
        this.getContentPane().add(label11);
        this.getContentPane().add(textfield11);
        this.getContentPane().add(new JPanel());
        this.getContentPane().add(new JPanel().add(button1));

        this.pack();
    }

    public String[] getEmployeeInfo(){
        String[] emp_info = new String[11];

        emp_info[0] = textfield1.getText();
        emp_info[1] = textfield2.getText();
        emp_info[2] = textfield3.getText();
        emp_info[3] = textfield4.getText();
        emp_info[4] = textfield5.getText();
        emp_info[5] = textfield6.getText();
        emp_info[6] = textfield7.getText();
        emp_info[7] = textfield8.getText();
        emp_info[8] = textfield9.getText();
        emp_info[9] = textfield10.getText();
        emp_info[10] = textfield11.getText();

        return emp_info;
    }

    public void hideSalaryInfo(){
        label11.setVisible(false);
        textfield11.setVisible(false);
    }

    public void showSalaryInfo(){
        label11.setVisible(true);
        textfield11.setVisible(true);
    }

    public void hideHourlyInfo(){
        label9.setVisible(false);
        textfield9.setVisible(false);
        label10.setVisible(false);
        textfield10.setVisible(false);
    }

    public void showHourlyInfo(){
        label9.setVisible(true);
        textfield9.setVisible(true);
        label10.setVisible(true);
        textfield10.setVisible(true);
    }

    public void clearTextFields(){
        textfield1.setText("");
        textfield2.setText("");
        textfield3.setText("");
        textfield4.setText("");
        textfield5.setText("yyyy");
        textfield6.setText("mm");
        textfield7.setText("dd");
        textfield8.setText("");
        textfield9.setText("0.00");
        textfield10.setText("0.00");
        textfield11.setText("0.00");
    }

    public void hideHourlyAndSalaryInfo(){
        hideSalaryInfo();
        hideHourlyInfo();
    }

    public void disableEmployeeNumberTextField(){
        textfield8.setEnabled(false);
    }

    public void enableEmployeeNumberTextField(){
        textfield8.setEnabled(true);
    }

    public void populateTextFields(String[] emp_info){
        textfield1.setText(emp_info[0]);
        textfield2.setText(emp_info[1]);
        textfield3.setText(emp_info[2]);
        textfield4.setText(emp_info[3]);
        textfield5.setText(emp_info[4]);
        if(Integer.parseInt(emp_info[5]) < 10) {
            textfield6.setText("0" + emp_info[5]);
        } else {
            textfield6.setText(emp_info[5]);
        }
        if(Integer.parseInt(emp_info[6]) < 10) {
            textfield7.setText("0" + emp_info[6]);
        } else {
            textfield7.setText(emp_info[6]);
        }
        textfield8.setText(emp_info[7]);
    }

    public String[] getEditedEmployeeInfo(){
        String[] emp_info = new String[8];

        emp_info[0] = textfield1.getText();
        emp_info[1] = textfield2.getText();
        emp_info[2] = textfield3.getText();
        emp_info[3] = textfield4.getText();
        emp_info[4] = textfield5.getText();
        emp_info[5] = textfield6.getText();
        emp_info[6] = textfield7.getText();
        emp_info[7] = textfield8.getText();

        return emp_info;
    }

    private boolean verifyFieldValues(){
        boolean ok = true;

        if(Pattern.matches("[A-Za-z]{1,64}", textfield1.getText())){
            // do nothing
        } else {
            textfield1.setText("-----Invalid First Name-----");
            ok = false;
        }

        if(Pattern.matches("[A-Za-z]{1,64}", textfield2.getText())){
            // do nothing
        } else {
            textfield2.setText("-----Invalid Middle Name-----");
            ok = false;
        }

        if(Pattern.matches("[A-Za-z]{1,64}", textfield3.getText())){
            // do nothing
        } else {
            textfield3.setText("-----Invalid Last Name-----");
            ok = false;
        }

        if(textfield4.getText().equals("Male") || textfield4.getText().equals("Female")){
            // do nothing
        } else {
            textfield4.setText("-----Male or Female-----");
            ok = false;
        }

        if(Pattern.matches("[1|2]{1}[0-9]{3}", textfield5.getText())){
            // do nothing
        } else {
            textfield5.setText("----invalid  Birth Year----");
            ok = false;
        }

        if(Pattern.matches("[0|1]{1}[0-9]{1}", textfield6.getText())){
            if((Integer.parseInt(textfield6.getText()) < 1) ||
               (Integer.parseInt(textfield6.getText()) > 12) ){
                textfield6.setText("----invalid  Birth Month----");
                ok = false;
            }
        } else {
            textfield6.setText("----invalid  Birth Month----");
            ok = false;
        }

        if(Pattern.matches("[0-3]{1}[0-9]{1}", textfield7.getText())){
            if((Integer.parseInt(textfield7.getText()) < 1) ||
               (Integer.parseInt(textfield7.getText()) > 31) ){
                textfield7.setText("----invalid  Birth Day----");
                ok = false;
            }
        } else {
            textfield7.setText("----invalid  Birth Day----");
            ok = false;
        }

        if(Pattern.matches("[0-9]{6}", textfield8.getText())){
            // do nothing
        } else {
            textfield8.setText("----invalid  Employee Number----");
            ok = false;
        }

        if(Pattern.matches("[0-9]{1,2}[.]{1}[0-9]{2}", textfield9.getText())){
            // do nothing
        } else {
            textfield9.setText("----invalid  Hours Worked----");
            ok = false;
        }

        if(Pattern.matches("[0-9]{1,3}[.]{1}[0-9]{2}", textfield10.getText())){
            // do nothing
        } else {
            textfield10.setText("----invalid  Hourly Rate----");
            ok = false;
        }

        if(Pattern.matches("[0-9]{1,7}[.]{1}[0-9]{2}", textfield11.getText())){
            // do nothing
        } else {
            textfield11.setText("----invalid  Salary----");
            ok = false;
        }

        return ok;
    }
}

通过以上代码和设计,实现了一个完整的员工管理系统,利用命令模式将操作封装成对象,提高了代码的可维护性和可扩展性。同时,通过动态工厂模式实现了命令的动态加载和执行。

这个员工管理系统的主要操作流程如下:
1. 用户启动应用程序, Controller 类初始化模型和视图。
2. 用户通过视图界面进行操作,如创建、编辑、删除员工等。
3. 视图触发 ActionListener actionPerformed() 方法。
4. actionPerformed() 方法通过 CommandFactory 获取相应的命令实例。
5. 设置命令的模型和视图。
6. 执行命令的 execute() 方法,完成相应的操作。

可以用 mermaid 流程图表示如下:

graph LR
    A[用户启动应用程序] --> B[Controller 初始化模型和视图]
    B --> C[用户通过视图界面操作]
    C --> D[视图触发 actionPerformed 方法]
    D --> E[CommandFactory 获取命令实例]
    E --> F[设置命令的模型和视图]
    F --> G[执行命令的 execute 方法]
    G --> H[完成操作,更新视图]

通过这个综合示例,可以更深入地理解命令模式在实际应用中的使用,以及如何将其与动态工厂模式结合,实现一个功能丰富的应用程序。

Java 命令模式与员工管理系统实现

7. 员工管理系统操作流程详解

在员工管理系统中,各个操作都有其特定的流程,下面详细介绍几个主要操作的具体步骤。

7.1 创建新员工

创建新员工分为创建小时工和创建 salaried 员工两种情况,具体流程如下:
1. 用户选择创建新员工类型 :用户在视图界面的菜单中选择“New Hourly…”或“New Salaried…”。
2. 弹出员工信息对话框 :视图调用 EditEmployeeDialog 类,根据用户选择的员工类型,显示相应的信息输入界面。
3. 用户输入员工信息 :用户在对话框中输入员工的基本信息,如姓名、性别、出生日期、员工编号等,以及与员工类型相关的薪资信息(小时工的工作小时数和小时费率,salaried 员工的月薪)。
4. 验证信息 :用户点击“Submit”按钮后, EditEmployeeDialog 类的 verifyFieldValues() 方法会对输入的信息进行验证,确保信息的有效性。
5. 创建员工对象 :如果信息验证通过,视图将获取用户输入的信息,并调用模型的相应方法创建员工对象。对于小时工,调用 Model 类的 createHourlyEmployee() 方法;对于 salaried 员工,调用 Model 类的 createSalariedEmployee() 方法。
6. 更新视图 :模型将新员工添加到员工列表中,并更新视图显示所有员工的信息。

可以用表格总结创建新员工的流程:
| 步骤 | 操作 | 涉及类 |
| ---- | ---- | ---- |
| 1 | 用户选择创建新员工类型 | View |
| 2 | 弹出员工信息对话框 | View EditEmployeeDialog |
| 3 | 用户输入员工信息 | EditEmployeeDialog |
| 4 | 验证信息 | EditEmployeeDialog |
| 5 | 创建员工对象 | View Model |
| 6 | 更新视图 | Model View |

7.2 编辑员工信息

编辑员工信息的流程如下:
1. 用户选择要编辑的员工 :用户在视图的文本区域中选择要编辑的员工信息行。
2. 弹出编辑员工信息对话框 :视图调用 EditEmployeeDialog 类,将所选员工的信息填充到对话框的文本框中。
3. 用户修改员工信息 :用户在对话框中修改员工的基本信息。
4. 验证信息 :用户点击“Submit”按钮后, EditEmployeeDialog 类的 verifyFieldValues() 方法会对修改后的信息进行验证。
5. 更新员工信息 :如果信息验证通过,视图将获取用户修改后的信息,并调用模型的 editEmployeeInfo() 方法更新员工信息。
6. 更新视图 :模型更新员工信息后,更新视图显示所有员工的信息。

可以用 mermaid 流程图表示编辑员工信息的流程:

graph LR
    A[用户选择要编辑的员工] --> B[弹出编辑员工信息对话框]
    B --> C[用户修改员工信息]
    C --> D[验证信息]
    D -->|验证通过| E[更新员工信息]
    D -->|验证失败| C
    E --> F[更新视图]
7.3 删除员工

删除员工的流程如下:
1. 用户选择要删除的员工 :用户在视图的文本区域中选择要删除的员工信息行。
2. 视图获取员工编号 :视图调用 getDeleteEmployeeInfo() 方法,从所选员工信息中提取员工编号。
3. 调用模型删除员工 :视图将员工编号传递给模型的 deleteEmployee() 方法,模型从员工列表中删除该员工。
4. 更新视图 :模型删除员工后,更新视图显示所有员工的信息。

8. 系统异常处理

在员工管理系统中,可能会出现各种异常情况,如命令未找到、文件保存或加载失败等。下面介绍系统中对这些异常的处理方式。

8.1 命令未找到异常

Controller 类的 actionPerformed() 方法中,当通过 CommandFactory 获取命令实例时,如果命令类不存在,会抛出 CommandNotFoundException 异常。异常处理代码如下:

public void actionPerformed(ActionEvent ae){
    try{
        BaseCommand command = command_factory.getCommand(ae.getActionCommand());
        command.setModel(its_model);
        command.setView(its_view);
        command.execute();
    }catch(CommandNotFoundException cnfe){
        System.out.println("Command not found!");
    }
}

当捕获到 CommandNotFoundException 异常时,系统会输出“Command not found!”信息,提示用户输入的命令无效。

8.2 文件保存或加载异常

Model 类的 saveEmployeesToFile() loadEmployeesFromFile() 方法中,当进行文件保存或加载操作时,如果出现异常,会捕获异常并输出相应的错误信息。以 saveEmployeesToFile() 方法为例,异常处理代码如下:

public void saveEmployeesToFile(File file){
    if(file == null){
        file = new File("employees.dat");
    }

    FileOutputStream fos = null;
    ObjectOutputStream oos = null;

    try{
        fos = new FileOutputStream(file);
        oos = new ObjectOutputStream(fos);
        oos.writeObject(employee_list);
        oos.close();
    }catch(Exception e){
        System.out.println("Problem saving employees file to disk!");
    }
}

当捕获到异常时,系统会输出“Problem saving employees file to disk!”信息,提示用户文件保存失败。

9. 系统优点与可扩展性分析
9.1 系统优点
  • 解耦性 :通过命令模式,将操作的知识与执行操作的对象解耦,使得系统的各个组件可以独立开发和维护。例如, Controller 类只负责接收用户操作并调用相应的命令,而具体的操作实现由各个命令类完成。
  • 可维护性 :将不同的操作封装在不同的命令类中,使得代码结构清晰,易于理解和维护。如果需要修改某个操作的实现,只需要修改相应的命令类即可。
  • 可扩展性 :系统采用动态工厂模式,可以根据需要动态加载和执行不同的命令。如果需要添加新的操作,只需要创建新的命令类,并在 Command.properties 文件中进行配置即可。
9.2 可扩展性分析
  • 添加新命令 :如果需要添加新的操作,如统计员工总数、计算员工平均薪资等,只需要创建新的命令类,继承 BaseCommand 类,并实现 execute() 方法。然后在 Command.properties 文件中添加新的命令映射,系统就可以支持新的操作。
  • 扩展员工类型 :如果需要添加新的员工类型,如兼职员工、实习生等,只需要在 com.pulpfreepress.model 包中创建新的员工类,实现 IEmployee 接口,并在 EmployeeFactory 类中添加相应的创建方法。同时,在视图界面中添加相应的操作菜单和信息输入界面,系统就可以支持新的员工类型。
10. 总结

通过本文的介绍,我们深入了解了命令模式的概念和实现方式,以及如何将其与动态工厂模式结合,实现一个功能丰富的员工管理系统。命令模式的使用使得系统具有良好的解耦性、可维护性和可扩展性,能够更好地应对系统的变化和需求。

在实际开发中,我们可以根据具体的需求和场景,灵活运用命令模式和其他设计模式,提高代码的质量和可维护性。同时,要注意异常处理和代码的健壮性,确保系统的稳定性和可靠性。希望本文对大家在 Java 开发和设计模式的应用方面有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值