本博文将介绍面向过程和面向对象设计的不同指出,将会学习面向对象程序设计的优点,并掌握如何高效的使用它。先来了解几个概念:不可变类、变量的作用域、this引用、类的抽象和封装、面向对象的思考、类的组合及其设计原则。
一:不可变类
一个对象一旦创建其内容就不能改变,这种对象叫做不可变对象。而它的类叫不可变类。必须满足以下三点:
1)所有数据域都是私有的;
2)没有修改器方法;
3)没有一个访问器方法,它会返回一个指向可变数据域的引用。
二:变量的作用域
一个类的实例变量和静态变量称为类变量或数据域。在方法内部定义的变量称为局部变量。类变量作用域是整个类,只能声明一次,但在一个方法不同的非嵌套块中,可以多次声明相同的变量。
如果一个局部变量和一个类变量具有相同的名字,那么局部变量有效,而同名的类变量将被隐藏。举例如下:
注意:为了避免混淆和错误,除了方法中的参数,不要将实例变量或静态变量的名字作为局部变量的名字。
三:this引用
关键字this是指向调用对象本身的引用名。常见的有两种应用方法。
1)引用类的隐藏数据域。隐藏的静态变量可以简单地通过“类名.静态变量”的方式引用;隐藏的实例变量就需要使用关键字this引用。关键字this给出一种指代对象的方法,这样,可以在实例方法中调用实例方法。
2)让构造方法调用同一个类的另一个构造方法。如下所示:
注意:如果一个类有多个构造方法,最好尽可能使用this(参数列表)实现它们。通常,无参数或参数少的构造方法可以用this(参数表)调用参数多的构造方法。这样做通常可以简化代码,使类易于阅读和维护。
Java要求在构造方法中,语句this(参数列表)应在任何其他语句之前出现 。
四:类的抽象和封装
类的抽象也是将类的实现和使用分离。如下图所示:
类的合约:类的创建者提供类的描述,让使用者明白如何才能实用类。从类外可以访问的全部方法和数据域,以及期望这些成员如何行动的描述,合称为类的合约。
类的封装:类的实现细节经过封装,对用户隐藏起来。
注意:在类的实现之前,可以先编写这个类的测试程序,即使不知道这个类是如何执行的也可以。这样做有以下几点好处:
1)演示开发类和使用类是两个不同的任务;
2)能使你跳过某个类的复杂实现、而不干扰整体思路;
3)通过使用熟悉类,更容易学会如何去实现一个类。
从现在开始的所有例子,都可以参照这个思路,先从这个类创建一个对象,然后尝试使用它的方法,之后就将注意力放在它的实现上。
五:面向对象的思考面向过程范式重在设计方法,面向对象范式将数据和方法组合在对象中。面向对象方法结合了面向过程范式的功能以及将数据的操作和操作集成在对象中的特性。
面向过程程序设计中,数据和数据上的操作是分离的,而且这里的方法论是要求给方法发送数据。面向对象程序设计将数据和它们的操作都放在一个对象中,它按照镜像到真实世界的方式组织程序,在真实世界中,所有的对象都是和属性及动作相关联的。
使用对象提高了软件的可重用性,并且使程序更易于开发和维护。Java程序设计涉及的是对对象的思考,一个Java程序可以看做是一个相互操作的对象集合。
六:对象的组合
一个对象可以包含另一个对象,这两个对象之间的关系称为组合。组合实际上是聚集关系的一种特殊形式。聚集模拟了具有关系,表示两个对象之间的归属关系。
归属关系中的所有者称为聚集对象,而它的类称为聚集类;从属对象称为被聚集对象,它的类称为被聚集类。
一个对象可以被几个其他聚集对象所拥有。如果一个对象只归属一个聚集对象,那么它和聚集对象之间的关系称为组合关系。
UML类图中,用实心的菱形表示组合关系,空心表示聚集关系。
七:对象的设计原则
1)内聚性
类应该描述一个单一的实体,而所有的类操作应该在逻辑上相互配合,支持一个连贯性的目标。如果一个实体担负太多的职责,就应该按各自的职责分成几个类。例如,String类、StringBuilder类、StringBuffer类都用于处理字符串,但是它们的职责不同。String类处理不可变字符串,StringBuilder类创建可变字符串,StringBuffer与StringBuilder类似,只是它还包含更新字符串的同步方法。
2)一致性
遵循标准Java程序设计风格和命名习惯。给类、数据域和方法选择有信息量的名字。流行的风格是将数据声明置于构造方法之前,并且将构造方法置于方法之前。
选择名字要保持一致,给类似的操作选择不同的名字并非好习惯。
一般来说,应该一律提供一个默认实例的公共无参构造方法。如果一个类不支持无参构造方法,要用文档写明原因。如果没有显示定义构造方法,就假定有一个空方法体的公共默认无参构造方法。
如果不像让用户创建类的对象,可以在类中声明一个私有的构造方法,如Math类和GuessDate类一样。
3)封装性
一个类应该使用private修饰符隐藏其数据,以免用户直接访问它。这使得类更易于维护。
如果想让数据可读,只需提供get方法;想让数据可更新,只需提供set方法。
4)清晰性
为使设计清晰,内聚性、一致性、封装性都是很好的设计原则。除此之外,类应该有一个很清晰的合约,易于解释和理解。
类应该没有对用户使用目的和使用时间进行限制,设计属性以容许用户按值的任何顺序和任何组合来设置,所设计的方法实现的功能应该与他们出现的顺序无关。
方法应该在不产生混淆的情况下凭直觉来定义。
不应该声明一个来自其他数据域的数据域。
5)完整性
类经常是为许多不同用户的使用而设计的。为了能够在一个广泛的应用中使用,一个类应该通过属性和方法提供多种方案以适应用户的不同需求。如为满足不用应用需求,String类包含了40多种很实用的方法。
了解了以上几个概念,让我们来看几个范例的练习,以增进理解。
范例一:贷款类的实现,并编写测试程序。程序要求用户输入年利率、贷款年限、贷款总额;程序输出每月还款额以及总共需要还款数目。
实现的源代码如下所示:
package Blog;
import java.util.Date;
import java.util.Scanner;
import javax.swing.*;
public class blogTryProject{
public static void main(String[]args){
TestLoanClass tlc = new TestLoanClass();
tlc.main(args);
}
}
//贷款类测试类
class TestLoanClass{
public static void main(String[]args){
Scanner input = new Scanner(System.in);
System.out.print("Enter yearly interest rate,for example,8.25: ");
double annualInterestRate = input.nextDouble();
System.out.print("Enter number of years as an integer: ");
int numberOfYears = input.nextInt();
System.out.print("Enter loan amount,for example,120000.95 : ");
double loanAmount = input.nextDouble();
Loan loan = new Loan(annualInterestRate,numberOfYears,loanAmount);
System.out.printf("The loan was created on %s\n"+"The monthly payment is %.2f\n"
+ "The total payment is %.2f\n",loan.getLoanDate().toString(),
loan.getMonthlyPayment(),loan.getTotalPayment());
}
}
//贷款类
class Loan{
private double annualInterestRate;
private int numberOfYears;
private double loanAmount;
private java.util.Date loanDate;
public Loan(){
this(2.5,1,1000);
}
public Loan(double annualInterestRate,int numberOfYears,double loanAmount){
this.annualInterestRate = annualInterestRate;
this.numberOfYears = numberOfYears;
this.loanAmount = loanAmount;
loanDate = new java.util.Date();
}
public double getAnnualInterestRate(){
return annualInterestRate;
}
public void setAnnualInterestRate(double annualInterestRate){
this.annualInterestRate = annualInterestRate;
}
public int getNumberOfYears(){
return numberOfYears;
}
public void setNumberOfYears(int numberOfYears){
this.numberOfYears = numberOfYears;
}
public double getLoanAmount(){
return loanAmount;
}
public void setLoanAmount(double loanAmount){
this.loanAmount = loanAmount;
}
public double getMonthlyPayment(){
double monthlyInterestRate = annualInterestRate / 1200;
double monthlyPayment = loanAmount * monthlyInterestRate /
(1 - (Math.pow(1/(1+monthlyInterestRate), numberOfYears*12)));
return monthlyPayment;
}
public double getTotalPayment(){
double totalPayment = getMonthlyPayment() * numberOfYears * 12;
return totalPayment;
}
public java.util.Date getLoanDate(){
return loanDate;
}
}
实现的源代码如下所示:
package Blog;
import java.util.Date;
import java.util.Scanner;
public class blogTryProject{
public static void main(String[]args){
TestStackOfIntegers tsoi = new TestStackOfIntegers();
tsoi.main(args);
TestCourse tc = new TestCourse();
tc.main(args);
}
}
//堆栈类测试
class TestStackOfIntegers{
public void main(String[]args){
StackOfIntegers stack = new StackOfIntegers();
for(int i = 0;i < 10;i++)
stack.push(i);
System.out.println("堆栈的出栈顺序为:");
while(!stack.empty())
System.out.print(stack.pop()+" ");
System.out.println();
}
}
//堆栈类
class StackOfIntegers{
private int[] elements;
private int size;
public static final int DEFUALT_CAPACITY = 16;
public StackOfIntegers(){
this(DEFUALT_CAPACITY);
}
public StackOfIntegers(int capacity){
elements = new int[capacity];
}
public void push(int value){
if(size >= elements.length){
int[] temp = new int[elements.length * 2];
System.arraycopy(elements, 0, temp, 0, elements.length);
elements = temp;
}
elements[size++] = value;
}
public int pop(){
return elements[--size];
}
public int peek(){
return elements[size - 1];
}
public boolean empty(){
return size == 0;
}
public int getSize(){
return size;
}
}
//选课类测试程序
class TestCourse{
public static void main(String[]args){
Course course1 = new Course("Data Structures");
Course course2 = new Course("Database Systems");
course1.addStudent("Peter Jones");
course1.addStudent("Brain Smith");
course1.addStudent("Anne Kennedy");
course2.addStudent("Peter Jones");
course2.addStudent("Steve Smith");
System.out.println("Number fo students in course1: "+course1.getNumberOfStudents());
String[] students = course1.getStudents();
for(int i = 0;i < course1.getNumberOfStudents();i++)
System.out.print(students[i]+", ");
System.out.println();
System.out.println("Number of students in course2: "+course2.getNumberOfStudents());
String[] studentsOfCourse2 = course2.getStudents();
for(int i = 0;i < course2.getNumberOfStudents();i++)
System.out.print(studentsOfCourse2[i]+", ");
System.out.println();
course1.dropStudent("Peter Jones");
System.out.println("删除Peter Jones之后的学生为:");
System.out.println("Number fo students in course1: "+course1.getNumberOfStudents());
students = course1.getStudents();
for(int i = 0;i < course1.getNumberOfStudents();i++)
System.out.print(students[i]+", ");
System.out.println();
System.out.println("将某门课程清空之后,学生为:");
course2.clear("DataBase Systems");
System.out.print("Number of students in course2: "+course2.getNumberOfStudents());
studentsOfCourse2 = course2.getStudents();
for(int i = 0;i < course2.getNumberOfStudents();i++)
System.out.print(studentsOfCourse2[i]+", ");
}
}
//选课类
class Course{
private String courseName;
private String[] students = new String[10];
private int numberOfStudents;
public Course(String courseName){
this.courseName = courseName;
}
public void addStudent(String student){
if(numberOfStudents > students.length){
String[] newStudents = new String[students.length * 2];
System.arraycopy(students, 0, newStudents, 0, students.length);
students = newStudents;
}
students[numberOfStudents++] = student;
}
public String[] getStudents(){
return students;
}
public int getNumberOfStudents(){
return numberOfStudents;
}
public String getCourseName(){
return courseName;
}
public void dropStudent(String student){
for(int i = 0;i < numberOfStudents;i++){
if(students[i] == student){
if(i == numberOfStudents - 1){
students[i] = "";
numberOfStudents--;
}
else{
for(int j = i;j < numberOfStudents - 1;j++){
students[j] = students[j+1];
}
students[numberOfStudents - 1] = "";
numberOfStudents--;
}
}
}
}
public void clear(String courseName){
for(int i = 0;i < numberOfStudents;i++){
students[i] = "";
numberOfStudents--;
}
numberOfStudents--;//由于numberOfStudents是自加统计的,所以要多减一次
}
}
运行效果如图所示:
实现的源代码如下所示:
package Blog;
import java.util.Date;
import java.util.Scanner;
public class blogTryProject{
public static void main(String[]args){
TestATM tatm = new TestATM();
tatm.main(args);
}
}
//ATM机模拟
class TestATM{
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
Acount[] atm = new Acount[10];
//初始化对象数组的值为100
for(int i = 0;i < atm.length;i++){
atm[i] = new Acount();
atm[i].setBalance(100);
}
//ATM操作
while(true){
System.out.print("请输入一个账户ID, (eg:0~9) : ");
int numberOfId = input.nextInt();
int numberOfChoice = 0;
//操作选择
while(true){
System.out.println("Main menu");
System.out.println("1:查询余额");
System.out.println("2:取款");
System.out.println("3:存款");
System.out.println("4:退出此次ID的卡");
System.out.println("5:退出ATM");
System.out.println("6:转账");
System.out.print("选择一个操作,(1~6): ");
numberOfChoice = input.nextInt();
//输入合法性判断
while(true){
if(numberOfChoice >= 1 && numberOfChoice <= 6)
break;
else{
System.out.print("输入错误,请重新输入操作类型: ");
numberOfChoice = input.nextInt();
}
}
switch (numberOfChoice) {
case 1: {
System.out.println("余额为: "
+ atm[numberOfId].getBalance());
break;
}
case 2: {
System.out.print("输入取款数目: ");
double withDrawNumber = input.nextDouble();
atm[numberOfId].withDraw(withDrawNumber);
break;
}
case 3: {
System.out.print("输入存款数目: ");
double depositNumber = input.nextDouble();
atm[numberOfId].deposit(depositNumber);
break;
}
case 4: {
break;
}
case 6:{
System.out.print("输入要转入的账户,(0~9): ");
int numOfIdIn = input.nextInt();
System.out.print("输入要转入的金额: ");
double numOfIn = input.nextDouble();
atm[numberOfId].withDraw(numOfIn);
atm[numOfIdIn].deposit(numOfIn);
}
}
//结束本个对象ATM或全部ATM操作判断
if(numberOfChoice == 4 || numberOfChoice == 5)
break;
}
//结束ATM判断
if(numberOfChoice == 5){
System.out.println("结束ATM操作");
break;
}
}
}
}
//存取款类
class Acount{
private int id;
private double balance;
private double annualInterestRate;
private Date dateCreated;
Acount(){
id = 0;
balance = 0;
dateCreated = new Date();
}
Acount(int newID,double newBalance){
id = newID;
balance = newBalance;
dateCreated = new Date();
}
public int getId(){
return id;
}
public void setId(int newID){
id = newID;
}
public double getBalance(){
return balance;
}
public void setBalance(double newBalance){
balance = newBalance;
}
public double getAnnualInterestRate(){
return annualInterestRate;
}
public void setAnnualInterestRate(double newRate){
annualInterestRate = newRate;
}
public String getDateCreated(){
return dateCreated.toString();
}
public double getMonthlyInterestRate(){
return annualInterestRate / 12.0;
}
public void withDraw(double outBalance){
balance -= outBalance;
}
public void deposit(double inBalance){
balance += inBalance;
}
}