您的位置:首页 > 编程语言 > Java开发

java基础——面向对象设计方法

2013-07-12 14:15 465 查看
面向对象设计方法

前面介绍了面向对象技术的两个最基本、最重要的概念——类和对象,下面介绍一下面向对象技术的设计思路。

对于初学者来说,面向对象是学习Java 语言时的第一个难点,其实面向对象只是一种思考问题的方式,或者理解为组织数据和功能的方式而已,当系统中的数据和功能都实现以后,按照数据和功能的相关性进行组织。在使用面向对象技术设计项目时,

一般的步骤如下:

1、抽象类

2、抽象类的属性和方法

3、通过对象的关联构造系统

其中步骤1 和2 是设计需要实现的功能,步骤3 更多的和业务逻辑相关,体现设计的结构不是很多。

抽象类

抽象类最基本的方式是——将名词转换为类。

在一个系统中会存在很多的名词,如果这些名词需要频繁的在系统中进行使用时,则可以将这些名词抽象成类,便于后续的时候。例如在一个学生成绩管理系统中,则名词:学生、课程等则可以抽象成类。而实际在抽象时,由于有一定的主观性,所以在系统设计时,不同人设计的系统会存在一些不同。

抽象类的属性和方法

把系统中的类抽象出来了以后,就可以设计每个类的内部结构了,而每个类内部最重要的结构就是属性和方法了。

抽象属性最基本的方式是——将数据抽象为属性。

抽象方法最基本的方式是——将功能抽象为方法。

在一个类内部会存在很多的数据和功能,在实际抽象时,只需要抽象自己需要的数据和功能即可。例如在学生成绩管理系统中,学生的姓名、班级和各个科目的成绩都是系统中需要使用的数据,而学生的家庭住址,联系电话则不会必须的属性,可以根据实际的需要取舍数据的值。抽象功能时,只需要把该类在系统中需要执行的动作提取出来,然后使用方法的语法进行描述即可。当然,面向对象设计还涉及很多其它的知识,这里讲解的只是一些基础的入门知识,更多的有关面向对象的知识可以阅读关于面向对象技术的专门书籍,并且在项目开发中逐步体会这些知识。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------

面向对象三大特性

面向对象技术在实际开发中有很多的特性,总结起来最核心的特性主要有三个:封装、继承和多态。

封装性

封装性指在实际实现时,将复杂的内部结构隐藏起来,并为这组复杂的结构取一个统一的名称进行使用。在现实世界中,大量的存在封装的例子,例如电脑的硬盘,将多组复杂的线路和存储信息的磁片封装起来,并将该组结构取名为硬盘,以后就可以使用硬盘来代表该结构,而不需要更多的了解内部的信息。在面向对象技术中,类是典型的封装性的体现,类将一组属性和功能组合成一个统一的结构,并使用类名来代表该结构。封装性的最大优势在于隐藏每个类的内部实现(内部结构),从而既方便项目的分解也降低了项目的难度。

例如以设计汽车为例,我们可以把汽车看作是软件开发中的整个项目,在实际设计时,首先可以将设计汽车分解为设计汽车的每个组件,然后具体对每个组件进行设计,而组件和组件的设计之间关联性很小,例如设计发动机的设计小组不需要很详细的了解轮胎设计小组的工作。而这里的每个组件可以看作实际面向对象设计中的类,每个类都把自己的内部实现隐藏起来,只通过名称使其它类了解该类的作用,并开放一些基本的功能供其它的类使用即可。这样可以在实际设计时,每个类都更注重自身的实现,而对于其它类的实现不需要深入了解,这样可以在总体上降低交流的频率,从而降低整个项目的复杂度。

通常情况下,一般把类和类之间的关联性又称作耦合性,类和类之间的关联性比较低也称作耦合性比较低。在实际设计项目时,低耦合的项目是程序设计人员设计系统的目标之一。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------

继承性

在我们认知现实世界时,一般会把事物进行分类,而每一类内部又划分出很多的小类,生物学中将该方式体现的很彻底。例如猩猩属于动物中的哺乳类灵长目,这里的动物、哺乳类和灵长目都是一个特定的类别,和以前不同的是这些类别之间存在包含关系(is-a),换句话说,也就是哺乳类是动物类的一种,灵长目是哺乳类的一种。其实在程序设计中,很多设计出来的类也存在这样的包含关系,这样

一个类的内部会包含和其它类类似的特征和属性,如果在设计时可以以另外一个类为基础进行设计,那将是多么激动人心的特性,这个特性就是面向对象设计中的继承性。

在一个项目中,如果类和类之间存储包含关系,即一个类是另外一个类的一种,就可以使用继承。继承性提供了全新的类设计方式,可以充分利用了已有类内部的结构和功能,极大的降低了类内部的代码重复,是设计类的一种显著的变革,对于大型的项目设计十分有用。另外很多技术的应用中也包含大量的继承成分,使整个技术体系比较固定。

继承语法

在Java 语言中,继承的语法格式比较简单,如下所述:

访问控制符[修饰符] class 类名extends 父类名{

……

}

在声明类时,声明该类的继承关系,使用extends 关键字实现,其中extends 关键字前面是声明出的新类名,extends 关键字后面的类名是被继承的类名,要求被继承的类名已存在。Java 语言采用的是单重继承,也就是说一个类只能有一个直接父类。在类声明时,如果没有使用extends 关键字声明父类,则自动继承Object 类。说明:Object 类是系统提供的类,该类已存在。

示例代码如下:

//Animal.java

public class Animal {

/**类型名称*/

String name;

/**移动方式*/

int moveType;

}

//Mammalia.java

public class Mammalia extends Animal{

/**哺育时间*/

int fosterTime;

}

这里Mammalia 类就是Animal 类的子类,Animal 类就是Mammalia 类的父类,子类和父类具有相对性,正如一个祖孙三代的家庭内部,每个人相对于不同的人扮演不同的角色一样。同时类和类之间的继承具备传递性,就如现实中的血缘关系一样。

继承说明

两个类之间如果存在了继承关系以后,将带来哪些不同呢?下面依次来进行说明:

★子类拥有父类的所有属性

子类中继承父类中所有的属性,在父类中声明的属性在子类内部可以直接调用。

说明:如果访问控制符限制则无法访问。

★子类拥有父类的所有方法

子类中继承父类中所有的方法,在父类中声明的方法在子类内部可以直接调用。

说明:如果访问控制符限制则无法访问。

★子类不拥有父类的构造方法

子类不继承父类的构造方法,如果需要在子类内部使用和父类传入参数一样的构造方法,则需要在子类内部重新声明这些构造方法。

★子类类型是父类类型

子类类型的对象可以自动转换为父类类型的对象,父类类型的对象则需要强制转换为子类的对象,转换的语法个基本数据类型转换的语法相同。

方法覆盖

前面介绍了继承的一些基础知识,现在介绍一些在使用继承时需要注意的问题。熟悉这些问题将更好的解决项目中的实际问题。

例如在实际的游戏中,会按照怪物的种类实现设计。首先设计一个基础类Monster,然后按照怪物类别设计Monster 的子类,如Boss、NormalMonster等。则在实际实现时,每个怪物都有移动(move)的功能, 但是在Boss 和NormalMonster 的移动规则存在不同。这样就需要在子类的内部重新编写移动的功能,从而满足实际的移动要求。该示例的实现代码如下:

//Monster.java

public class Monster{

public void move(){

//移动功能

}

}

//Boss.java

public class Boss extends Monster{

public void move(){

//Boss 类的移动规则

}

}

//NormalMonster.java

public class NormalMonster extends Monster{

public void move(){

// NormalMonster 类的移动规则

}

}

这样在Monster 的每个子类内部都重新书写了move 方法的功能,这种在子类内部重新父类中的方法的语法现象,称作方法覆盖(override)。方法覆盖在实际中保持了类的结构的统一,在实际使用时将极大的方便程序开发人员的使用,使项目的整体结构保持统一,便于项目的维护。在使用子类的对象时,子类内部的方法将覆盖从父类继承过来的方法,也就是说子类的对象调用的是子类的功能方法,而不是父类的方法。在进行方法覆盖时,子类内部的方法和父类的方法声明相同,而且子类方法的限制不能比父类的方法严格。例如不能使用比父类限制更大的访问控制符或抛出比父类更多的异常等,这个在实际使用方法覆盖时需要特别的注意。在实际的项目中大量的存在需要在子类内部重写父类的功能方法的地方,恰当的使用方法覆盖将为项目开发带来很大的便利。

需要注意的问题

除了方法覆盖以外,在实际使用继承时还有很多需要注意的问题。下

面就这些问题进行一一说明。

①:属性覆盖没有必要

方法覆盖可以重写对应的功能,在实际继承时在语法上也支持属性覆盖(在子类内部声明和父类属性名相同的属性),但是在实际使用时修改属性的类型将导致类结构的混乱,所以在继承时不能使用属性覆盖。

②:子类构造方法的书写

该项是继承时书写子类最需要注意的问题。在子类的构造方法内部必须调用父类的构造方法,为了方便程序员进行开发,如果在子类内部不书写调用父类构造方法的代码时,则子类构造方法将自动调用父类的默认构造方法。而如果父类不存在默认构造方法时,则必须在子类内部使用super 关键字手动调用,关于super关键字的使用将在后续进行详细的介绍。

说明:子类构造方法的参数列表和父类构造方法的参数列表不必完全相同。

③:子类的构造过程

在构造子类时由于需要父类的构造方法,所以实际构造子类的过程就显得比较复杂了。其实在实际执行时,子类的构造过程遵循:首先构造父类的结构,其次构造子类的结构,无论构造父类还是子类的结构,都是首先初始化属性,其次执行构造方法。则子类的构造过程具体如下:

如果类A 是类B 的父类,则类B 的对象构造的顺序如下:

★ 类A 的属性初始化

★ 类A 的构造方法

★ 类B 的属性

★ 类B 的构造方法

由于任何一个类都直接或间接继承自Object 类,所以Object 类的属性和构造方法都是首先执行的。

④:不要滥用继承

在实际的项目设计中,继承虽然很经常使用,但是还是不能滥用,使用继承的场合以及相关问题参看下面的说明。

如何设计继承

在实际的项目中,类和类之间的关系主要有三种:

第一种:没有关系

项目中的两个类之间没有关联,不需要进行消息传递,则这两个类之间就没有关系,可以互相进行独立的设计。

第二种:使用关系(has-a)

如果一个类的对象是另外一个类的属性,则这两个类之间的关系是使用关系。例如把房屋(House)看作是一个类,把门(Door)看成另外一个类,则房屋有一个门,代码的实现如下:

//House.java

public class House{

public Door door;

}

//Door.java

public class Door{

}

则这里Door 的对象是House 类的属性,则Door 和House 类之间的关系就是使用关系,House 使用Door 类来制作自身。使用关系提供了使用已有类来声明新类的方式,可以以组合的方式来构建更复杂的类,这是项目中使用类的常见方式之一。判断是否是使用关系的依据就是:has-a,一个类具备另外一个类的对象,例如一个House 有一个门。

第三种:继承关系(is-a)

如果一个类是另外一个类的一种,也就是在分类上存在包含关系,则应该使用继承来实现。例如Boss 是怪物的一种,则使Boss 继承Monster 类。下面简单介绍一些项目中继承的设计方法。

在实际设计继承时,一般有两种设计的方法:

第一:自上而下的设计

在实际设计时,考虑类的体系结构,先设计父类,然后根据需要来增加子类,并

在子类的内部实现或添加对应的方法。

第二:自下而上的设计

在实际设计时,首先不考虑类的关系,每个类都分开设计,然后从相关的类中把重复的属性和方法抽象出来形成父类。

对于初学者来说,第二种设计方式相对来说比较容易实现,所以一般初学者都按照第二种设计方式进行设计,设计完成以后再实现成具体的代码。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

多态性

多态性是面向对象技术中最灵活的特性,主要是增强项目的可扩展性,提高代码的可维护性。多态性依赖继承特性,可以把多态理解为继承性的扩展或者深入。在这里把多态性分为两方面来进行介绍,对象类型的多态和对象方法的多态。

为了方便后续的讲解,首先给出一个继承结构的示例。

//文件名:SuperClass.java

public class SuperClass{

public void test(){

System.out.println(“SuperClass”);

}

}

// 文件名:SubbClass1.java

public class SubbClass1 extends SuperClass{

public void test(){

System.out.println(“SubbClass1”);

}

}

// 文件名:SubbClass2.java

public class SubbClass2 extends SuperClass{

public void test(){

System.out.println(“SubbClass2”);

}

}

在该示例代码中,SubbClass1 和SubbClass2 是SuperClass 的子类,并且在子类的内部都覆盖父类中的test 方法。由于这三个类中都书写构造方法,则按照默认构造方法的约定,每个类中都会被自动添加一个默认的构造方法。

对象类型的多态

对象类型的多态是指声明对象的类型不是对象的真正类型,而对象的真正类型由创建对象时调用的构造方法进行决定。例外,按照继承性的说明,子类的对象也是父类类型的对象,可以进行直接赋值。

例如如下代码:

SuperClass sc = new SubbClass1();

这里声明了一个SuperClass 类型的对象sc,然后使用SuperClass 的子类SubbClass1 的构造方法进行创建,因为子类类型的对象也是父类类型的对象,所以创建出来的对象可以直接赋值给父类类型的对象sc。除了对象的赋值以外,另外一个更重要的知识是sc 对象虽然使用SuperClass 声明的类型,但是内部存储的却是SubbClass1 类型的对象。这个可以Java 语言的中instanceof运算符进行判断。

instanceof 是一个运算符,其作用是判断一个对象是否是某个类类型的对象,如果成立则表达式的值为true,否则为false。

语法格式如下:

对象名 instanceof 类名

需要注意的是:这里的类名必须和声明对象时的类之间存储继承关系,否则将出现语法错误。

测试类型的代码如下:

/**

* 测试对象类型的多态

*/

public class TestObjectType {

public static void main(String[] args) {

SuperClass sc = new SubbClass1();

boolean b = sc instanceof SuperClass;

boolean b1 = sc instanceof SubbClass1;

System.out.println(b);

System.out.println(b1);

}

}

该测试程序的输出结果是:

true

true

由程序运行结果可以看出,sc 既是SuperClass 类型的对象,也是SubbClass1 类型的对象,而SubbClass1 的类型被隐藏起来了,这就是对象的多态。其实sc 对象不仅仅在类型上是SubbClass1 类型的,其存储的内容也是SubbClass1 的内容,具体参看后面介绍的对象方法的多态。对象类型的多态有很多的用途,极大的方便了对象的存储和传递,使代码很方便的进行扩展,对于已有代码不产生影响。下面介绍两个基本的使用。

①: 对象的存储

在存储一系列不同子类的对象时,可以使用父类的结构来进行声明,这样可以方便数据的存储,例如需要存储多个SubbClass1 和SubbClass2 的对象时,则可以声明一个SuperClass 类型的数组进行存储,示例代码如下:

SuperClass sc[] = new SuperClass[3];

sc[0] = new SubbClass1();

sc[1] = new SubbClass2();

sc[2] = new SubbClass1();

则这里的数组sc,可以存储各个类型子类的对象,而数组中每个元素的值都是存储的对应子类的对象,而只是在名义上的类型(语法上的类型)是SuperClass类型的,这样将方便程序的控制,当增加新的子类类型时,已有的代码不需要进行改造就可以自动适应新的子类的结构。

例如新增了一个SuperClass 的子类SubbClass3,则该数组的代码可以修改成如下:

SuperClass sc[] = new SuperClass[3];

sc[0] = new SubbClass1();

sc[1] = new SubbClass2();

sc[2] = new SubbClass3();

其它的代码都需要进行修改,就可以适应新的结构,这是多态性最主要的用途。

②:对象的传递

在方法的传入参数传递,以及返回值处理方面都从对象类型的多态中受益。在向方法中传入参数时,如果该方法需要处理各个子类的对象,则只需要书写一个接受父类类型对象的方法即可。例如:

public void testObjectTypeMethod(SuperClass sc){}

则该在调用该方法时,可以传入SuperClass 的对象,也可以传入其子类的对象,如果传入的是子类的对象,则子类对象中的内容不会丢失。例如调用的示例代码如下:

SuperClass sc = new SuperClass();

SubbClass1 sc1 = new SubbClass1();

SubbClass2 sc2 = new SubbClass2();

testObjectTypeMethod(sc);

testObjectTypeMethod(sc1);

testObjectTypeMethod(sc2);

这里说明的只是调用时的语法结构,这样的特性将使我们只需要书写一个方法,就可以处理所有子类的对象,简化代码的书写,降低代码的重复,从而降低维护的难度。另外,方法的返回值也可以利用到该特性,例如如下方法:

public SuperClass testObjectTypeMethod2(){}

则在该方法的内部,既可以返回SuperClass 类型的对象,也可以返回其子类的对象,也能简化代码的书写,便于代码的阅读和维护。

关于对象类型的多态,就简单的说明这么多,具体在项目中如何进行使用,还需要一定的技巧和方法。

对象方法的多态

对象方法的多态基于方法的覆盖,也就是该对象调用的方法具体是子类的方法还是父类的方法,由创建对象时使用的构造方法决定,而不是由声明对象时声明的类型决定。

示例代码如下:

/**

* 测试对象方法的多态

*/

public class TestObjectMethod {

public static void main(String[] args) {

SuperClass sc = new SuperClass();

SubbClass1 sc1 = new SubbClass1();

SubbClass2 sc2 = new SubbClass2();

SuperClass sc3 = new SubbClass1();

testObjectTypeMethod(sc);

testObjectTypeMethod(sc1);

testObjectTypeMethod(sc2);

testObjectTypeMethod(sc3);

}

public static void testObjectTypeMethod(SuperClass sc){

sc.test(); //调用被覆盖的方法

}

}

该代码的执行结果如下:

SuperClass

SubbClass1

SubbClass2

SubbClass1

则从代码的执行结果看,虽然testObjectTypeMethod 方法接收的是SuperClass 类型的对象,但是传入子类对象时,子类对象的内容没有丢失,所以在调用test 方法时,还是调用的对应对象中对应的test 方法。这样就在功能上实现了对象的传递,从而保留了对象的内容,极大的方便了代码的扩展性。但是,由于Java 在执行程序时,在程序运行的过程中,需要判断对象调用的具体是父类的方法还是子类的方法,所以程序的执行速度会稍微有所降低。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐