原始类型之殇

作者: 真海 | 来源:发表于2018-11-08 00:10 被阅读8次

在日常编码设计中,我们在领域模型中表达属性时既可以使用原始类型也可以使用强类型,例如表达金额的时候既可以使用long也可以使用Money,那在原始类型与强类型之间应该如何选择呢?

原始类型弊端(Primitive obession)

下面的例子是一个Customer类

public class Customer {

    private String name;

    private String email;

    public Customer(String name, String email) {

        this.name = name;

        this.email = email;

    }

}

在Customer中的属性都是通过原始类型表示的,现在需要对name和email加上一些校验逻辑(领域知识)

public class Customer {

    private String name;

    private String email;

    public Customer(String name, String email) {

        if ((name == null) || (name.length() > 4)) {

            throw new InvalidArgumentException("name is invalid");

        }

        if ((email == null) ||

                !email.matches("^([\\w\\.\\-]+)@([\\w\\-]+)((\\.(\\w){2,3})+)$")) {

            throw new InvalidArgumentException("email is invalid");

        }

        this.name = name;

        this.email = email;

    }

}

然而,除了需要在领域模型中进行校验之外,相同的逻辑还需要在其他地方(如业务层,显示层等)实现一遍

public class CustomerService {

    public Customer createCustomer(String name, String email) {

        if ((name == null) || (name.length() > 4)) {

            throw new InvalidArgumentException("name is invalid");

        }

        if ((email == null) ||

                !email.matches("^([\\w\\.\\-]+)@([\\w\\-]+)((\\.(\\w){2,3})+)$")) {

            throw new InvalidArgumentException("email is invalid");

        }

        return new Customer(name, email);

    }

}

可以看到上面的代码违反了DRY原则,同一段逻辑在不同的地方出现。因为name与email的校验知识属于领域知识,所以在领域模型中必须有体现,不能因为service校验了就不管(而且Customer可能会在很多其它地方使用),而service也需要对参数进行检验,当然可以使用声明式校验框架来对参数进行统一检查,不过这属于技术实现。作为领域知识来思考的话,name与string,email与string是否等价,其实不然,email除了string表示的值之外了还包含了其他的隐藏规则,而这些规则是单独的string无法承载的

强类型

为了解决上面的问题,可以通过将name与email定义成强类型

public class CustomerName {

    private String value;

    private Customer Name(String value) {

        this.value = value;

    }

    public static Result create(String value) {

        if ((value == null) || (value.length() > 4)) {

            return Result.Fail("name is invalid");

        }

        return Result.Success(newCustomerName(value));

    }

    //TODO get/equals/hashcode

}

email类型与name类型类似

这种方式的好处是将校验逻辑(或者其他业务逻辑)收敛到一个地方,如果需要修改只需要修改email与name内部逻辑即可,值得注意的是上面类型的构造函数式私有的,这样新建对象只能通过create方法,保证了创建的对象都是合法的

修改之后的业务层逻辑

public class CustomerService {

    public Customer createCustomer(String name, String email) {

        Result customerName = CustomerName.create(value);

        Result email = Email.create(value);

        if (customerName.isFail()) {

            throw new InvalidArgumentException(customerName.getMessage());

        }

        if (email.isFail()) {

            throw new InvalidArgumentException(email.getMessage());

        }

        returnnewCustomer(customerName.getValue(), email.getValue());

    }

}

修改之后的Customer

public class Customer {

    private CustomerName name;

    private Email email;

    public Customer(CustomerName name, Email email) {

        if ((name == null) || (email == null)) {

            throw new InvalidArgumentException("name and email can not be null");

        }

        this.name = name;

        this.email = email;

    }

}

通过强类型的好处有以下几点:

将业务逻辑收敛到了一个单独的领域类中,避免了逻辑散落到各个地方

强类型可以减少出错机会,在进行赋值操作时编译器会强制检查,减少了将email的值赋值给name之类的错误

需要指出的是,在使用强类型的时候应该将类型穿越整个系统,直到到达系统边界时(client调用或者数据持久化),而不是在系统中进行不但的转换。

//bad

public void  changeEmail(String oldEmail, String newMail) {   

    Result oldEmail = Email.create(value);   

    Result newEmail = Email.create(value);

    if ( oldEmail.isFail() || newEmail.isFail() ) {

          return;

    }

    Customer customer = getCustomerByEmail(oldEmail); 

    customer.setEmail(newEmail.value); 

  }

//good

public  void  changeEmail(Email oldEmail, Email newMail)  {   

      Customer customer = getCustomerByEmail(oldEmail);       

      customer.setEmail(newEmail);

}

由于使用强类型需要在类型与原始值之间进行转换,而且需要定义额外的类型,可能会影响代码的整洁性,因此需要根据不同的场景去决定到底是否需要使用强类型

英文原文

相关文章

  • 原始类型之殇

    在日常编码设计中,我们在领域模型中表达属性时既可以使用原始类型也可以使用强类型,例如表达金额的时候既可以使用lon...

  • Javascript学习笔记——3类型、值和变量

    数据类型 java script的数据类型分为两类:原始类型和对象类型。 原始类型 原始类型包括数字、字符串和布尔...

  • js 的这几种语言类型你真的了解吗?

    1.js有几种语言类型?【两大类型: 原始类型和引用类型】 *原始类型: 1.又被称为基本类型,原始类型保存的变量...

  • 原始类型与对象类型区别

    在 JS 中,除了原始类型那么其他的都是对象类型了。对象类型和原始类型不同的是,原始类型存储的是值,对象类型存储的...

  • 2.js基础--string

    包装类型:专门封装原始类型的值,并提供对原始类型的值进行操作的API。原始类型的值本身没有任何功能,当试图对原始类...

  • js数据类型运算符流程控制语句

    JavaScript 定义了几种数据类型? 哪些是原始类型?哪些是复杂类型?原始类型和复杂类型的区别是什么? 原始...

  • q第三章 类型、值和变量

    数据类型: 原始类型(primitive type)和对象类型(object type)。原始类型包括: 对象是属...

  • JavaScript面试题

    JavaScript定义了几种数据类型? 哪些是原始类型?哪些是复杂类型?原始类型和复杂类型的区别是什么? 原始类...

  • Js数据类型

    Js有2种类型的值,分别是原始类型值和引用类型值。 原始类型 原始类型值(基础数据类型)有5种,分别是:Undef...

  • JS的数据类型、运算符和表达式

    JS分为原始类型和引用类型 原始类型 原始类型分为数值型、字符串型、布尔型、未定义型(undefined)、nul...

网友评论

    本文标题:原始类型之殇

    本文链接:https://www.haomeiwen.com/subject/svqixqtx.html