Java 中的对象
一、Java Bean对象
JavaBeans 是可重用的 Java 组件,它们是用符合约定的 Java 语言编写的类。 最大的作用是将许多对象封装到单个对象(bean)中,以便它们可以作为单个 bean而不是多个bean传递。 JavaBean 是可序列化的 Java 对象,具有无参构造函数,并允许使用 getter 和 setter 方法访问属性。
为了充当 JavaBean 类,对象类必须遵守有关方法命名、构造和行为的某些约定。 这些约定使得拥有可以使用、重用、替换和连接JavaBean 的工具成为可能。
所需的约定是:
- 该类必须具有公共默认构造函数。 允许在框架(例如Spring)轻松实例化。
- 类属性必须是 private 修饰符修饰
- 必须遵循标准命名约定,与类属性配套 public 修饰的 getter、setter 方法 和其他方法。 这允许在框架内轻松自动检查和更新 bean 的属性。
- 该类应该是可序列化的。 这允许应用程序和框架以独立于 VM 和平台的方式可靠地保存、存储和恢复 bean 的状态。
例如下面的例子,这是一个标准Java Bean对象,虽然这里没有显式定义构造函数,但Java类本来就有个默认构造函数。
//这是一个javaBean 对象
public class Person implements Serializable{
private String name;
private int age;
public String getName(){ return name;}
public void setName(String name){this.name=name;}
public int getAge(){ return age;}
public void setAge(int age){this.age=age;}
}
有趣的另一个示例,这不是Java Bean因为类属性没有private修饰符以及没有getter/setter方法。
//这不是javaBean 对象
public class Person{
String name;
int age;
}
二、POJO 对象
POJO 对象之所有叫简单类,是因为有如下7个特征
- 不可实现任何接口
- 不可扩展任何类
- 不可具备业务逻辑
- 仅含无参构造函数
- 没有外部注解的
- 类属性的访问修饰符为private
- 类有和属性相关的getter 和 setter。
POJO的实例主要用于数据的临时传递,但不可以跨网络传输。这里可以看如下示例,Person是一个一个POJO对象
public class Person{
private String name;
private int age;
public String getName(){ return name;}
public void setName(String name){this.name=name;}
public int getAge(){ return age;}
public void setAge(int age){this.age=age;}
}
那么以下这些都并非Pojo对象
//这是一个javaBean 对象
public class Person implements Serializable{
private String name;
private int age;
public String getName(){ return name;}
public void setName(String name){this.name=name;}
public int getAge(){ return age;}
public void setAge(int age){this.age=age;}
}
UnitDto不是POJO对象,而是一个数据传输对象(DTO,下文会提及)、也是一个Bean
//这不是POJO对象
public class UnitDto extends EasyUiDto<Unit> {
@NotBlank(message = "参数name不能为空")
@NoSpeChar(message = "参数name不能包含特殊字符")
private String name;
@JsonCreator
public UnitDto(
@JsonProperty("page") Integer page,
@JsonProperty("rows") Integer rows,
@JsonProperty("order") String order,
@JsonProperty("sort") String sort,
@JsonProperty("name") String name){
super(page,rows,order,sort);
this.name="".equals(name)?"%%":"%"+name+"%";
System.out.println("UnitDto:"+this.name);
}
@Override
public Unit toEntity() {
Unit cat=new Unit();
BeanUtils.copyProperties(this,cat);
return cat;
}
}
POJO与JavaBean的比较:
JavaBean遵循了POJO的命名规范和类属性的约定。但POJO不能等同JavaBean,而JavaBean可看作POJO的一个超集。
三、Entity(实体对象)
实体对象满足JavaBean的所有特征,并且有自己的特征。
- 可以根据业务需求自定义一个或多个构造函数。
- getter或setter方法内部允许存在小量的业务逻辑。
- 部分getter方法不一定与Entity的属性对应。例如下面示例的getAge()用于获取User对象的年龄,而这个年龄是根据属性birth日期对象计算出来的。
例如下表的User的它的属性和数据库中的User表的所有列一一对应的。
public class User implements Serializable {
private Integer id;
private String username;
private LocalDate birth;
public User(){}
public User(String name,LocalDate birth){
this.username=username;
this.birth=birth
}
public LocalDate getBirth() {
return birth;
}
public void setBirth(LocalDate birth) {
this.birth = birth;
}
public void setBirth(String date){
DateTimeFormatter fm=DateTimeFormatter.ofPattern("yyyy-MM-dd");
birth=LocalDate.parse(date,fm);
}
public Integer getAge() {
if(birth==null){
return 0;
}
LocalDate pdate= LocalDate.of(birth.getYear(),birth.getMonth(),
birth.getDayOfMonth());
LocalDate now=LocalDate.now();
Period dif=Period.between(pdate,now);
return dif.getYears();
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
Entity的作用实现与数据库表一一对应的关系,即我们常说的ORM(对象关系映射)。
一个表名对应一个类名
每个表的列对应Entity类中的属性(名的列名称和属性名不相同时,需要额外做映射配置,例如MyBatis中的Mapper配置)。
业务层的方法通常将一个Entity作为该方法的参数,通过传入Entity实参可以对字段定义和状态进行判断或过滤操作。
数据持久层的方法主要以Entity作为该方法的参数,读取Entity的各个属性值,并构建SQL语句。
换句话说,网上很多文章将Entity和JavaBean等同其实是不太正确的。Entity是JavaBean的一个超集。
四、Value Object
值对象或 VO 是一个对象,从Java编程语法符合Entity的所有约定规则,之所以要从 Entity 区分 VO 出来,是因为要从某些业务需求上强化一个容器存储数据值的特性,弱化容器的引用特性(即对象的唯一标识,从C的角度理解即VO实例的内存地址)。这意味着具有相同属性集的两个 VO 实例应该被认为是相同的,而两个Entity实例即使它们的属性匹配,但彼此的内存地址是不一样的会被认为不同。VO并且通常不用于在应用程序边界之间传输数据。
在 Java 中实现Value Object模式有点困难,因为 == 运算符总会比较对象的内存地址。
public class PersonVo implements Serializable {
private String name;
private Integer age;
public PersonVo(String name, Integer age) {
this.name = name;
this.age = age;
}
public PersonVo() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PersonVo personVo = (PersonVo) o;
return name.equals(personVo.name) && age.equals(personVo.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
下面这段代码没什么好解释的,如果你非得在==操作符上纠结的话,那么返回per1和per2的比较结果不相同。
public class Test {
public static void main(String[] args) {
PersonVo per1=new PersonVo("itDog",26);
PersonVo per2=new PersonVo("itDog",26);
System.out.println(per1==per2); //false
System.out.println(per1.equals(per2)); //true
}
}
五、数据传输对象
数据传输对象 (DTO)完全符合Enitiy的所有约定,但DTO更着重在程序表现层处于用户设备和Controller传输数据的设计模式。DTO的存在如下设计需求:
- 通过DTO验证所有请求的数据是否安全
- DTO隐藏数据库的敏感的字段信息,透过DTO向用户界面的字段名完全可以跟实体的字段不一样,或者选择不公开实体的某些字段。
- DTO 的传输逻辑如下图所示。
六、Domain Object
从编程约定来说,领域对象的构成其实和Entity、DTO是没有区别,但我们要理解的Domain这个词所赋予的特殊含义--领域(或者说"行业"),
例如银行是一个领域、保险金融是一个领域、医疗也是一个领域。要为这些行业开发对应的信息系统。对于对象的看法不同领域的人去理解是不一样的,因为不同的领域的业务细节也不尽相同。
例如一个人去银行办理业务,那么银行从业人员会将这个人看作账户(Account),一个人可以有一个或多个账户Account。这里使用依赖组合的方式来设计该领域模型
public class Account implements Serializable{
private Person person; //人
private Long AccountID; //银行账户
private Double money; //银行存款
...
public void transfer(Account a,Account b){....} //转账
}
或者类继承的可以这样设计Account的领域模型
public class Person implements Serializable{.....}
public class Account extends Person {
private Long AccountID; //银行账户
private Double money; //银行存款
...
public void transfer(Account a,Account b){....} //转账
}
对于医院,医疗从业者会将这个人看作是病人(Patient)。
public class Patient implements Serializable{
private Person person; //人
private Long visitID; //就诊号
private Integer branch; //隶属医科分类
private Integer bedNo; //床号
...
public void payment(Double money){....}
}
或者使用类继承的方式去这样设计Patient
public class Person implements Serializable{.....}
public class Patient extends Person {
private Long visitID; //就诊号
private Integer branch; //隶属医科分类
private Integer bedNo; //床号
...
public void payment(Double money){....}
}
领域对象和实体都是类。为了区分它们,通常针对,我们会为领域对象建立一个独立的包,而不应该和实体所在包混合在一起,某些情况下,我们可以将领域对象可以充当DTO的角色,也可以充当实体的角色。没有条文规定一定要做什么。即便是这样,在 Java 世界中,领域对象使用得更多,而在 C# 世界中,MS 又会称其为模型(model),在笔者看来,不同的厂商的编程语言为了树立自家的技术规范,在对象命名规范中装逼而已。