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

本项目于4.21日实验课验收

更新完成

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

HIT

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

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

文章目录

  • 本项目于4.21日实验课验收
  • `更新完成`
  • 如果有所参考 请点点关注 点点赞[GitHub Follow一下谢谢](https://github.com/gzn00417)
  • 2020春计算机学院《软件构造》课程Lab3实验报告
  • 1 实验目标概述
  • 2 实验环境配置
  • 3 实验过程
    • 3.1 待开发的三个应用场景
    • 3.2 面向可复用性和可维护性的设计:PlanningEntry < R >
      • 3.2.1 PlanningEntry的共性操作
      • 3.2.2 局部共性特征的设计方案
      • 3.2.3 面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)
    • 3.3 面向复用的设计:R
    • 3.4 面向复用的设计:Location
    • 3.5 面向复用的设计:Timeslot
    • 3.6 面向复用的设计:EntryState及State设计模式
    • 3.7 面向应用的设计:Board
    • 3.8 Board的可视化:外部API的复用
    • 3.9 PlanningEntryCollection的设计
    • 3.10 可复用API设计及Façade设计模式
      • 3.10.1 检测一组计划项之间是否存在位置独占冲突
      • 3.10.2 检测一组计划项之间是否存在资源独占冲突
      • 3.10.3 提取面向特定资源的前序计划项
    • 3.11 设计模式应用
      • 3.11.1 Factory Method
      • 3.11.2 Iterator
      • 3.11.3 Strategy
  • 3.12 应用设计与开发
    • 3.12.1 航班应用
        • 3.12.1.1 初始化数据
        • 3.12.1.2 起始界面
        • 3.12.1.4 新建计划项
        • 3.12.1.5 分配资源
        • 3.12.1.6 询问状态
        • 3.12.1.7 计划项操作(启动、取消、暂停、结束)
        • 3.12.1.8 API:检查冲突、查找前置计划项
        • 3.12.1.9 管理(增加/删除)资源
        • 3.12.1.10 管理(增加/删除)位置
        • 3.12.1.11 同一资源的计划项
      • 3.12.2 高铁应用
      • 3.12.3 学习活动应用
    • 3.13 基于语法的数据读入
      • 3.13.1 航班
      • 3.13.2 高铁
      • 3.13.3 学习活动
    • 3.14 应对面临的新变化
      • 3.14.1 变化1:航班
      • 3.14.2 变化2:高铁
      • 3.14.3 变化3:学习活动
    • 3.15 Git仓库结构

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为例:

    @Overridepublic String toString() {return "{" + " intResourceNumber='" + getIntResourceNumber() + "'" + "}";}@Overridepublic 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;}@Overridepublic 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) {// iteratorIterator<PlanningEntry<Resource>> iterator = super.iterator();// new 2D-vectorVector<Vector<?>> vData = new Vector<>();// new titlesVector<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;}}// timeLocalDateTime currentTime = LocalDateTime.parse(strCurrentTime,DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));LocalDateTime scheduleTime = intType == FlightBoard.ARRIVAL ? planningEntry.getTimeArrival(): planningEntry.getTimeLeaving();// check time in rangeif (scheduleTime.isBefore(currentTime.plusHours(HOURS_RANGE))&& scheduleTime.isAfter(currentTime.minusHours(HOURS_RANGE))) {// get informationString 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 vectorVector<String> vRow = new Vector<>();vRow.add(strScheduleTime);vRow.add(planningEntryNumber);vRow.add(locationOrigin);vRow.add("-->");vRow.add(locationTerminal);vRow.add(state);// add in 2D-vectorvData.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>>() {@Overridepublic 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>>() {@Overridepublic 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 {@Overridepublic boolean checkLocationConflict(List<PlanningEntry<Resource>> entries) {……
}
public class PlanningEntryAPIsSecond extends PlanningEntryAPIs {@Overridepublic 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分支所指向的位置。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/47064.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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

为了鄙视代写抄袭伸手党&#xff0c;删除了一些“容易抄袭”的部分 有问题/询问省略部分——欢迎QQ交流&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 本项目于5.19日实验课完成 &#xff08;该更新的&#xff09;更新完成 如果有所参考 请点点关注 点点赞Git…

HIT csapp LAB1报告

实验报告 实 验&#xff08;一&#xff09; 目 录 第1章 实验基本信息 1.1 实验目的 1.2 实验环境与工具 1.2.1 硬件环境 1.2.2 软件环境 1.2.3 开发工具 1.3 实验预习 第2章 实验环境建立 2.1 Windows下 hello程序的编辑与运行&#xff08;5分&#xff09; …

[HITML]哈工大2020秋机器学习Lab4实验报告

Gtihub仓库 2020年春季学期 计算学部《机器学习》课程 Lab4 实验报告 姓名学号班号电子邮件手机号码 1 实验目的 实现一个PCA模型&#xff0c;能够对给定数据进行降维&#xff08;即找到其中的主成分&#xff09; 2 实验要求及实验环境 2.1 实验要求 测试&#xff1a; 首…

[HITML]哈工大2020秋机器学习Lab3实验报告

GitHub仓库 2020年春季学期 计算学部《机器学习》课程 Lab3 实验报告 姓名学号班号电子邮件手机号码 1 实验目的 实现一个k-means算法和混合高斯模型&#xff0c;并且用EM算法估计模型中的参数。 2 实验要求及实验环境 2.1 实验要求 测试&#xff1a;用高斯分布产生k个高…

SwiftUI——提供多种小组件(Widget)供用户选择

有时候app需要提供多种widget供用户选择/使用&#xff0c;那么可以使用以下方法&#xff0c;不过需要注意&#xff0c;每个app最多可以包含5种小组件&#xff08;Widget&#xff09;&#xff1a; 首先做几个小组件&#xff1a; //省略之前的Provider和SimpleEntey部分 //第一个…

iOS轻量级组件化管理工具,让你轻轻松松添加自己的小组件.

2019独角兽企业重金招聘Python工程师标准>>> TXRouter 轻量级组件化管理工具,让你轻轻松松添加自己的小组件.该工具原理简单、制作轻松、思路清晰等优点. TXRouter优点 比MGJRouter更加简单、使用更加方便原理简单、制作轻松、思路清晰 TXModel缺点 不能高大上定义U…

iOS14 Widget小组件开发实践1——了解Widget

iOS14带来了新的UI组件&#xff1a;WidgetKit&#xff0c;人称小组件&#xff08;用过安卓的都能理解&#xff09;&#xff0c;这货的前身其实就是iOS10时候引入的Today Extension。 Widget为应用程序提供了这样一种功能&#xff1a;其可以让用户在主屏幕上展示App中用户所关心…

如何用iOS14 Widget小组件自定义玩法

一.了解Widget iOS14带来了新的UI组件:WidgetKit,人称小组件(用过安卓的都能理解),这货的前身其实就是iOS10时候引入的Today Extension。 Widget为应用程序提供了这样一种功能:其可以让用户在主屏幕上展示App中用户所关心的信息。例如一款天气软件,其可以附带一个Widg…

iOS14 Widget开发踩坑(二)修正版-多个小组件

iOS14 Widget开发踩坑&#xff08;二&#xff09;修正版-多个小组件 前言添加多个小组件情况1.对于已经写好的一个小组件情况2.对于新建好还没写的小组件 Widgets的限制突破小组件个数限制的方法参考文献 前言 2020年12月3日 经过进一步的学习 发现了文章中还有很多错误&#…

IOS小组件(1):概述

IOS小组件(1):概述 引言小组件是什么?小组件概述小组件开发备注小组件实现原理结语引言 本系列文章作者是安卓开发,以安卓开发的视角学习IOS小组件,记录一下踩坑记录,如有讲得不对的地方,路过大佬多包涵。如果你是想深入学习小组件,建议您顺着笔者的编号顺序阅读本系列…

ios中在app应用内刷新小组件数据

需求&#xff1a; 我们需要在app应用内刷新时间线&#xff0c;让桌面小组件加载最新的内容。即app内修改了共享数据后&#xff0c;需要通知桌面小组件强制刷新&#xff0c;显示改变后的内容。 当某种情况影响到小组件的当前时间线时&#xff0c;您的 App 可以指示 WidgetKit …

android 仿苹果 小组件,仿ios14桌面小部件

仿ios14桌面小部件&#xff0c;这是一款面向广大安卓手机用户推出的高仿iOS14桌面插件软件&#xff0c;大家可以使用这款软件快速完成自己想要的桌面显示&#xff0c;多种插件一键点击轻松完成设置过程&#xff0c;让大家体验到同款iOS14桌面强大的功能&#xff0c;非常有意思的…

iOS14 小组件 开发1

前言:小组件的开发和我们正常情况开发App的逻辑是一样的,正常情况分为:网络请求,数据模型,view,渲染.只不过是小组件的开发使用了 swiftUI 语言来编写,所以要对SwiftUI的空间有所了解. 好!那我们接下来开始我们的小组件开发吧. 首先,创建Widget Extension 然后选择证书,起个名…

Android-为应用添加widget小组件

最近在实现为应用程序添加小组件的功能&#xff0c;记录一下开发过程。 1.添加一个小组件模板 设置组件类名称&#xff0c;其他属性按需求设置&#xff0c;这些属性在生成的文件中也可修改 ​​​​​​ 工程main目录下会生成类文件&#xff0c;res目录下生成资源文件&#x…

iOS 15 新增多个实用小组件

iOS 14 上线加入的桌面小组件功能非常受欢迎&#xff0c;因此苹果iOS 15新系统对小组件功能进行深度优化&#xff0c;并加入了多个实用又有趣的小组件。用户可以简单通过上下滑动来选择、重新排列、智能堆栈小组件&#xff0c;也可以对它们进行删除、智能旋转&#xff0c;还可以…

Widget小组件

目录 技能点 Widget背调 a. 设计定位 b. Widget小组件限制 c. Widget小组件 开发须知 d. 什么是 SwiftUI App Group 数据共享 a. 配置 App Groups 1、开发者账号配置&#xff0c;并更新pp证书 2、Xcode配置 b. 缓存数据共享-代码实现 1、文件存储 2. 沙盒存储&…

iOS_小组件widget基本功能

创建 在当前工程里新建target 选择Today Extension 独立应用 widget虽做为应用的扩展, 但却是两个完全独立的应用 widget上线需要单独申请 AppID 和 Bundle Identifier , 需要配置 证书 和 Provisioning Profiles(配置文件) 第三方pod导入, 也的重新导入一份 target MMWidg…

iOS 14-Widget小组件2—实现

Widget 实现 认识与配置实现效果图支持显示方式交互数据共享刷新策略网络加载屏幕适配支持多个小部件布局例子源码其他问题参考认识与配置 上一篇文章已经做了比较详细的介绍与创建配置iOS 14-Widget小组件1—初识 实现效果图 支持显示方式 @main 入口添加.supportedFamilie…

自定义开发苹果手机显示汽车小组件

实时获取汽车数据信息实时获取汽车定位信息&#xff08;点击地址进入高德&#xff09;显示当前位置的天气情况可定制显示当前城市是否限行可定制当前城市油价信息实时刷新最新数据 感兴趣的可以私聊加群

iOS 小组件 widget

苹果官网文档&#xff08;apple developer&#xff09;widgetkit 文章目录 1.创建小组件2.编辑小组件3.数据共享4.拖动排序5.参考链接 1.创建小组件 File -> New -> Target 搜索widget,点击next 勾上Include Configuration Intent, 表示需要编辑小组件&#xff0c;点击f…