欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java和Ceylon对象的构造和验证

程序员文章站 2024-03-11 12:46:31
当变换java代码为ceylon代码时,有时候我会遇到一些java类构造器混淆了验证与初始化的情形。让我们使用一个简单但是人为的代码例子来说明我想阐述的意思。 一些坏代码...

当变换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中,编译器在对象初始化逻辑中添加了一些非常严厉的限制,以保证对象的所有领域仅被赋值一次。

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