首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >我的修复商店软件的ID生成器类

我的修复商店软件的ID生成器类
EN

Code Review用户
提问于 2019-12-30 14:55:02
回答 3查看 96关注 0票数 3

我为我的修理店软件创建了一个ID生成器类,为软件中使用的所有数据实体生成新的ID。它还将ID的int值转换为用于UI的String值,将用于UI的String值转换为int值。我想对我的IDGenerator课程有一个大致的看法。我是否使用了一些反模式,我的代码是否足够清晰。我的项目在GitHub上。

IDGenerator.java

代码语言:javascript
复制
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;
            }
        }   
    }
}

IDGenertatorTest.java

代码语言:javascript
复制
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;
        }
    }
}
EN

回答 3

Code Review用户

回答已采纳

发布于 2020-01-02 23:32:49

首先,我们应该为您的ID生成器描述一个用户故事。这种描述应该包含用例(包括示例)、需求和相关的技术上下文。

Use-Story

作为一个新实体(系统)的创建者,我想确保标识符(ID)可以在系统中识别这个实体。因此,在创建和持久化新实体时,应该自动生成ID。

作为系统的管理员或用户,我想直接从生成的ID中读取有关实体的一些信息,因此ID应该是一个自然键,它编码了一些业务信息。

需求:

  1. ID应该是数字的
  2. 根据实体类型的不同,ID的形状/模式可能会有所不同。
  3. 被编码到ID中的模式和信息应该说明一些关于它的信息。因此,它包含信息部分,每个部分编码为一个数字。部件可以是工作站id、运行计数器或创建日期。
  4. ID的信息部分(数字)用破折号-分隔。
  5. 每个实体类型的运行计数器应该(至少)是唯一的。
  6. 默认ID (如果没有单独为实体类型指定)应该包含两个部分:(a)工作站-id为1位数,(b)运行计数器为2位数。例如,模式<workstation_id>-<count>可以表示为在工作站1上创建的第一个实体XY (运行计数器01)的1-01
  7. 实体类型票据的ID应该包含一个额外的信息:(c)以6位yyMMdd格式创建日期。例如,模式<workstation_id>-<date_yyMMdd>-<count>可以表示为在工作站130th of December 2019上创建的第一个票据(运行计数器01)的1-191230-01
  8. 此外,实体类型票证的运行计数器应在每个新的创建日期重新设置。因此在每个创建日期内,运行计数器是唯一的。

设计与建模

现在您可以对满足需求所需的数据结构(即类或接口)进行建模。

EntityID:学习UUID类及其实现的接口:

代码语言:javascript
复制
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。对于特例票据,您需要继承这个类,如下所示。

TicketId

代码语言:javascript
复制
public 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])

代码语言:javascript
复制
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());
   }

}

以上课程有一些好处:

  • 现在可以独立测试,而不需要某些DataManager (独立的)
  • 现在可以有不同的实例(每个实例管理特定实体类型及其计数器的状态)。

使用最后一个(多个实例),我们实现了需求5。

  • 可以是专门化的,这意味着它可以继承到管理某些属性TicketIdGenerator的某个类lastIssuedDateUtc,以便在尚未达到counterMax的情况下重置计数器。
代码语言:javascript
复制
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。

票数 3
EN

Code Review用户

发布于 2019-12-31 14:52:03

只对IDGenerator类使用静态方法是反模式的。最好是创建这些实例方法,并在需要时创建类的实例。一个原因可能是可扩展性,另一个原因可能是线程安全。我有一种感觉,静态局部变量lastTicketDate可能会给比赛条件带来麻烦。

如果您确实希望拥有对它的静态访问,请考虑使用静态方法使其为单例来获取实例。这并不理想,但仍然是更好的方法,它正在使您的代码非静态的,保持对类的引用在局部变量/类变量中,而不是静态访问。线程安全问题仍然存在,因为只要有静态方法,任何线程都可以静态访问类。

票数 3
EN

Code Review用户

发布于 2019-12-31 01:47:47

使用普通加法将创建相同的ID。例如,如果对于1个ID,dayIDValue为6,monthIDValue为5,则如果下次dayIDValue为5,monthIDValue为6,则将得到相同的ID。

我建议使用UUID班级。它允许您选择不同的版本,这取决于您需要的ID有多独特。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/234828

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档