我为我的修理店软件创建了一个ID生成器类,为软件中使用的所有数据实体生成新的ID。它还将ID的int值转换为用于UI的String值,将用于UI的String值转换为int值。我想对我的IDGenerator课程有一个大致的看法。我是否使用了一些反模式,我的代码是否足够清晰。我的项目在GitHub上。
public class IDGenerator
{
private static final int WORKSTATION_ID = 1;
private static final int DAY_ID_FORMATER = 100;
private static final int MONTH_ID_FORMATER = 10000;
private static final int YEAR_ID_FORMATER = 1000000;
private static final int WORKSTATION_ID_FORMATER = 100000000;
private static final int WORKSTATION_ID_VALUE = WORKSTATION_ID * WORKSTATION_ID_FORMATER;
private static LocalDate lastTicketDate = LocalDate.MIN;
public static int getNewID(EntityType entityType)
{
Objects.requireNonNull(entityType, "EntityType is null");
int entityCounter = DataManager.getEntityCounter(entityType);
switch(entityType)
{
case TICKET:
{
checkNewWorkDay();
LocalDate today = LocalDate.now();
int dayIDValue = today.getDayOfMonth() * DAY_ID_FORMATER;
int monthIDValue = today.getMonthValue() * MONTH_ID_FORMATER;
int yearIDValue = (today.getYear() % DAY_ID_FORMATER) * YEAR_ID_FORMATER;
return entityCounter + 1
+ dayIDValue
+ monthIDValue
+ yearIDValue
+ WORKSTATION_ID_VALUE;
}
default:
{
return entityCounter + 1 + WORKSTATION_ID_VALUE;
}
}
}
private static void checkNewWorkDay()
{
if(lastTicketDate != LocalDate.now())
{
DataManager.resetTicketCounter();
lastTicketDate = LocalDate.now();
}
}
public static String toString(EntityType entityType, int id)
{
Objects.requireNonNull(entityType, "EntityType is null");
String workstationID = String.valueOf(id / WORKSTATION_ID_FORMATER);
switch(entityType)
{
case TICKET:
{
String date = String.valueOf((id % WORKSTATION_ID_FORMATER) / DAY_ID_FORMATER);
String dailyTicketCounter = String.valueOf(id % DAY_ID_FORMATER);
if(dailyTicketCounter.length() == 1)
{
dailyTicketCounter = '0' + dailyTicketCounter;
}
return workstationID + "-" + date + "-" + dailyTicketCounter;
}
default:
{
String entityCounter = String.valueOf(id % WORKSTATION_ID_FORMATER);
return workstationID + "-" + entityCounter;
}
}
}
public static int toInt(EntityType entityType, String displayName)
{
Objects.requireNonNull(entityType, "EntityType is null");
if(LabelName.NULL_ITEM.equals(displayName))
return 0;
int workstationID = Integer.parseInt(displayName.split("-")[0]) * WORKSTATION_ID_FORMATER;
switch(entityType)
{
case TICKET:
{
int date = Integer.parseInt(displayName.split("-")[1]) * DAY_ID_FORMATER;
int entityCounterNumber = Integer.parseInt(displayName.split("-")[2]);
return workstationID + date + entityCounterNumber;
}
default:
{
int entityCounterNumber = Integer.parseInt(displayName.split("-")[1]);
return workstationID + entityCounterNumber;
}
}
}
}public class IDGeneratorTest
{
@ParameterizedTest
@EnumSource(EntityType.class)
public void getNewIDTest(EntityType entityType)
{
switch(entityType)
{
case TICKET:
assertEquals(119123001, IDGenerator.getNewID(entityType));
break;
default:
assertEquals(100000001, IDGenerator.getNewID(entityType));
break;
}
}
@ParameterizedTest
@EnumSource(EntityType.class)
public void toStringTest(EntityType entityType)
{
switch(entityType)
{
case TICKET:
assertEquals("1-191230-01", IDGenerator.toString(entityType, 119123001));
break;
default:
assertEquals("1-21", IDGenerator.toString(entityType, 100000021));
break;
}
}
@ParameterizedTest
@EnumSource(EntityType.class)
public void toIntTest(EntityType entityType)
{
switch(entityType)
{
case TICKET:
assertEquals(119123001, IDGenerator.toInt(entityType, "1-191230-01"));
break;
default:
assertEquals(100000021, IDGenerator.toInt(entityType, "1-21"));
break;
}
}
}发布于 2020-01-02 23:32:49
首先,我们应该为您的ID生成器描述一个用户故事。这种描述应该包含用例(包括示例)、需求和相关的技术上下文。
作为一个新实体(系统)的创建者,我想确保标识符(ID)可以在系统中识别这个实体。因此,在创建和持久化新实体时,应该自动生成ID。
作为系统的管理员或用户,我想直接从生成的ID中读取有关实体的一些信息,因此ID应该是一个自然键,它编码了一些业务信息。
-分隔。<workstation_id>-<count>可以表示为在工作站1上创建的第一个实体XY (运行计数器01)的1-01。yyMMdd格式创建日期。例如,模式<workstation_id>-<date_yyMMdd>-<count>可以表示为在工作站1上30th of December 2019上创建的第一个票据(运行计数器01)的1-191230-01。现在您可以对满足需求所需的数据结构(即类或接口)进行建模。
类EntityID:学习UUID类及其实现的接口:
public class EntityID implements Serializable, Comparable<EntityID> {
public static final String SEPARATOR = "-";
public static final int REQUIRED_PARTS = 2;
protected int workstationId;
protected int counter;
public EntityID(int workstationId, int counter) {
// could add some parameter validation (like only positive values)
this.workstationId = workstationId;
this.counter = counter;
}
@Override // implements Comparable<EntityID>
public int compareTo(EntityID value) {
// needed to make sure if compared objects are same, or for sorting: lesser, greater
// left for you to implement
return -1;
}
public static EntityID fromString(String name) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArumentException("ID to parse must not be null or empty!");
}
String[] parts = name.split(SEPARATOR);
if (parts.length != REQUIRED_PARTS) {
throw new IllegalArumentException(String.format("ID to parse must consist of %d parts, separated by '%s', but was: %s", REQUIRED_PARTS, SEPARATOR, name));
}
int workstationId = Integer.parseInt(parts[0]);
int counter = Integer.parseInt(parts[1]);
return new EntityID(workstationId, counter);
}
// getter and setter for all information parts
// left to implement
public String toString() {
return String.format("%01d%s%02d", workstationId, SEPARATOR, counter);
}
}这个类服务器默认ID。对于特例票据,您需要继承这个类,如下所示。
TicketIdpublic class TicketID extends EntityID implements Serializable, Comparable<TicketID> {
public static final String SEPARATOR = "-";
public static final int REQUIRED_PARTS = 3;
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd");
protected LocalDate creationDateUtc;
public TicketID(int workstationId, int counter, LocalDate utcDate) {
super(workstationId, counter);
this.creationDateUtc = utcDate;
}
@Override // implements Comparable<TicketID>
public int compareTo(TicketID value) {
// needed to make sure if compared objects are same, or for sorting: lesser, greater
// left for you to implement
return -1;
}
public static TicketID fromString(String name) {
if (name == null || name.trim().isEmpty()) {
throw IllegalArumentException("ID to parse must not be null or empty!");
}
String[] parts = name.split(SEPARATOR);
if (parts.length != REQUIRED_PARTS) {
throw new IllegalArumentException(String.format("ID to parse must consist of %d parts, separated by '%s', but was: %s", REQUIRED_PARTS, SEPARATOR, name));
}
int workstationId = Integer.parseInt(parts[0]);
LocalDate utcDate = LocalDate.parse(parts[1], DATE_FORMATTER);
int counter = Integer.parseInt(parts[2]);
return new TicketID(workstationId, counter, utcDate);
}
// getter and setter for all information parts
// left to implement
public String toString() {
// could also use a DateFormatter like
// return String.format("%01d-%s-%02d", workstationId, DATE_FORMATTER.format(creationDateUtc), counter);
return String.format("%01d-%tY%tm%td-%02d", workstationId, creationDateUtc, counter);
}
}解析(表单表示字符串到值ID)和格式化(从值ID到表示字符串)是如何使用静态facotry方法fromString和实例方法toString实现的。
上述类实现了要求1.4-7。
现在ID发生器开始发挥作用了。使用工厂设计模式,我们可以如下所示实现它。
每个实体类型的counter应该在生成器中管理,而不是依赖于某些DataManager。因此,利用Java的并发计数器类AtomicInteger(https://stackoverflow.com/questions/4818699/practical-uses-for-atomicinteger](https://stackoverflow.com/questions/4818699/practical-uses-for-atomicinteger])
public class EntityIdGenerator {
protected AtomicInteger counter;
protected Integer counterMax;
protected Integer workstationId;
// here the current value from DataManager can be injected as counterStart
// counterMax = 99 (limit to 2-digits), workstationId = 1
public EntityIdGenerator(int counterStart, int counterMax, int workstationId) {
if (counterMax <= counterStart || counterStart < 0 || counterMax < 1) {
throw new IllegalArgumentException("CounterStart must positive and >= 0, counterMax must be > 1 and > counterStart"!);
}
this.counter = new AtomicInteger(counterStart);
this counterMax = counterMax;
this.workstationId = workstationId;
}
protected int rollToNextCounter() {
// if current counter is 99 == counterMax, then reset
boolean hasReachedMaxAndReset = this.counter.compareAndSet(counterMax, 0);
// if counterStart was 0, the nextCounter will also be 0 (first used)
return this.counter.getAndIncrement();
}
public EntityID generateId() {
return new EntityID(this.workstationId, rollToNextCounter());
}
}以上课程有一些好处:
使用最后一个(多个实例),我们实现了需求5。
TicketIdGenerator的某个类lastIssuedDateUtc,以便在尚未达到counterMax的情况下重置计数器。public class TicketIdGenerator extends EntityIdGenerator {
private LocalDate lastIssuedDateUtc;
public TicketIdGenerator(int counterStart, int counterMax, LocalDate lastIssuedDateUtc) {
super(counterStart, counterMax);
if (lastIssuedDateUtc > todayUtc()) {
throw new IllegalArgumentException("date (as UTC) must can be now or in the past; must not be in the future: " + lastIssuedDateUtc);
}
this.lastIssuedDateUtc = lastIssuedDateUtc;
}
public LocalDate todayUtc() {
return LocalDate.now("UTC");
}
public TicketID generateId() {
if (this.lastIssuedDateUtc < todayUtc()) {
this.lastIssuedDateUtc = todayUtc();
}
return new TicketID(this.workstationId, rollToNextCounter(), this.lastIssuedDateUtc);
}
// rest of logic and methods remain same as in EntityIdGenerator
}在上面,我们实现了需求8。
发布于 2019-12-31 14:52:03
只对IDGenerator类使用静态方法是反模式的。最好是创建这些实例方法,并在需要时创建类的实例。一个原因可能是可扩展性,另一个原因可能是线程安全。我有一种感觉,静态局部变量lastTicketDate可能会给比赛条件带来麻烦。
如果您确实希望拥有对它的静态访问,请考虑使用静态方法使其为单例来获取实例。这并不理想,但仍然是更好的方法,它正在使您的代码非静态的,保持对类的引用在局部变量/类变量中,而不是静态访问。线程安全问题仍然存在,因为只要有静态方法,任何线程都可以静态访问类。
发布于 2019-12-31 01:47:47
使用普通加法将创建相同的ID。例如,如果对于1个ID,dayIDValue为6,monthIDValue为5,则如果下次dayIDValue为5,monthIDValue为6,则将得到相同的ID。
我建议使用UUID班级。它允许您选择不同的版本,这取决于您需要的ID有多独特。
https://codereview.stackexchange.com/questions/234828
复制相似问题