当前位置: 首页 > 编程笔记 >

Java和Ceylon对象的构造和验证

龚镜
2023-03-14
本文向大家介绍Java和Ceylon对象的构造和验证,包括了Java和Ceylon对象的构造和验证的使用技巧和注意事项,需要的朋友参考一下

当变换Java代码为Ceylon代码时,有时候我会遇到一些Java类构造器混淆了验证与初始化的情形。让我们使用一个简单但是人为的代码例子来说明我想阐述的意思。

一些坏代码

考虑下面的Java类。(伙计,不要在家里写这样的代码)

public class Period {
 private final Date startDate;
 private final Date endDate;
 //returns null if the given String
 //does not represent a valid Date
 private Date parseDate(String date) {
  ...
 }
 public Period(String start, String end) {
  startDate = parseDate(start);
  endDate = parseDate(end);
 }
 public boolean isValid() {
  return startDate!=null && endDate!=null;
 }
 public Date getStartDate() {
  if (startDate==null) 
   throw new IllegalStateException();
  return startDate;
 }
 public Date getEndDate() {
  if (endDate==null)
   throw new IllegalStateException();
  return endDate;
 }
}

嘿,我之前已经警告过,它是人为的。但是,在实际Java代码中找个像这样的东西实际上并非不常见。

这里的问题在于,即使输入参数(在隐藏的parseDate()方法中)的验证失败了,我们还是会获得一个Period的实例。但是我们获取的那个Period不是一个“有效的”状态。严格地说,我的意思是什么呢?

好吧,假如一个对象不能有意义地响应公用操作时,我会说它处于一个非有效状态。在这个例子里,getStartDate() 和getEndDate()会抛出一个IllegalStateException异常,这就是我认为不是“有意义的”一种情况。

从另外一方面来看这个例子,在设计Period时,我们这儿出现了类型安全的失败。未检查的异常代表了类型系统中的一个“漏洞”。因此,一个更好的Period的类型安全的设计,会是一个不使用未检查的异常—在这个例子中意味着不抛出IllegalStateException异常

(实际上,在真实代码中,我更有可能遇到一个getStartDate() 方法它不检查null ,在这个代码行之后就会导致一个NullPointerException异常,这就更加糟糕了。)

我们能够很容易地转换上面的Period类成为Ceylon形式的类:

shared class Period(String start, String end) {
 //returns null if the given String
 //does not represent a valid Date
 Date? parseDate(String date) => ... ;
 value maybeStartDate = parseDate(start);
 value maybeEndDate = parseDate(end);
 shared Boolean valid
  => maybeStartDate exists 
  && maybeEndDate exists;
 shared Date startDate {
  assert (exists maybeStartDate);
  return maybeStartDate;
 }
 shared Date endDate {
  assert (exists maybeEndDate);
  return maybeEndDate;
 }
}

当然了,这段代码也会遇到与原始Java代码同样的问题。两个assert符号冲着我们大喊,在代码的类型安全中有一个问题。

使Java代码变得更好

Java里我们怎么改进这段代码呢?好吧,这儿就是一个例子关于Java饱受诟病的已检查异常会是一个非常合理的解决方法!我们可以稍微修改下Period来从它的构造器中抛出一个已检查的异常:

public class Period {
 private final Date startDate;
 private final Date endDate;
 //throws if the given String
 //does not represent a valid Date
 private Date parseDate(String date)
   throws DateFormatException {
  ...
 }
 public Period(String start, String end) 
   throws DateFormatException {
  startDate = parseDate(start);
  endDate = parseDate(end);
 }
 public Date getStartDate() {
  return startDate;
 }
 public Date getEndDate() {
  return endDate;
 }
}

现在,使用这个解决方案,我们就不会获取一个处于非有效状态的Period,实例化Period的代码会由编译器负责去处理无效输入的情形,它会捕获一个DateFormatException异常。

try {
 Period p = new Period(start, end);
 ...
}
catch (DateFormatException dfe) {
 ...
}

这是一个对已检查异常不错的、完美的、正确的使用,不幸的是我几乎很少看到Java代码像上面这样使用已检查异常。

使Ceylon代码变得更好

那么Ceylon怎么样呢?Ceylon没有已检查异常,因而我们需要寻找一个不同的解决方式。典型地,在Java调用一个函数会抛出一个已检查异常的情形中,Ceylon会调用函数返回一个联合类型。因为,一个类的初始化不返回除了类自己外的任何类型,我们需要提取一些混合的初始化/验证的逻辑来使其成为一个工厂函数。

//returns DateFormatError if the given 
//String does not represent a valid Date
Date|DateFormatError parseDate(String date) => ... ;
shared Period|DateFormatError parsePeriod
  (String start, String end) {
 value startDate = parseDate(start);
 if (is DateFormatError startDate) {
  return startDate;
 }
 value endDate = parseDate(end);
 if (is DateFormatError endDate) {
  return endDate;
 }
 return Period(startDate, endDate);
}
shared class Period(startDate, endDate) {
 shared Date startDate;
 shared Date endDate;
}

根据类型系统,调用者有义务去处理DateFormatError:

value p = parsePeriod(start, end);
if (is DateFormatError p) {
 ...
}
else {
 ...
}

或者,如果我们不关心给定日期格式的实际问题(这是有可能的,假定我们工作的初始化代码丢失了那个信息),我们可以使用Null而不是DateFormatError:

//returns null if the given String 
//does not represent a valid Date
Date? parseDate(String date) => ... ;
shared Period? parsePeriod(String start, String end)
 => if (exists startDate = parseDate(start), 
   exists endDate = parseDate(end))
  then Period(startDate, endDate)
  else null;
shared class Period(startDate, endDate) {
 shared Date startDate;
 shared Date endDate;
}

至少可以说,使用工厂函数的方法是优秀的,因为通常来说在验证逻辑和对象初始化之间它具有更好的隔离。这点在Ceylon中特别有用,在Ceylon中,编译器在对象初始化逻辑中添加了一些非常严厉的限制,以保证对象的所有领域仅被赋值一次。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。

 类似资料:
  • 类 类是对某一事物的抽象描述,具体地讲,类是C++中的一种构造的数据类型。它即可包含描述事物的数据,又可包含处理这些数据的函数,类在程序运行时是被用作样板来建立对象的。所以要建立对象,首先必须定义类。 定义类 定义一个类的一般格式为: class 类名{     private:         成员表1;     public:         成员表2;     protected:     

  • 映射对构造函数没有限制或要求 (__init__ )类的方法。您可以自由地为您想要的函数要求任何参数,为ORM未知的实例分配属性,并且通常在编写Python类的构造函数时做您通常会做的任何其他事情。 sqlAlchemy ORM不调用 __init__ 从数据库行重新创建对象时。ORM的过程有点类似于Python标准库的 pickle 模块,调用低级 __new__ 方法,然后在实例上悄悄地恢复属

  • 我有一个如下所示的构造函数。 使用Mockito,我无法初始化所有3个对象accountDetailsRestClient、constantMapService和calculationMap。如果accountDetailsRestClient不为null,则constantMapService为null,反之亦然。 Mockito测试类: 但是accountDetailsRestClient现在

  • 8.5. 构造值对象 Go有一个内建的new函数,用于在堆上为任意类型变量分配一个空间。新分配的 内存会内自动初始化为0。 例如,new(int) 会在堆上分配一个整型大小的空间, 然后初始化为0,然后返回 *int 类型的地址。 和C++中不同的 是,new是一个函数而不是运算符,因此 new int 用法是错误的。 对于字典和管道,必须用内建的make函数分配空间。对于没有初始化的字典或 管道

  • 问题内容: 是否完全违反Java创建类结构对象的方式? 我可以看到一个类,其访问器和增变器更像Java。 第一个示例中的类在符号上很方便。 这不是那么方便。 问题答案: 这是一个经常讨论的话题。在对象中创建公共字段的缺点是您无法控制为其设置的值。在有许多程序员使用同一代码的小组项目中,避免副作用很重要。此外,有时最好返回字段对象的副本或以某种方式对其进行转换等。您可以在测试中模拟此类方法。如果创建

  • 经过前面一系列的学习,你可能对类和对象已经有了一定的了解,这是因为 Java 语言是纯面向对象的编程语言,类和对象在 Java 中无处不在。 在程序设计时,我们针对事物的特征和行为使用代码进行实现,就是面向对象编程。可以毫不夸张地说,面向对象编程是中大型项目代码最好的组织形式。 本小节我们将学习类和对象的基本概念、类和对象的关系,除了字段和方法,在类中都可以定义什么其他内容。也会讲解什么是实例化、