哈工大2020软件构造Lab3实验报告

2023-11-20

本项目于4.21日实验课验收

更新完成

如果有所参考 请点点关注 点点赞GitHub Follow一下谢谢

HIT

2020春计算机学院《软件构造》课程Lab3实验报告

  • Software Construction 2020 Spring
  • Lab-3 Reusability and Maintainability oriented Software Construction
  • CSDN博客

文章目录

1 实验目标概述

本次实验覆盖课程第 3、4、5 章的内容,目标是编写具有可复用性和可维护性的软件,主要使用以下软件构造技术:

  • 子类型、泛型、多态、重写、重载
  • 继承、代理、组合
  • 常见的 OO 设计模式
  • 语法驱动的编程、正则表达式
  • 基于状态的编程
  • API 设计、API 复用

本次实验给定了五个具体应用(高铁车次管理、航班管理、操作系统进程管理、大学课表管理、学习活动日程管理),学生不是直接针对五个应用分别编程实现,而是通过 ADT 和泛型等抽象技术,开发一套可复用的 ADT 及其实现,充分考虑这些应用之间的相似性和差异性,使 ADT 有更大程度的复用(可复用性)和更容易面向各种变化(可维护性)。

2 实验环境配置

3 实验过程

3.1 待开发的三个应用场景

我选择的三个应用场景:

  • 航班管理
  • 高铁车次管理
  • 学习日程管理

这三个场景的异同点:

  • 位置的数量:分别为1个、2个和多个
  • 仅有学习日程的位置可更改
  • 航班为单个资源,高铁为有序多个资源,学习日程为无序多个资源
  • 仅有高铁车次可阻塞
  • 时间均在创建时设定

3.2 面向可复用性和可维护性的设计:PlanningEntry < R >

计划项是一个状态可变的ADT,它保存有一个计划项的时间、地点、资源等有效信息。PlanningEntry在我的设计中是一个接口,设计有各种计划项均要实现的方法以及工厂方法。

3.2.1 PlanningEntry的共性操作

3个工厂方法分别能够返回指定类型的PlanningEntry实现类,以Flight Schedule为例:

/**
 * a factory method for generating an instance of Flight Schedule
 * @param <R>
 * @param location
 * @param timeSlot
 * @param planningEntryNumber
 * @return an empty instance of planning entry of flight schedule
 */
public static <R> FlightSchedule<R> newPlanningEntryOfFlightSchedule(Location location, TimeSlot timeSlot,
        String planningEntryNumber) {
    return new FlightSchedule<R>(location, timeSlot, planningEntryNumber);
}

状态的转换,以目标状态进行分类,能够将状态转换为RUNNING、BLOCKED、CANCELLED、ENDED四种之一(其中转换为ALLOCATED是个性化设计),分别用4个方法来实现。以start()为例:

/**
 * start the planning entry
 * @return true if the entry is started
 */
public Boolean start();

Getter()包括了获取Location、TimeSlot、State、Type、Number、Type一些共有的信息对象。

3.2.2 局部共性特征的设计方案

CommonPlanningEntry类实现了PlanningEntry接口中共性方法,包括了状态转换和Getter方法。
状态转换,以Start()为例:将状态转换委派给state对象的Setter操作,通过常量来进行目标状态的区分。在state对象中,首先判断该转换是否合法(访问EntryStateEnum静态常量进行判断),然后在进行状态覆盖,最后返回操作成功与否的标识。

@Override
public Boolean start() {
    return this.state.setNewState(strPlanningEntryType, "Running");
}

Getter操作访问CommonPlanningEntry中定义的共性成员变量,包括Location、Resource等等。以getLocation()为例:

@Override
public Location getLocation() {
    return this.location;
}

此外,设计抽象方法getPlanningDate()等Spec相同的方法。

/**
 * get the planning date
 * @return LocalDate of planning date
 */
public abstract LocalDate getPlanningDate();

3.2.3 面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)

3个子类型的不同主要在于两方面:Location、TimeSlot、Resource等信息的存储模式和信息的修改。
存储模式:在我的设计中,信息存储的差异统一合并到“信息对象”的内部中,通过不同子类型的不同Getter来得到相应的信息细节。信息对象具体设计在3.3-3.6说明。
以Flight Schedule为例,getLocationOrigin()、getLocationTerminal()方法获得了起飞、降落机场;而在Activity Calendar中则用getStrLocation()获得活动地点。

    /**
     * get the origin location object
     * @return the origin location object
     */
    public String getLocationOrigin() {
        return super.getLocation().getLocations().get(ORIGIN);
    }

    /**
     * get the terminal location object
     * @return the terminal location object
     */
    public String getLocationTerminal() {
        return super.getLocation().getLocations().get(TERMINAL);
	}

由于Location存储为List,因此List大小分别为1、2、n,根据不同的计划项特点设计Getter即可实现不同的特性。
信息修改:根据不同计划项信息的修改特点进行设计。例如allocateResource(),飞机只能分配一个资源,而高铁为多个有序资源(用List< R >存储),活动为多个无序资源。以高铁的分配资源为例:

/**
 * allocate the resource to the flight schedule
 * set the state as ALLOCATED
 * @param resources
 * @return true if the resource is set and state is ALLOCATED
 */
public Boolean allocateResource(R... resources) {
    this.resources.addAll(Arrays.asList(resources));
    this.ORIGIN = 0;
    this.LENGTH = this.resources.size();
    this.TERMINAL = this.resources.size() - 1;
    return this.state.setNewState(strPlanningEntryType, "Allocated");
}

通过不定项的资源作为参数,然后保存到list中,获取长度、起终点标识,并设置状态。
此外,Activity Calendar可以在开始前设置新地点,则再该计划项子类中添加对应的Setter方法。

/**
 * set a new location
 * @param strNewLocation
 */
public void setNewLocation(String strNewLocation) {
    if (this.getState().getStrState().equals("ALLOCATED"))
        this.location = new Location(strNewLocation);
}

最后,重写不同的equals、hashcode、toString方法。以Activity为例:

    @Override
    public String toString() {
        return "{" + " intResourceNumber='" + getIntResourceNumber() + "'" + "}";
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof ActivityCalendar)) {
            return false;
        }
        ActivityCalendar<R> activityCalendar = (ActivityCalendar<R>) o;
        return intResourceNumber == activityCalendar.intResourceNumber;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(intResourceNumber);
}

航班管理javadoc

在这里插入图片描述

高铁管理javadoc

在这里插入图片描述

活动日程javadoc

在这里插入图片描述

JUnit测试

在这里插入图片描述

3.3 面向复用的设计:R

资源有多种,因为Resource被设计为一个接口,有3个实现类:Plane、Train和Document。Resource接口中有3种子类的工厂方法。

在这里插入图片描述

3种子类存储有各自的独特信息,以Document为例:

在这里插入图片描述

其中publishDate在构造时用String输入,降低前置条件,并在构造方法中转换。

this.publishDate = LocalDate.parse(strPublishDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));

3种子类均为immutable,设计有各个成员变量的Getter,并且根据要求重写equals等。以Plane为例:

@Override
public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof Plane)) {
        return false;
    }
    Plane plane = (Plane) o;
    return Objects.equals(number, plane.number) && Objects.equals(strType, plane.strType)
            && intSeats == plane.intSeats && age == plane.age;
}

Junit测试
在这里插入图片描述

3.4 面向复用的设计:Location

由于我选择的3种计划项位置数量各不相同,因此我采用一个List来存储若干的位置,通过PlanningEntry的不同Getter来获取。
Field、AF、RI、Safety:

private final List<String> locations = new ArrayList<String>();
/*
 * AF:
 * locations represent the locations in the plan
 * 
 * RI:
 * locations should be as long as arrival and leaving in class TimeSlot
 * 
 * Safety:
 * do not provide mutator
 */

在构造器中,参数为若干个String类型地址,将这些String均加入List。

/**
 * constructor
 * @param locations
 */
public Location(String... locations) {
    for (String str : locations)
        this.locations.add(str);
    checkRep();
}

因此,Location本质上是一个存储有多个String的List。

Junit测试

在这里插入图片描述

3.5 面向复用的设计:Timeslot

Time Slot和Location是共同设计的,存储有两个List,分别代表对应的位置的到达和离开时间。由此设计,可以精确到每个地点的到达和离开时间,若为第一个地点,则到达和离开时间相同;若为最后一个地点也如此;若只有一个地点,则也如此。
由此,3种不同的计划项,通过不同的Getter实现不同的特征。

private final List<LocalDateTime> arrival = new ArrayList<>();
private final List<LocalDateTime> leaving = new ArrayList<>();
/*
 * AF:
 * arrival[i] represent the time it arrives locations[i]
 * leaving[i] represent the time it leaves locations[i]
 * 
 * when Flight Schedule:
 * length == 2, arrival[0] == leaving[0], arrival[1] == leaving[1]
 * 
 * when Activity Schedule:
 * length == 1, arrival[0] is ending time, leaving[0] is beginning time
 * 
 * RI:
 * the length of arrival and leaving should be equal
 * leaving[i] should be later than arrival[i]
 * when i<length arrival[i] and leaving[i] should be non-null
 * 
 * Safety:
 * do not provide mutator
 */

3.6 面向复用的设计:EntryState及State设计模式

EntryState是一个可变对象,成员变量有类型为enum的state。

private EntryStateEnum state;

AF、RI:

/*
 * AF:
 * the state enum's name represents the state 
 * RI:
 * state must be in enums
 * Safety:
 * it's a mutable object, but do not let the outside modify state directly
 */

在构造方法中,通过字符串参数toUpperCase,再对应到EntryStateEnum中的某一个枚举,进行初始化。

/**
 * constructor
 * @param stateName
 */
public EntryState(String stateName) {
    this.state = EntryStateEnum.valueOf(stateName.toUpperCase());
    checkRep();
}

状态是可变的,因此它需要设置一个Setter,即setNewState()。由于不同的计划项类型,可以设置的state不同,因此参数需要有计划项类型和新状态的字符串。这样的一个方法可以满足各种状态的转换。

/**
 * set the new state
 * @param strPlanningEntryType in {"FlightSchedule", "TrainSchedule", "ActivityCalendar"}
 * @param strNewState
 * @return true if the setting is successful, false if not
 */
public Boolean setNewState(String strPlanningEntryType, String strNewState) {
    assert (strPlanningEntryType.toLowerCase().contains("train")
            || !this.getStrState().toLowerCase().equals("blocked"));
    if (this.setAvailability(strPlanningEntryType, strNewState.toUpperCase())) {
        this.state = EntryStateEnum.valueOf(strNewState.toUpperCase());
        return true;
    }
    return false;
}

判断合法性的工作交给另一个范围值为Boolean的方法setAvailability(),而该方法又将这项工作委派给EntryStateEnum中的静态Map变量。该Map分为两种,一种是可能被Block的,一种则不行。判断是否可以Block的工作在EntryStateEnum中进行,用一个List来保存可以Block的类型的关键字(增强鲁棒性)。

/**
 * judge whether this state can be transferred to the new state
 * @param strPlanningEntryType in {"FlightSchedule", "TrainSchedule", "ActivityCalendar"}
 * @param strNewState
 * @return true if the current state can be transferred to the new state, false if not
 */
private Boolean setAvailability(String strPlanningEntryType, String strNewState) {
    List<EntryStateEnum> availableStatesList = new ArrayList<EntryStateEnum>(
            Arrays.asList(this.getState().newStateAchievable(strPlanningEntryType)));
    return availableStatesList.contains(EntryStateEnum.valueOf(strNewState.toUpperCase()));
}
该方法基于委派EntryState查询“可以到达的新状态”的一个List,确认新状态在该List中,的方法来确定Availability。EntryStateEnum有这些枚举变量:
/**
 * they represent 6 states of the planning entry
 */
WAITING, ALLOCATED, RUNNING, BLOCKED, ENDED, CANCELLED;

静态存储能/不能Block的“可以到达的新状态”的Map,Key为该枚举,Value为List。通过匿名对象初始化方法来初始化。

/**
 * achievable states map for entries able to be blocked
 */
public static final Map<EntryStateEnum, EntryStateEnum[]> newStateAchievableBlockedAble = new HashMap<EntryStateEnum, EntryStateEnum[]>() {
    private static final long serialVersionUID = 1L;
    {
        put(WAITING, new EntryStateEnum[] { ALLOCATED, CANCELLED });
        put(ALLOCATED, new EntryStateEnum[] { RUNNING, CANCELLED });
        put(RUNNING, new EntryStateEnum[] { BLOCKED, ENDED });
        put(BLOCKED, new EntryStateEnum[] { RUNNING, CANCELLED });
        put(CANCELLED, new EntryStateEnum[] {});
        put(ENDED, new EntryStateEnum[] {});
    }
};

/**
 * achievable states map for entries not able to be blocked
 */
public static final Map<EntryStateEnum, EntryStateEnum[]> newStateAchievableBlockedDisable = new HashMap<EntryStateEnum, EntryStateEnum[]>() {
    private static final long serialVersionUID = 1L;
    {
        put(WAITING, new EntryStateEnum[] { ALLOCATED, CANCELLED });
        put(ALLOCATED, new EntryStateEnum[] { RUNNING, CANCELLED });
        put(RUNNING, new EntryStateEnum[] { ENDED });
        put(CANCELLED, new EntryStateEnum[] {});
        put(ENDED, new EntryStateEnum[] {});
    }
};

保存可以Block的计划项名称:

/**
 * define which is able to be blocked
 */
public static final List<String> keyWords = new ArrayList<String>() {
    private static final long serialVersionUID = 1L;
    {
        add("Train");
    }
};

建立一个属于枚举的成员方法,返回“可以到达的新状态”。调用原状态的枚举对象查询该List(即this代表当前状态):

/**
 * get all states achievable
 * @param strPlanningEntryType
 * @return array of the states
 */
public EntryStateEnum[] newStateAchievable(String strPlanningEntryType) {
    for (String str : keyWords)
        if (strPlanningEntryType.contains(str))
            return EntryStateEnum.newStateAchievableBlockedAble.get(this);
    return EntryStateEnum.newStateAchievableBlockedDisable.get(this);
}

因此,在状态模式的设计种,一次设置新状态的操作,经过:

PlanningEntryCollection
-> PlanningEntry
-> EntryState.setNewState() {
EntryState.setAvailability() -> EntryStateEnum.newStateAchievable()
}

完成一次指定操作。其中,在PlanningEntryCollection和PlanningEntry中采用外观模式包装成5个方法,分别到达5种状态。

3.7 面向应用的设计:Board

Board是每个地方的信息板,以机场为例,每个机场有1小时内到达航班和起飞航班的显示。Board是一个抽象类,有3个不同的实现类,分别完成3个应用场景的Board。在初始化时,保存PlanningEntryCollection作为成员变量,以便遍历PlanningEntry。并构造一个JFrame用于可视化。

在这里插入图片描述

下面里Flight Board为例:
设定参数:

/**
 * choose flights within HOURS_RANGE before or later
 */
private static final int HOURS_RANGE = 1;
/**
 * visualization label of arrival
 */
public static final int ARRIVAL = 1;
/**
 * visualization label of leaving
 */
public static final int LEAVING = -1;

构造方法,继承父类Board的构造函数:

public FlightBoard(PlanningEntryCollection planningEntryCollection) {
    super(planningEntryCollection);
}

在可视化时,输入为当前时间(也可以即时获得)、位置字符串和类型(起飞/到达),若位置为空,则认为查询所有机场。

/**
 * visualize planning entries at current time in chosen location of the type
 * @param strCurrentTime
 * @param strLocation
 * @param intType
 */
public abstract void visualize(String strCurrentTime, String strLocation, int intType);
}

3.8 Board的可视化:外部API的复用

首先获得PlanningEntryCollection的PlanningEntry进行遍历,获得时间该航班的(到达/起飞)时间,与当前时间进行比对,若差距在预设的范围内(HOURS_RANGE=1)便将该PlanningEntry的信息记录到Vector上,再将该Vector加入二维Vector上,该二维Vector用于生成JTable。
Board.makeTable()中新建JTable,将信息输入表格,再将表格加入JFrame,委派JFrame进行可视化。

@Override
public void visualize(String strCurrentTime, String strLocation, int intType) {
    // iterator
    Iterator<PlanningEntry<Resource>> iterator = super.iterator();
    // new 2D-vector
    Vector<Vector<?>> vData = new Vector<>();
    // new titles
    Vector<String> vName = new Vector<>();
    String[] columnsNames = new String[] { "Time", "Entry Number", "Origin", "", "Terminal", "State" };
    for (String name : columnsNames)
        vName.add(name);
    while (iterator.hasNext()) {
        FlightSchedule<Resource> planningEntry = (FlightSchedule<Resource>) iterator.next();
        // if the location isn't chosen, then the board be as all airports'
        if (!strLocation.isEmpty()) {
            if (intType == FlightBoard.ARRIVAL) {
                if (!planningEntry.getLocationTerminal().toLowerCase().equals(strLocation.toLowerCase()))
                    continue;
            } else {
                if (!planningEntry.getLocationOrigin().toLowerCase().equals(strLocation.toLowerCase()))
                    continue;
            }
        }
        // time
        LocalDateTime currentTime = LocalDateTime.parse(strCurrentTime,
                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
        LocalDateTime scheduleTime = intType == FlightBoard.ARRIVAL ? planningEntry.getTimeArrival()
                : planningEntry.getTimeLeaving();
        // check time in range
        if (scheduleTime.isBefore(currentTime.plusHours(HOURS_RANGE))
                && scheduleTime.isAfter(currentTime.minusHours(HOURS_RANGE))) {
            // get information
            String strScheduleTime = scheduleTime.toString().substring(11);
            String planningEntryNumber = planningEntry.getPlanningEntryNumber();
            String locationOrigin = planningEntry.getLocationOrigin();
            String locationTerminal = planningEntry.getLocationTerminal();
            String state = planningEntry.getState().getStrState();
            // load in 1D vector
            Vector<String> vRow = new Vector<>();
            vRow.add(strScheduleTime);
            vRow.add(planningEntryNumber);
            vRow.add(locationOrigin);
            vRow.add("-->");
            vRow.add(locationTerminal);
            vRow.add(state);
            // add in 2D-vector
            vData.add((Vector<?>) vRow.clone());
        }
    }
    // visualization (extends from Board.maketable)
    makeTable(vData, vName, intType == ARRIVAL ? "Arrival" : "Leaving");
}

此外,我还添加了可视化所有entry的功能:

@Override
public void showEntries(Resource r) {
    Iterator<PlanningEntry<Resource>> iterator = super.iterator();
    Vector<Vector<?>> vData = new Vector<>();
    Vector<String> vName = new Vector<>();
    String[] columnsNames = new String[] { "Time", "Entry Number", "Origin", "", "Terminal", "State" };
    for (String name : columnsNames)
        vName.add(name);
    while (iterator.hasNext()) {
        FlightSchedule<Resource> planningEntry = (FlightSchedule<Resource>) iterator.next();
        if (planningEntry.getResource() != null && !planningEntry.getResource().equals(r))
            continue;
        String strScheduleTime = planningEntry.getTimeLeaving() + " - " + planningEntry.getTimeArrival();
        String planningEntryNumber = planningEntry.getPlanningEntryNumber();
        String locationOrigin = planningEntry.getLocationOrigin();
        String locationTerminal = planningEntry.getLocationTerminal();
        String state = planningEntry.getState().getStrState();
        Vector<String> vRow = new Vector<>();
        vRow.add(strScheduleTime);
        vRow.add(planningEntryNumber);
        vRow.add(locationOrigin);
        vRow.add("-->");
        vRow.add(locationTerminal);
        vRow.add(state);
        vData.add((Vector<?>) vRow.clone());
    }
    super.makeTable(vData, vName, "Entries");

可视化效果:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.9 PlanningEntryCollection的设计

该ADT是PlanningEntry的集合类。该集合类应该能够存储所有计划项、所有位置和可用资源,以及作为一个“管理者”的身份来操作这些成员变量。将PlanningEntryCollection作为抽象类,定义实现类的功能:

/**
 * planning entry collection is used to:
 * manage resource, locations;
 * generate / cancel / allocate / start / block / finish a planning entry;
 * ask the current state
 * search the conflict in the set of planning entry ( location / resource )
 * present all the plan that one chosen resource has been used (Waiting, Running, Ended)
 * show the board
 */

主要有6个功能:

  1. 管理资源和位置;
  2. 操作一个计划项:新建、取消、分配资源、开启、暂停、结束;
  3. 查询计划项当前状态;
  4. 查找冲突
  5. 获得所有计划项
  6. 打印信息板

在这里插入图片描述

在这里插入图片描述

在PlanningEntryCollection中实现了一些共性方法,并定义了一些抽象方法。不同子类实现有差异的方法有:添加计划项、分配资源、排序计划项;其余为相同的实现。
开始、取消、暂停、结束一个计划项这4种操作利用外观模式,将单个计划项中的成员方法进行封装。首先在所有计划项中找到该计划项,若找到则再进行对应的操作。以cancelPlanningEntry()为例:

/**
 * cancel a plan
 * @param planningEntryNumber
 * @return true if cancelled successfully
 */
public Boolean cancelPlanningEntry(String planningEntryNumber) {
    PlanningEntry<Resource> planningEntry = this.getPlanningEntryByStrNumber(planningEntryNumber);
    return planningEntry == null ? false : planningEntry.cancel();
}

此外,共性方法还有计划项、资源、位置的Getter,以及删除单个资源和位置的方法。
接下来是有差异的方法。首先是新增一个计划项,以Flight Schedule为例。首先addPlanningEntry()有一个重载方法,可以将已经提取好的参数直接输入并新建:

/**
 * generate a planning entry by given params
 * @param planningEntryNumber
 * @param departureAirport
 * @param arrivalAirport
 * @param departureTime
 * @param arrivalTime
 * @return the flight schedule
 */
public FlightSchedule<Resource> addPlanningEntry(String planningEntryNumber, String departureAirport,
        String arrivalAirport, String departureTime, String arrivalTime) {
    Location location = new Location(departureAirport, arrivalAirport);
    TimeSlot timeSlot = new TimeSlot(Arrays.asList(departureTime, arrivalTime),
            Arrays.asList(departureTime, arrivalTime));
    this.collectionLocation.addAll(location.getLocations());
    PlanningEntry<Resource> flightSchedule = PlanningEntry.newPlanningEntryOfFlightSchedule(location, timeSlot,
            planningEntryNumber);
    this.planningEntries.add(flightSchedule);
    return (FlightSchedule<Resource>) flightSchedule;
}

通过重载的方法,提供两种不同的新建方式,方便在之后的GUI客户端新建计划项的操作。接下来实现在抽象类中定义的方法,主要是要通过正则表达式提取所需的要素,并调用上述方法。用Pattern对象定义模式,用Matcher对象进行匹配;若匹配成功,则用group方法提取参数输入上述方法。

@Override
public FlightSchedule<Resource> addPlanningEntry(String stringInfo) {
    Pattern pattern = Pattern.compile(
            "Flight:(.*?),(.*?)\n\\{\nDepartureAirport:(.*?)\nArrivalAirport:(.*?)\nDepatureTime:(.*?)\nArrivalTime:(.*?)\nPlane:(.*?)\n\\{\nType:(.*?)\nSeats:(.*?)\nAge:(.*?)\n\\}\n\\}\n");
    Matcher matcher = pattern.matcher(stringInfo);
    if (!matcher.find())
        return null;
    String planningEntryNumber = matcher.group(2);
    String departureAirport = matcher.group(3);
    String arrivalAirport = matcher.group(4);
    String departureTime = matcher.group(5);
    String arrivalTime = matcher.group(6);
    return this.addPlanningEntry(planningEntryNumber, departureAirport, arrivalAirport, departureTime, arrivalTime);
}

分配资源也是有差异的方法,与新建计划项的方法类似。我也新增了几个重载方法,模块化、也方便调用。有两种Pattern,对应两种输入模式。

@Override
public Resource allocatePlanningEntry(String planningEntryNumber, String stringInfo) {
    if (this.getPlanningEntryByStrNumber(planningEntryNumber) == null)
        return null;
    Pattern pattern1 = Pattern.compile(
            "Flight:(.*?),(.*?)\n\\{\nDepartureAirport:(.*?)\nArrivalAirport:(.*?)\nDepatureTime:(.*?)\nArrivalTime:(.*?)\nPlane:(.*?)\n\\{\nType:(.*?)\nSeats:(.*?)\nAge:(.*?)\n\\}\n\\}\n");
    Pattern pattern2 = Pattern.compile("Plane:(.*?)\n\\{\nType:(.*?)\nSeats:(.*?)\nAge:(.*?)\n\\}\n");
    Matcher matcher = pattern1.matcher(stringInfo);
    if (!matcher.find()) {
        matcher = pattern2.matcher(stringInfo);
        if (!matcher.find())
            return null;
    }
    String number = matcher.group(7);
    String strType = matcher.group(8);
    int intSeats = Integer.valueOf(matcher.group(9));
    double age = Double.valueOf(matcher.group(10));
    return this.allocateResource(planningEntryNumber, number, strType, intSeats, age);
}

最后是按时间顺序排序计划项sortPlanningEntries()。首先定义一个Comparator,并重写其compare方法,然后调用Collections.sort()方法进行排序。在Flight Schedule中的Comparator对象,compare方法通过获取时间进行比较,具体如下:

	Comparator<PlanningEntry<Resource>> comparator = new Comparator<PlanningEntry<Resource>>() {
            @Override
            public int compare(PlanningEntry<Resource> o1, PlanningEntry<Resource> o2) {
                return ((FlightSchedule<Resource>) o1).getTimeLeaving()
                        .isBefore(((FlightSchedule<Resource>) o2).getTimeArrival()) ? -1 : 1;
            }
        };

在各个子类型中,还有依据计划项编号获取计划项等封装方法,减轻客户端操作集合类的难度。

3.10 可复用API设计及Façade设计模式

Junit测试
在这里插入图片描述

3.10.1 检测一组计划项之间是否存在位置独占冲突

要检测一组计划项之间是否存在位置冲突,主要应该检测每一个位置的若干计划项是否有时间冲突。首先要保存下每个位置的所有计划项,我使用的是一个Map、键为位置String、值为使用该位置的所有计划项的List。

Map<String, List<ActivityCalendar<Resource>>> locationMap = new HashMap<>();

接下来,遍历所有计划项。对于每个计划项:若该计划项的位置未被加入Map的键集合,则加入并将值赋值为仅有该计划项的List;否则,将该计划项加入原有的值的List中,并考察List中是否有冲突,最后更新该值。

			if (locationMap.keySet().contains(strLocation)) {
                List<ActivityCalendar<Resource>> calendars = new ArrayList<>();
                calendars.addAll(locationMap.get(strLocation));
                calendars.add(activityCalendar);
                ……
                locationMap.remove(strLocation);
                locationMap.put(strLocation, calendars);
            } else {
                locationMap.put(strLocation, new ArrayList<ActivityCalendar<Resource>>() {
                    private static final long serialVersionUID = 1L;
                    {
                        add(activityCalendar);
                    }
                });
            }

考察List是否有冲突,要遍历任意两个计划项,是否存在这时间重叠。对于任意两个不同计划项c1、c2,分别获取它们的起始时间和结束时间,进行比较:若一方的起始时间早于另一方的结束时间且结束时间晚于起始时间,则认为冲突。

LocalDateTime t1b = c1.getBeginningTime(), t1e = c1.getEndingTime();
LocalDateTime t2b = c2.getBeginningTime(), t2e = c2.getEndingTime();
if ((t1b.isBefore(t2e) || t1b.isEqual(t2e)) && (t1e.isAfter(t2b) || t2e.isEqual(t2b)))
	return true;

返回true表示冲突,返回false表示无冲突。
流程图:

3.10.2 检测一组计划项之间是否存在资源独占冲突

该操作与上一方法类似,代码级别复用即可。
要检测一组计划项之间是否存在资源冲突,主要应该检测使用每一个资源的若干计划项是否有时间冲突。首先要保存下每个位置的所有计划项,我使用的是一个Map、键为资源、值为使用该位置的所有计划项的List。
接下来,遍历所有计划项。对于每个计划项:若该计划项的资源未被加入Map的键集合,则加入并将值赋值为仅有该计划项的List;否则,将该计划项加入原有的值的List中,并考察List中是否有冲突,最后更新该值。
考察List是否有冲突,要遍历任意两个计划项,是否存在这时间重叠。对于任意两个不同计划项p1、p2,分别获取它们的起始时间和结束时间,进行比较:若一方的起始时间早于另一方的结束时间且结束时间晚于起始时间,则认为冲突。
返回true表示冲突,返回false表示无冲突。
流程图:

在这里插入图片描述

3.10.3 提取面向特定资源的前序计划项

提取特定资源的前序计划项,是要搜索使用同一资源、计划时间在选定计划项之前且最晚(与选定计划项时间最近)的计划项。该方法类似与在一组数据中选取符合条件的最大值。整体思路就是遍历、筛选、比较
首先,初始化“最晚时间”和“前序计划项”;

LocalDateTime latestDateTime = LocalDateTime.MIN;
PlanningEntry<Resource> prePlanningEntry = null;

然后,遍历所有计划项,选出使用相同资源的计划项(在迭代时比较资源是否相同来进行筛选):

for (int i = 0; i < entries.size(); i++) {
    if (entries.get(i).getResource().equals(e.getResource())) {
        ……
}

在迭代中比较,若符合筛选条件,且比原最晚时间更晚,则更新:

LocalDateTime endingTime = planningEntry.getTimeArrival();
if (endingTime.isAfter(latestDateTime) 
&& endingTime.isBefore(e.getTimeLeaving())) {
     latestDateTime = endingTime;
     prePlanningEntry = planningEntry;
}

最后返回prePlanningEntry即可。
流程图:

在这里插入图片描述

3.11 设计模式应用

请分小节介绍每种设计模式在你的ADT和应用设计中的具体应用。

3.11.1 Factory Method

设置3个PlanningEntry接口的工厂方法,分别新建1种计划项子类型。以Flight Schedule为例,需要输入Location、TimeSlot和计划项编号3个参数,返回一个FlightSchedule计划项类型:

/**
 * a factory method for generating an instance of Flight Schedule
 * @param <R>
 * @param location
 * @param timeSlot
 * @param planningEntryNumber
 * @return a empty instance of planning entry of  flight schedule
 */
public static <R> FlightSchedule<R> newPlanningEntryOfFlightSchedule(Location location, TimeSlot timeSlot, String planningEntryNumber) {
    return new FlightSchedule<R>(location, timeSlot, planningEntryNumber);
}

3.11.2 Iterator

在Collection中,用一个List存储所有的计划项;因此在Board中,迭代器的方法该存储计划项的list.iterator()

public Iterator<PlanningEntry<Resource>> iterator() {
    return planningEntryCollection.getAllPlanningEntries().iterator();
}
	此外,在需要比较PlanningEntry时,新建comparator对象,重写compare方法。
Comparator<PlanningEntry<Resource>> comparator = new Comparator<PlanningEntry<Resource>>() {
      @Override
      public int compare(PlanningEntry<Resource> o1, PlanningEntry<Resource> o2) {
           return ((FlightSchedule<Resource>) o1).getTimeLeaving()
                  .isBefore(((FlightSchedule<Resource>) o2).getTimeArrival()) ? -1 : 1;
      }
};

FlightBoard.visualize()方法中,使用该迭代器生成方法:

Iterator<PlanningEntry<Resource>> iterator = super.iterator();

3.11.3 Strategy

在抽象类PlanningEntryAPIs中设置抽象方法:

/**
 * For Activity Calendar
 * check locations of planning entry in entries if they are conflicted
 * @param entries
 * @return true if there are locations conflict
 */
public abstract boolean checkLocationConflict(List<PlanningEntry<Resource>> entries);

分别用若干子类来实现该方法。我使用了PlanningEntryAPIsFirstPlanningEntryAPIsSecond两个类分别实现了检查位置冲突的方法。

public class PlanningEntryAPIsFirst extends PlanningEntryAPIs {
    @Override
    public boolean checkLocationConflict(List<PlanningEntry<Resource>> entries) {
        ……
}
public class PlanningEntryAPIsSecond extends PlanningEntryAPIs {
    @Override
    public boolean checkLocationConflict(List<PlanningEntry<Resource>> entries) {
        ……
}

在调用该方法时,首先新建两种对象之一,再调用其方法。注意:静态方法不能被重写。

boolean flag = (new PlanningEntryAPIsFirst()).checkLocationConflict(flightScheduleCollection.getAllPlanningEntries());

3.12 应用设计与开发

3.12.1 航班应用

3.12.1.1 初始化数据

在界面显示之前,App会先将指定文件的数据读入,并且存储到App静态变量flightScheduleCollection中,以便之后功能的使用。
根据语法读入的既定规则,将每13行(航班数据是13行为单位,用静态常量存储)作为一个数据单元,每读入13行字符串则新建一个计划项。

/**
 * read file and add planning entries in txt
 * @param strFile
 * @throws Exception
 */
public static void readFile(String strFile) throws Exception {
    BufferedReader bReader = new BufferedReader(new FileReader(new File(strFile)));
    String line = "";
    int cntLine = 0;
    StringBuilder stringInfo = new StringBuilder("");
    while ((line = bReader.readLine()) != null) {
        if (line.equals(""))
            continue;
        stringInfo.append(line + "\n");
        cntLine++; 
        if (cntLine % INPUT_ROWS_PER_UNIT == 0) {
            FlightSchedule<Resource> flightSchedule = flightScheduleCollection
                    .addPlanningEntry(stringInfo.toString());
            if (flightSchedule != null)
                flightScheduleCollection.allocatePlanningEntry(flightSchedule.getPlanningEntryNumber(),
                        stringInfo.toString());
            stringInfo = new StringBuilder("");
        }
    }
    bReader.close();
    // flightScheduleCollection.sortPlanningEntries();
}

3.12.1.2 起始界面

该界面的框架基于用网格布局,包含了9类要求实现功能,每类功能在点击之后会新建窗口,进行交互;若有多项功能合并为一个按钮,则还会在点开后进行选择。

在这里插入图片描述

此后的JFrame也大多于此设计类似。

JFrame mainFrame = new JFrame("Flight Schedule");
mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
mainFrame.setLayout(new GridLayout(3, 3, 10, 5));
mainFrame.setVisible(true);
mainFrame.setSize(800, 300);

3.12.1.3 可视化
首先在主框架中新建一个按钮并命名,再添加动作Listener,若按下该按键,则启动专属于可视化的窗口。

JButton visualizeButton = new JButton("Visualize");
mainFrame.add(visualizeButton);
visualizeButton.addActionListener((e) -> visualization());

在可视化的窗口中,添加输入时间的流面板,以及输入到达/起飞机场的文本框。

在这里插入图片描述

该窗口的两个按键会调用Board中的功能,具体在3.8节中阐述。

3.12.1.4 新建计划项

新建该面板,提供输入各项参数的文本框,并在按下按键后,将信息整理成与文本数据相同格式的数据字符串,添加计划项。
航班有5个信息,则需要5个输入信息的流面板。批量化处理

String[] panelsName = new String[] { "Planning Entry Number:", "Departure Airport:", "Arrival Airport:", "Departure Time (yyyy-MM-dd HH:mm):", "Arrival Time (yyyy-MM-dd HH:mm):" };
        List<JPanel> panelsList = new ArrayList<>();
        List<JTextField> textList = new ArrayList<>();
        for (int i = 0; i < panelsName.length; i++) {
            JPanel newPanel = new JPanel();
            panelsList.add(newPanel);
            newPanel.setLayout(new FlowLayout());
            newPanel.add(new JLabel(panelsName[i]));
            JTextField newText = new JTextField(LINE_WIDTH);
            textList.add(newText);
            newPanel.add(newText);
            addPlanningEntryFrame.add(newPanel);
        }

Action Listener中,若按钮被按下,则读入字符串,返回新建的结果,弹出提示信息(成功/失败)。

enterButton.addActionListener((e) -> {
    List<String> gotString = new ArrayList<>();
    for (int i = 0; i < panelsName.length; i++) {
        gotString.add(textList.get(i).getText());
    }
    flightScheduleCollection.addPlanningEntry(gotString.get(0), gotString.get(1), gotString.get(2),
            gotString.get(3), gotString.get(4));
    addPlanningEntryFrame.dispose();
    JOptionPane.showMessageDialog(addPlanningEntryFrame, "Successfully", "Add Planning Entry", JOptionPane.PLAIN_MESSAGE);
    addPlanningEntryFrame.dispose();
});

在这里插入图片描述

3.12.1.5 分配资源

读入方法与上述类似:读入资源信息,通过按钮的动作进行操作的启动。

在这里插入图片描述

3.12.1.6 询问状态

读入方法与上述类似:读入计划项编号信息,通过按钮启动,再通过提示框来显示状态。

在这里插入图片描述

在这里插入图片描述

3.12.1.7 计划项操作(启动、取消、暂停、结束)

通过一个CombBox给出操作的选项,对指定计划项进行操作。

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

3.12.1.8 API:检查冲突、查找前置计划项

给出3个流面板,前两个是检查位置/资源按键,后一个是输入计划项编号查找前置项。

在这里插入图片描述

3.12.1.9 管理(增加/删除)资源

在读入用户输入的资源,并进行增加/删除前,要给出所有资源的信息,再通过必要信息进行操作。

String resourcesStrings = "";
Set<Resource> allResource = flightScheduleCollection.getAllResource();
List<Resource> allResourceList = new ArrayList<>();
int i = 0;
for (Resource plane : allResource) {
    i++;
    resourcesStrings += String.valueOf(i) + ": " + ((Plane) plane).toString() + "\n";
    allResourceList.add(plane);
}

在这里插入图片描述

3.12.1.10 管理(增加/删除)位置

与上述类似。

在这里插入图片描述

3.12.1.11 同一资源的计划项

搜索同一编号的资源的计划项汇集成表格,委派Board进行显示。

在这里插入图片描述

3.12.2 高铁应用

与上述类似。差别主要在于表格的显示需要多个站点。

在这里插入图片描述

3.12.3 学习活动应用

与上述类似。

在这里插入图片描述

新增修改地点的操作。

在这里插入图片描述

3.13 基于语法的数据读入

3.13.1 航班

依据给定数据的格式,可以设计正则表达式,并保存在Pattern对象中。

Pattern pattern = Pattern.compile("Flight:(.*?),(.*?)\n\\{\nDepartureAirport:(.*?)\nArrivalAirport:(.*?)\nDepatureTime:(.*?)\nArrivalTime:(.*?)\nPlane:(.*?)\n\\{\nType:(.*?)\nSeats:(.*?)\nAge:(.*?)\n\\}\n\\}\n");

每读入一个单位的数据就进行匹配,若Matcher对象有找到,则匹配成功。

Matcher matcher = pattern.matcher(stringInfo);
if (!matcher.find()) return null;

根据数据格式,进行提取。

​    String planningEntryNumber = matcher.group(2);
​    String departureAirport = matcher.group(3);
​    String arrivalAirport = matcher.group(4);
​    String departureTime = matcher.group(5);
​    String arrivalTime = matcher.group(6);

最后基于这些参数新建计划项。

3.13.2 高铁

高铁的数据格式和样例均没有给定,于是我需要自行建立。该设计参考航班数据的格式。

首先预设一些高铁的站点。

String[] cities = new String[] { "Harbin", "Beijing", "Shanghai", "Shenzhen", "Guangzhou" };

对于每一个新建的计划项,随机站点数,和每个站点的信息;还有随机高铁信息(或固定信息选调)。例如,一趟火车的整体信息:

String planningDate = "2020-01-01";
String planningNumber = String.valueOf(i);
String trainNumber = String.valueOf(Math.random());
String trainType = "Business";
String trainCapacity = String.valueOf(100);

站点的信息可以随机为:

String stations = "";
for (int j = 1; j <= M; j++) {
stations += cities[j - 1] + " " + String.format("2020-01-01 10:%d", j * 10) + " " + String.format("2020-01-01 10:%d", j * 10) + "\n";
}

最后,参考给定数据格式,合并这些信息,保存到文件中:

content += String.format("Train:%s,%s\n{\n%sTrain:%s\n{\nTrainType:%s\nTrainCapacity:%s\n}\n}\n", planningDate, planningNumber, stations, trainNumber, trainType, trainCapacity);

打开文件保存:

try {
            File file = new File("data/TrainSchedule/TrainSchedule_1.txt");
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter fileWriter = new FileWriter(file.getName(), true);
            fileWriter.write(content);
            fileWriter.flush();
            fileWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

数据样例如下:

Train:2020-01-01,1
{
Harbin 2020-01-01 10:10 2020-01-01 10:10
Beijing 2020-01-01 10:20 2020-01-01 10:20
Shanghai 2020-01-01 10:30 2020-01-01 10:30
Shenzhen 2020-01-01 10:40 2020-01-01 10:40
Guangzhou 2020-01-01 10:50 2020-01-01 10:50
Train:A0
{
TrainType:Business
TrainCapacity:100
}
}

3.13.3 学习活动

与高铁的数据设计类似,主要差别在单个地点。

在设计计划项和资源信息时(以4月课程为例):

String planningDate = String.format("2020-04-0%d", i % 9 + 1);
String activityNumber = String.valueOf(i);
String room = "Zhengxin44";
String beginningTime = String.format("2020-04-0%d 10:00", i % 9 + 1);
String endingTime = String.format("2020-04-0%d 12:00", i % 9 + 1);
String docName = "Software Construction";
String publishDepartment = "HIT";
String publishDate = "2000-01-01";

按照指定格式加入数据:

content += String.format("Activity:%s,%s\n{\nRoom:%s\nBeginningTime:%s\nEndingTime:%s\nDocument:A4\n{\nDocName:%s\nPublishDepartment:%s\nPublishDate:%s\n}\n}\n",planningDate, activityNumber, room, beginningTime, endingTime, docName, publishDepartment, publishDate);

数据样例:

Activity:2020-04-02,1
{
Room:Zhengxin44
BeginningTime:2020-04-02 10:00
EndingTime:2020-04-02 12:00
Document:A4
{
DocName:Software Construction
PublishDepartment:HIT
PublishDate:2000-01-01
}
}

3.14 应对面临的新变化

评估之前的设计是否可应对变化、代价如何
如何修改设计以应对变化

3.14.1 变化1:航班

航班管理中变化在于从2个站变成3个站,根据之前的思路,在取3个站位置/资源时用不同的静态常量作为下标即可。

public static final int ORIGIN = 0, MID = 1, TERMINAL = 2;

因此,获取位置的方法可以更改为(type为上述常量之一):

public String getLocation(int type) {
    return super.getLocation().getLocations().get(type);
}

获取资源同理。
所以,该变化可以通过继承父类,进行方法的增加/重载以达到变化的目的。总体代价不大,仅有3个Getter

3.14.2 变化2:高铁

高铁的变化在于,若被分配资源后则不能被取消。这个变化可以通过重写cancel()方法,增加判断语句限制前置状态即可。

@Override
public Boolean cancel() {
    if (this.state.getState().equals(EntryStateEnum.ALLOCATED))
        return false;
    return this.state.setNewState(strPlanningEntryType, "Cancelled");
}

因此,其代价为重写一个方法,代价很小。

3.14.3 变化3:学习活动

学习活动的变化在于,学习活动也可以被临时暂停。这个变化可以通过增加可阻塞的活动名单。

EntryStateEnum.BlockAbleKeyWords.add("Activity");

该操作在构造方法中进行,代价极小。

3.15 Git仓库结构

请在完成全部实验要求之后,利用Git log指令或Git图形化客户端或GitHub上项目仓库的Insight页面,给出你的仓库到目前为止的Object Graph,尤其是区分清楚314change分支和master分支所指向的位置。

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

哈工大2020软件构造Lab3实验报告 的相关文章

  • Android NumberPicker 带字符串

    I have customised the NumberPicker to show text The output is this 当我按 确定 时 我想将 e x 鼠标添加到我的列表 文章 中 我得到的是索引值 int 它由 array
  • JavaEE 8 教程,在 hello1 项目上部署失败

    我正在尝试学习 Java EE 8 我遵循了官方指南https javaee github io tutorial https javaee github io tutorial 但我有这个问题 cargo maven2 plugin 1
  • JPanel透明背景和显示元素[重复]

    这个问题在这里已经有答案了 我插入一个背景图e 变成 aJPanel但一些界面元素消失了 以下 Java Swing 元素不会出现 标签标题 标签 usuario 标签 密码 按钮加速器 你能否使图像透明或元素不透明 setOpaque f
  • Spring webflow 应用程序:HTTP 302 暂时移动

    我的 java 应用程序中的每个请求都会生成另外 2 个带有 HTTP 302 错误的请求 例如 如果请求查看名为板 html 这个请求是从首页 html 我收到按以下顺序生成的 3 个请求 POST home html 302 Moved
  • 有效地查找正则表达式的所有重叠匹配项

    这是后续与 java 正则表达式匹配的所有重叠子字符串 https stackoverflow com q 11303309 244526 有没有办法让这段代码更快 public static void allMatches String
  • Maven + Cobertura:无法找到[您的班级]。你指定了源目录吗?

    我有 MyMath 类 有两个简单的方法 multi 和 add 和测试类只会测试多种方法 public class MainTest Test public void testMultiply MyMath tester new MyMa
  • 如何解决错误:java.lang.ClassNotFoundException:io.netty.util.concurrent.GenericFutureListener?

    昨天我第一次尝试用 Java 制作 Prometheus 客户端 从 Python 开始 最后是 GoLang 是否找到示例 import io prometheus client Counter import io prometheus
  • 如何正确配置Tomcat SSLHostConfig?

    我正在按照本教程在 tomcat 中启用 ssl https medium com raupach how to install lets encrypt with tomcat 3db8a469e3d2 https medium com
  • LibGdx 如何使用 OrthographicCamera 滚动?

    我已经找了 10 个小时 字面意思 我已经完成了 我需要问一下 事情是我正在学习如何使用 LibGdx 来编写 Java 游戏 我正在做一个水平太空飞船游戏 所以 我最糟糕的问题是我不知道如何滚动 我认为绘制会更好地解释 我想绘制一个巨大的
  • grails 上的同步块在 Windows 上有效,但在 Linux 上无效

    我有一个 grails 应用程序 它依赖于服务中的同步块 当我在 Windows 上运行它时 同步按预期工作 但当我在 ams linux 上运行时 会出现 StaleObjectStateException 该问题在以下示例中重现 cla
  • Java中的DRY原则[关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我一直在读关于DRY https en wikipedia org wiki Don 27t repeat yourself原则 虽然看起来
  • 通用 JSF 实体转换器[重复]

    这个问题在这里已经有答案了 我正在编写我的第一个 Java EE 6 Web 应用程序作为学习练习 我没有使用框架 只是使用 JPA 2 0 EJB 3 1 和 JSF 2 0 我有一个自定义转换器 用于将存储在 SelectOne 组件中
  • 将字符串中的字符向左移动

    我是 Stack Overflow 的新手 有一道编程课的实验室问题一直困扰着我 该问题要求我们将字符串 s 的元素向左移动 k 次 例如 如果输入是 Hello World 和3 它将输出 lo WorldHel 对于非常大的 k 值 它
  • 防止 Firebase 中的待处理写入事务不起作用

    我的目标是在单击按钮时将名称插入 Cloud Firestore 中 但如果用户未连接到互联网 我不希望保存处于挂起状态 我不喜欢 Firebase 保存待处理写入的行为 即使互联网连接已恢复 我研究发现Firebase 开发人员建议使用事
  • 监控 Java 应用程序上的锁争用

    我正在尝试创建一个小基准 在 Groovy 中 以显示几个同步方法上的高线程争用 当监控自愿上下文切换时 应该会出现高争用 在 Linux 中 这可以通过 pidstat 来实现 程序如下 class Res private int n s
  • 使用 Android 的 Mobile Vision API 扫描二维码

    我跟着这个tutorial http code tutsplus com tutorials reading qr codes using the mobile vision api cms 24680关于如何构建可以扫描二维码的 Andr
  • Java 9 中紧凑字符串和压缩字符串的区别

    有什么优点紧凑的字符串 http openjdk java net jeps 254JDK9 中的压缩字符串 压缩字符串 Java 6 和紧凑字符串 Java 9 都有相同的动机 字符串通常实际上是 Latin 1 因此浪费了一半的空间 和
  • 如何在Java中跨类共享变量,我尝试了静态不起作用

    类 Testclass1 有一个变量 有一些执行会改变变量的值 现在在同一个包中有类 Testclass2 我将如何访问 Testclass2 中变量的更新值 由 Testclass1 更新 试过这个没用 注意 Testclass1和Tes
  • 将隐藏(生物识别)数据附加到 pdf 上的数字签名

    我想知道是否可以使用 iText 我用于签名 或 Java 中的其他工具在 pdf 上添加生物识别数据 我会更好地解释一下 在手写板上签名时 我会收集签名信息 例如笔压 签名速度等 我想将这些信息 java中的变量 与pdf上的签名一起存储
  • 如何使用 Spring AOP 建议静态方法?

    在执行类的静态方法之前和之后需要完成一些日志记录 我尝试使用 Spring AOP 来实现这一点 但它不起作用 而对于正常方法来说它起作用 请帮助我理解如何实现这一点 如果可以使用注释来完成 那就太好了 也许您应该在使用 Spring AO

随机推荐

  • 重启Vcenter命令

    重启Vcenter命令 通过ssh登录Vcenter 输入root 输入shell 输入service control stop all 输入service control start all 停止 启动或重新启动 VMware vCent
  • linux xenserver教程,XenServer常用命令

    监控检查类 xentop 查看XenServer与VM的资源使用情况 xsconsole 进入XenServer管理面板 查看网卡 IP 系统版本 系统时间 硬件信息等 xe task list 查看XenServer临时任务进程 serv
  • 【C语言】验证哥德巴赫猜想

    文章目录 问题来源 题目要求 如何判断素数 主函数 完整代码 效果演示 写代码中的误解 总结 问题来源 这是学校的一个作业 原题如下 题目先给出了哥德巴赫猜想的背景知识 我还真不知道 2000以内的正偶数都能分解成两个质数 素数 之和 题目
  • 毕业设计-基于 PID 控制算法仿真算法研究- Matlab

    目录 前言 课题背景和意义 实现技术思路 一 基本原理 二 无超调 PID 控制器的设计 三 无超调 PID 设计的验证 代码 实现效果图样例 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着备考或实习为毕业后面临的就业升学做准备 一
  • 安装anaconda及修改conda config 的channels/default_channels

    先说一下安装anaconda的方法 很简单 就是去官网下载然后在本地安装 bash Anaconda3 4 4 0 Linux x86 64 sh 这个过程中要耐心 会有提问 需要输入yes来回应 并且需要按很多的回车 总之 看见让输入ye
  • 转:机器学习的理解

    转李航博士的一篇关于机器学习理解的文章 算算时间 从开始到现在 做机器学习算法也将近八个月了 虽然还没有达到融会贯通的地步 但至少在熟悉了算法的流程后 我在算法的选择和创造能力上有了不小的提升 实话说 机器学习很难 非常难 要做到完全了解算
  • Ridis持久化

    Redis持久化 RDB Redis DataBase Redis会单独创建 fork 一个子进程来进行持久化 会先将数据写入到一个临时文件中 待持久化都结束了 再用这个临时文件替换上次持久化好的文件 整个过程中 主进程是不进行io操作的
  • 8--UI 初步认识 简易计算器

    UI是App的根基 一个App应该是先有UI界面 然后在UI的基础上增加实用功能 2 UI相对简单易学 UI普遍是学习过程中最简单的一块 能快速拥有成就感和学习兴趣 3 UI至关重要 开发中的绝大部分时间都在处理UI 谨记一条IOS软件开发
  • MySQL根据某一个或者多个字段查找重复数据

    sql 查出一张表中重复的所有记录数据 1 表中有id和name 两个字段 查询出name重复的所有数据 select from xi a where a username in select username from xi group
  • 系列教程

    PDF Search 系列教程来咯 在 Part 1 中 我们将演示如何从 PDF 中提取 处理并存储图像及文本 随着神经搜索 Neural Search 技术的普及 越来越多开发者 开始尝试用 Jina 解决非结构化数据的索引和搜索问题
  • MySQL必知必会 学习笔记 第二十五章 使用触发器

    触发器在MySQL 5中增加 触发器可以在MySQL响应DELETE INSERT UPDATE语句时自动执行一条SQL语句 MySQL 5中触发器名在每个表中唯一而不是在一个数据库中唯一 其他DBMS有的重名限制是数据库范围 以后MySQ
  • lua和测试(一)

    lua做为一门高级语言 在游戏产业运用到机会越来越多了 测试掌握几门脚本语言也有一定的重要性 以下对于lua组合输入做出一些引导 测试需要掌握的关于返回数值 主要用到布尔类 前言的指引 lua的语法比较简单和清晰 学过c语言的可以很好的掌握
  • 并发编程系列之自定义线程池

    前言 前面我们在讲并发工具类的时候 多次提到线程池 今天我们就来走进线程池的旅地 首先我们先不讲线程池框架Executors 我们今天先来介绍如何自己定义一个线程池 是不是已经迫不及待了 那么就让我们开启今天的旅途吧 什么是线程池 线程池可
  • selenium+python 对输入框的输入处理

    最近自己在做项目的自动化测试 公司无此要求 在用户管理模块做修改用户信息时 脚本已经跑成功 并且的确做了update操作 但是自己登陆页面检查 信息却没有被修改 再次确定系统该模块的编辑功能可用 脚本如下 if result num gt
  • 近千万EOS被盗事件回顾,大家请保护好自己的EOS私钥

    最近有伙伴被盗了价值近千万的EOS 于是查看了这次被盗活动账号记录 这次分享出来 一是有可能大家有线索 二是也让大家意识到数字货币私钥安全的重要性 事件回顾 受害人在7 9号被偷盗人通过update auth更换了账号授权公私钥 紧接着被转
  • 零基础到GPT高手:快速学习与利用ChatGPT的完全指南

    进入人工智能时代 令人惊叹的ChatGPT技术正在引爆全球 您是否想象过能够与智能语言模型对话 提升工作效率 解锁创意 甚至实现商业化变现 在本篇文章中 我将向你揭示ChatGPT的原理 学习技巧 并展示如何利用ChatGPT提升工作效率和
  • Windows11:QT5.14.2+PCL1.12.0+VS2019环境配置

    之前在win10系统下配置了PCL1 8 1 QT5 9 1 VS2015的开发环境 由于PCL库已经更新到了1 12 1而且1 8 1一直有bug 为了使用下新的算法库 今天配置一下新的开发环境 1 安装Qt5 14 2 Qt5 14 2
  • 【b站雅思笔记】Simon‘s IELTS Course - 听力部分

    前情提要 b站up主贼开心的小林上传的Simon的听力课 资料均来源于她 参考 雅思阅读 最好的雅思课程 阅读部分全集 https www bilibili com video BV1ea4y1x7qR spm id from 333 78
  • Spring为什么要用的三级缓存解决循环依赖

    一 代码准备 Component aService public class AService Autowired private BService bService public void test System out println
  • 哈工大2020软件构造Lab3实验报告

    本项目于4 21日实验课验收 更新完成 如果有所参考 请点点关注 点点赞GitHub Follow一下谢谢 2020春计算机学院 软件构造 课程Lab3实验报告 Software Construction 2020 Spring Lab 3