学习笔记6(聚合)

聚合(aggregation)

在现实生活中,一个对象可能是由很多部分组成的,比如房子这个对象是由许多窗户对象,墙对象等等共同组成的。所以有时需要从一个对象扩展,进而创建出另一个对象。 

在程序设计中,有些时候也要对一个对象扩展创建新的对象,比如在大学里你希望创建一个course类,它拥有如下信息:课程的名字,导师的各种信息(姓、名、办公室),所用书本的信息(书名、出版社、作者)。

在这个例子中,course类涉及到了导师和书本这两个对象。当然,我们可以把这两个对象的所有消息都储存在course类的字段当中,但是这并不是一个好的习惯。好的原则是分离这些项,使他们有自己的类,比如导师有自己的instructor类,书本有textbook类,下面以instructor类为例。

1、例子

(1)instructor类和textbook类

学习笔记6(聚合)_第1张图片

 

 /**
  This class stores data about an instructor.
  */
 
  public class Instructor
  {
  private String lastName; // Last name
  private String firstName; // First name
  private String officeNumber; // Office number

 /**
 This constructor initializes the last name,
 first name, and office number.
 @param lname The instructor's last name.
 @param fname The instructor's first name.
 @param office The office number.
 */

 public Instructor(String lname, String fname,
 String office)
 {
 lastName = lname;
 firstName = fname;
 officeNumber = office;
 }

 /**
 The copy constructor initializes the object
 as a copy of another Instructor object.
 @param object2 The object to copy.
 */

 public Instructor(Instructor object2)
 {
 lastName = object2.lastName;
 firstName = object2.firstName;
 officeNumber = object2.officeNumber;
 }

 /**
 The set method sets a value for each field.
 @param lname The instructor's last name.
 @param fname The instructor's first name.
 @param office The office number.
 */

 public void set(String lname, String fname,
 String office)
 {
 lastName = lname;
 firstName = fname;
 officeNumber = office;
 }
 /**
 toString method
 @return A string containing the instructor
 information.
 */

 public String toString()
 {
 // Create a string representing the object.
 String str = "Last Name: " + lastName +
 "\nFirst Name: " + firstName +
 "\nOffice Number: " + officeNumber;

 // Return the string.
 return str;
} 
}

 textbook类和instructor类基本上一样:

学习笔记6(聚合)_第2张图片

(2) course类和主函数

学习笔记6(聚合)_第3张图片

 

/**
  This class stores data about a course.
  */
 
  public class Course
  {
  private String courseName; // Name of the course
  private Instructor instructor; // The instructor
  private TextBook textBook; // The textbook

 /**
 This constructor initializes the courseName,
 instructor, and text fields.
 @param name The name of the course.
 @param instructor An Instructor object.
 @param text A TextBook object.
 */

 public Course(String name, Instructor instr,
 TextBook text)
 {
 // Assign the courseName.
 courseName = name;
 // Create a new Instructor object, passing
 // instr as an argument to the copy constructor.
 instructor = new Instructor(instr);

 // Create a new TextBook object, passing
 // text as an argument to the copy constructor.
 textBook = new TextBook(text);
 }

 /**
 getName method
 @return The name of the course.
 */

 public String getName()
 {
 return courseName;
 }

 /**
 getInstructor method
 @return A reference to a copy of this course's
 Instructor object.
 */

 public Instructor getInstructor()
 {
 // Return a copy of the instructor object.
 return new Instructor(instructor);
 }

 /**
 getTextBook method
 @return A reference to a copy of this course's
 TextBook object.
 */

 public TextBook getTextBook()
 {
 // Return a copy of the textBook object.
 return new TextBook(textBook);
 }

 /**
 toString method
 @return A string containing the course information.
 */

 public String toString()
 {
 // Create a string representing the object.
 String str = "Course name: " + courseName +
 "\nInstructor Information:\n" +
 instructor +
 "\nTextbook Information:\n" +
 textBook;

 // Return the string.
 return str;
 }
 }
当一个类中的实例有其他类的成员的时候,我们就说这两个类有一个“has a”关系。比如上三个类之间的关系就可以描述为:
    The course has an instructor.
    The course has a textbook.
有时候这种关系也被称为“whole-part”关系,因为一个类是另外一个更大的类的一部分。
我们来分析这个例子。
course类的构造器将instructor类的一个对象和textbook类的一个对象当做参数传入构造器(这两个对象是在主函数中创建的,其属性也是在主函数中设置的)。在course类的构造器中,new了一个instructor实例和textbook实例,将在主函数中写入的两个实例的信息深拷贝给course类构造器中新创建的这两个实例,这样就将在主函数中创建的两个实例聚合到course类的实例上了。
值得注意的是在聚合的过程中,course类的构造器已经接受了instructor类和textbook类的实例,可以直接使用这两个参数,将这两个参数直接聚合到course类中,但是在例子中却用复制构造器进行了深拷贝,再将深拷贝之后的两个实例聚合到course类中。这样做主要是为了数据的安全,之后会进行讨论。
下面是主函数:
/**
  This program demonstrates the Course class.
  */
 
  public class CourseDemo
  {
  public static void main(String[] args)
  {
  // Create an Instructor object.
 Instructor myInstructor =
 new Instructor("Kramer", "Shawn", "RH3010");

 // Create a TextBook object.
 TextBook myTextBook =
 new TextBook("Starting Out with Java",
 "Gaddis", "Pearson");

 // Create a Course object.
 Course myCourse =
 new Course("Intro to Java", myInstructor,
 myTextBook);

 // Display the course information.
 System.out.println(myCourse);
 }
 }

主函数先创建了一个instructor对象和textbook对象,初始化这两个对象,然后创建了course对象,将之前创建的instructor类型的实例和textbook类型的实例当做参数传入。实际上相当于分级进行初始化,更具层次。

(3)UML图

上面的程序实际上有三层,主函数一层,course类一层,textbook和instroctor类一层,用语言叙述很难理清关系,用UML图就清楚很多了。
学习笔记6(聚合)_第4张图片

靠近course类的那个菱形符号表示聚合,靠近谁就表示谁是聚合的整体。

2、聚合的安全性 

聚合显然是一种相当复杂的关系,所以可能出现安全问题,使得类之外的代码可以篡改类里的私有数据,要注意以下两点:

(1)在将小类传递给大类的时候(textbook类当作参数传递给course类的时候),进行深复制,不要使用对小类的引用。

 public Course(String name, Instructor instr,
 TextBook text)
 {
 // Assign the courseName.
 courseName = name;
 // Create a new Instructor object, passing
 // instr as an argument to the copy constructor.
 instructor = new Instructor(instr);

 // Create a new TextBook object, passing
 // text as an argument to the copy constructor.
 textBook = new TextBook(text);
 }

为什么进行深复制呢?因为course类的构造器是public的,所以可以在course类之外使用course类的构造器,如果在course类之外使用了course类的构造器,在主函数里也使用了course类的构造器,在主函数中初始化的course类的值就有可能被course类之外所使用的course类构造器所覆盖,这显然不是我们希望发生的。注意,使用深复制不是禁止course类之外使用构造器(如果希望禁止在类外使用构造器应该在构造器前使用private) ,而是保证即使主函数外使用了course类构造器时,主函数中初始化的数据也不会被覆盖。

那为什么string类的数据就不需要进行深复制呢?因为string类型非常特殊,虽然不属于基础数据类型,虽然传参的时候和其他非基础数据类型一样也传递的是地址,但是string类型本身是不可变的。比如一个内存中储存了string=“I love you”,这个内存中的数据就是不可改变的了。而同样不属于基础数据类型的textbook类就是可变的,比如textbook类有字段page,page有一个自己的内存,储存的值是一个int 10 ,这个内存还可以被改成任意的int类型的值。

所以在本例中,“Even if variables outside the Course class reference the same object that courseName references, the object cannot be changed.

而如果聚合的时候传参是基本数据类型,正如我们之前所说的,基础数据类型会先进行复制,再传入构造器,在构造器中做的任何改变都只是改变复制过后的值,并不影响原来的值。比如原来的参数a=2储存在内存001中,传参的时候会将2复制一份储存在内存002中,在course类之外你只会改变002的值,并不会改变001中储存的a=2.

(2)返回值的时候,返回复制的对象而非对象本身。

public Instructor getInstructor()
 {
 // Return a copy of the instructor object.
 return new Instructor(instructor);
 }

如果我们将程序这样写:

// Bad method
public Instructor getInstructor()
{
 // Return a reference to the instructor object.
 return instructor; // WRONG! Causes a security hole.
}

这个程序返回了一个对instructor类的引用,这意味着如果在course类之外调用这个方法的时候可以直接访问这个instructor对象,进而进行修改,这显然不是我们希望的,所以我们返回一个和原来instructor对象相同的对象,当从course类调用这个方法的时候,只会返回这个复制过的对象,并不会改变原来对象的值。

3、避免使用null

默认情况下,一个实例的所有字段会默认地设置为null,如果希望对这些设置为null的字段或变量执行操作,将不会通过编译。比如这个例子:

 /**
  This class stores a person's first, last, and middle
  names. The class is dangerous because it does not
  prevent operations on null reference fields.
  */
 
  public class FullName
  {
  private String lastName; // Last name
 private String firstName; // First name
 private String middleName; // Middle name

 /**
 The setLastName method sets the lastName field.
 @param str The String to set lastName to.
 */

 public void setLastName(String str)
 {
 lastName = str;
 }

 /**
 The setFirstName method sets the firstName field.
 @param str The String to set firstName to.
 */

 public void setFirstName(String str)
 {
 firstName = str;
 }

 /**
 The setMiddleName method sets the middleName field.
 @param str The String to set middleName to.
 */

 public void setMiddleName(String str)
 {
 middleName = str;
 }

 /**
 The getLength method returns the length of the
 full name.
 @return The length.
 */

 public int getLength()
 {
 return lastName.length() + firstName.length()
 + middleName.length();
 }

 /**
 The toString method returns the full name.
 @return A reference to a String.
 */

 public String toString()
 {
 return firstName + " " + middleName + " "
 + lastName;
 }
 }

这个例子中实例有三个string字段,由于没有编写构造器,这三个字段会默认设置为null,当调用getLength方法时,如果没有初始化三个string字段中的任意一个,就会报错,解决方法如下:

public int getLength()
{
 int len = 0;
 if (lastName != null)
 len += lastName.length();
 
 if (firstName != null)
 len += firstName.length();
 
 if (middleName != null)
 len += middleName.length();
 
 return len;
}

在getLength方法里,保证调用时对象不为null。

或者在构造类的时候,加上自己的构造器。当你为类编写构造器后,默认构造器就没办法再使用,类中的字段也就不可为null了。

你可能感兴趣的:(html5)