钟鼎 2020-06-05
@
在目前的软件开发领域有两种主流的开发方法:结构化开发方法和面向对象开发方法。早期的编程 语言如C、Basic、Pascal等都是结构化编程语言;随着软件开发技术的逐渐发展,人们发现面向对象可 以提供更好的可重用性、可扩展性和可维护性,于是催生了大量的面向对象的编程语言,如Java、 C#和 Ruby 等。
结构化程序设计方法主张按功能来分析系统需求,其主要原则可概括为自顶向下、逐步求精、模块化等。结构化程序设计首先采用结构化分析(Structured Analysis, SA)方法对系统进行需求分析,然后使用结构化设计(Structured Design, SD)方法对系统进行概要设计、详细设计,最后釆用结构化编 程(Structured Program, SP)方法来实现系统。使用这种SA、SD和SP的方式可以较好地保证软件系 统的开发进度和质量。
因为结构化程序设计方法主张按功能把软件系统逐步细分,因此这种方法也被称为面向功能的程序设计方法;结构化程序设计的每个功能都负责对数据进行一次处理,每个功能都接受一些数据,处理完后输出一些数据,这种处理方式也被称为面向数据流的处理方式。
结构化程序设计里最小的程序单元是函数,每个函数都负责完成一个功能,用以接收一些输入数据, 函数对这些输入数据进行处理,处理结束后输出一些数据。整个软件系统由一个个函数组成,其中作为 程序入口的函数被称为主函数,主函数依次调用其他普通函数,普通函数之间依次调用,从而完成整个软件系统的功能。
每个函数都是具有输入、输出的子系统,函数的输入数据包括函数形参、全局变量和常量等,函数的输出数据包括函数返回值以及传出参数等。结构化程序设计方式有如下两个局限性。
采用结构化方式设计的软件系统,整个软件系统就由一个个函数组成,这个软件的运行入口往往由一个“主函数”代表,而主函数负责把系统中的所有函数“串起来”。
面向对象是一种更优秀的程序设计方法,它的基本思想是使用类、对象、继承、封装、消息等基本概念进行程序设计。它从现实世界中客观存在的事物(即对象)出发来构造软件系统,并在系统构造中尽可能运用人类的自然思维方式,强调直接以现实世界中的事物(即对象)为中心来思考,认识问题, 并根据这些事物的本质特点,把它们抽象地表示为系统中的类,作为系统的基本构成单元(而不是用一 些与现实世界中的事物相关比较远,并且没有对应关系的过程来构造系统),这使得软件系统的组件可 以直接映像到客观世界,并保持客观世界中事物及其相互关系的本来面貌。
从图中看出,面向对象的软件系统由多个类组成,类代表了客观世界中具有某种特征的一类事物,比如汽车,汽车内部存在一些状态数据(Field),比如颜色、款式。
面向对象的语言不仅使用类来封装一类事物的内部状态数据,这种状态数据就对应于图中的Field(成员变量),而且类会提供操作这些状态数据的方法,还会为这类事物的行为特征提供相应的实现,这种实现也是方法。因此可以得到如下基本等式:
假设需要组装一台电脑,如果拿到手的是主板、CPU、内存条、硬盘等这种大粒度的组件,可以很轻松地组装成一台电脑;但如果拿到手的是一些二极管、三极管、 集成电路等小粒度的组件,要想把它们组装成电脑,就不简单了。如果把数据以及操j 作数据的方法都封装成对象,这就相当于提供了大粒度的组件,因此编程更容易。
从面向对象的眼光,开发者希望从自然的认识、使用角度来定义和使用类。也就是说,开发者希望直接对客观世界进行模拟:
不仅如此,面向对象程序设计与人类习惯的思维方法有较好的一致性,比如希望完成“猪八戒吃西瓜”:
在面向过程的程序世界里,一切以函数为中心,函数最大,因此这件事情会用如下语句来表达:
吃(猪八戒,西瓜);
在面向对象的程序世界里,一切以对象为中心,对象最大,因此这件事情会用如下语句来表达:
猪八戒.吃(西瓜);
对比两条语句不难发现,面向对象的语句更接近自然语言的语法:主语、谓语、宾语一目了然,十 分直观,因此程序员更易理解。
封装指的是将对象的实现细节隐藏起来,然后通过一些公用方法来暴露该对象的功能;
继承是面向向对象实现软件复用的重要手段,当子类继承父类后,子类作为一种特殊的父类,将直接获得父类的属性和方法;
多态指的是子类对象可以直接赋给父类变量,但运行时依然表现岀子类的行为特征,这意味着 同一个类型的对象在执行同一个方法时,可能表现出多种行为特征。
除此之外,抽象也是面向对象的重要部分,抽象就是忽略一个主题中与当前目标无关的那些方面, 以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是考虑部分问题。例如, 需要考察Person对象时,不可能在程序中把Person的所有细节都定义出来,通常只能定义Person的部 分数据、部分行为特征——而这些数据、行为特征是软件系统所关心的部分。
虽然抽象是面向对象的重要部分,但它不是面向对象的特征之一,因为所有的编程语都需要抽象.当开发者进行抽象时应该考虑哪些特征是软件系统所需要的,那么这些特征就应该使用程序记录并表现出来。因此,需要抽象哪些特征没有必然的规定,而是取决于软件系统的功能需求。
类( class) 是对某种事物的抽象,是构造对象的模板或蓝图。我们可以将类想象成月饼模具,将对象想象为月饼。由类构造(construct) 对象的过程称为创建类的实例 (instance )。
语法:
[修饰符]class类名{ 零个到多个构造器定义.. 零个到多个成员变量 零个到多个方法... }
说明:
public class Dog{ //成员变量 String breed; private int age; String color; //方法 void barking(){ } public void hungry(){ } private void sleeping(){ } }
一个类可以包含以下类型变量:
static是一个特殊的关键字,它可用于修饰方法、成员变量等成员。static修饰的成员表明它属于这个类本身,而不属于该类的单个实例,因为通常把static修饰的成员变量和方法也称为类变量、类方法。 不使用static修饰的普通方法、成员变量则属于该类的单个实例,而不属于该类。因为通常把不使用static 修饰的成员变量和方法也称为实例变量、实例方法。
由于static的英文直译就是静态的意思,因此有时也把static修饰的成员变量和方法称为静态变量和静态方法,把不使用static修饰的成员变量和方法称为非静态变量和非静态方法。静态成员不能直接访问非静态成员。提示:虽然绝大部分资料都喜欢把static称为静态,但实际上这种说法很模糊,完全无法说明static的真正作用。static的真正作用就是用于区分成员变量、方法、内部类、初始化块
每个类都有构造方法。如果没有显式地为类定义构造方法,Java编译器将会为该类提供一个默认构造方法。
在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法。
public class Puppy{ //无参构造方法 public Puppy(){ } //有参构造方法 public Puppy(String name){ // 这个构造器有一个参数:name } }
对象是类的一个实例。
对象的三个主要特性:
对象是根据类创建的。在Java中,使用关键字new来创建一个新的对象。创建对象需要以下三步:
public class Puppy{ public Puppy(String name){ //这个构造器仅有一个参数:name System.out.println("小狗的名字是 : " + name ); } public static void main(String[] args){ // 下面的语句将创建一个Puppy对象 Puppy myPuppy = new Puppy( "tommy" ); } }
public class Puppy{ int age; String name; public Puppy(String name){ // 这个构造器有一个参数:name this.name=name; System.out.println("小狗的名字是 : " + name ); } //无参构造方法 public Puppy(){ } public void setAge( int age ){ this.age = age; } public int getAge( ){ System.out.println("小狗的年龄为 : " + puppyAge ); return puppyAge; } public static void main(String[] args){ /* 创建对象 */ Puppy myPuppy = new Puppy( "tommy" ); /* 通过方法来设定age */ myPuppy.setAge( 2 ); /* 调用另一个方法获取age */ myPuppy.getAge( ); /*可以像下面这样访问成员变量 */ System.out.println("变量值 : " + myPuppy.puppyAge ); } }
创建对象语句:
Puppy p = new Puppy( "tommy" );
在这行代码中实际产生了两个东西:一个是p变量, 一个是Puppy对象。
从Puppy类定义来看,Puppy对象应包含1个实例变量,而变量是需要内存来存储的。因此,当创建Puppy对象时,必然需要有对应的内存来存储Puppy对象的实例变量。
从图中可以看出,Puppy对象由多块内存组成,不同内存块分别存储了 Puppy对象的不同成员变量。当把这个Puppy对象赋值给一个引用变量时,系统如何处理呢?难道系统会把这个Puppy对象 在内存里重新复制一份吗?显然不会,Java没有这么笨,Java让引用变量指向这个对象即可。也就是说,引用变量里存放的仅仅是一个引用,它指向实际的对象。
与前面介绍的数组类型类似,类也是一种引用数据类型,因此程序中定义的Puppy类型的变量实 际上是一个引用,它被存放在栈(stack)内存里,指向实际的Puppy对象;而真正的Puppy对象则存放在堆(heap) 内存中。图5.2显示了将Person对象赋给一个引用变量的示意图。
栈内存里的引用变量并未真正存储对象的成员变量,对象的成员变量数据实际存放在堆内存里;而引用变量只是指向该堆内存里的对象。从这个角度来看,引用变量与C语言里的指针很像,它们都是存储一个地址值,通过这个地址来引用到实际对象。实际上,Java里的引用就是C里的指针,只是Java 语言把这个指针封装起来,避免开发者进行烦琐的指针操作。
当一个对象被创建成功以后,这个对象将保存在堆内存中,Java程序不允许直接访问堆内存中的对象,只能通过该对象的引用操作该对象。也就是说,不管是数组还是对象,都只能通过引用来访问它们。
在示例中,p引用变量本身只存储了一个地址值,并未包含任何实际数据,但它指向实际的Person 对象,当访问p引用变量的成员变量和方法时,实际上是访问p所引用对象的成员变量和方法。
不管是数组还是对象,当程序访问引用变量的成员变量或方法时,实际上是访问该引 变量所引用的数组、对象的成员变量或方法。
堆内存里的对象可以有多个引用,即多个引用变量指向同一个对象,如:
//将p变量的值赋值给p2变量 Puppy p2 = p;
上面代码把p变量的值赋值给p2变量,也就是将p变量保存的地址值赋给p2变量,这样p2变量和 p变量将指向堆内存里的同一个Puppy对象。不管访问p2变量的成员变量和方法,还是访问p变量的成员变量和方法,它们实际上是访问同一个Puppy对象的成员变量和方法,将会返回相同的访问结果。
如果堆内存里的对象没有任何变量指向该对象,那么程序将无法再访问该对象,这个对象也就变成 了垃圾,Java的垃圾回收机制将回收该对象,释放该对象所占的内存区。
因此,如果希望通知垃圾回收机制回收某个对象,只需切断该对象的所有引用变量和它之间的关系 即可,也就是把这些引用变量赋值为null。
Java提供了一个this关键字,this关键字总是指向调用该方法的对象。根据this出现位置的不同, this作为对象的默认引用有两种情形。
this关键字最大的作用就是让类中一个方法,访问该类里的另一个方法或实例变量。
public class DogTest{ //定义一个jump()方法 public void jump(){ System.out.println("正在执jump方法”); } //定义一个run()方法,run()方法需要借助jump()方法 public void run()( //使用this引用调用run()方法的对象 this.jump(); System.out.println("正在执行 run 方法”); } }
大部分时候,普通方法访问其他方法、成员变量时无须使用this前缀,但如果方法里有个局部变量 和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用this前缀。
Java 允许使用包( package ) 将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。
标准的 Java 类库分布在多个包中,包括 java.lang、java.util 和java.net 等。标准的 Java包具有一个层次结构。如同硬盘的目录嵌套一样,也可以使用嵌套层次组织包。所有标准的
Java 包都处于java 和 javax 包层次中。
使用包的主要原因是确保类名的唯一性。假如两个程序员不约而同地建立了 Employee类。只要将这些类放置在不同的包中, 就不会产生冲突。事实上,为了保证包名的绝对唯一性, Sun 公司建议将公司的因特网域名(这显然是独一无二的) 以逆序的形式作为包名,并且对于不同的项目使用不同的子包.
从编译器的角度来看, 嵌套的包之间没有任何关系。例如,java.util 包与java.util.jar 包毫无关系。每一个都拥有独立的类集合。
一个类可以使用所属包中的所有类, 以及其他包中的公有类( public class。) 我们可以采用两种方式访问另一个包中的公有类。
第一种方式:在每个类名之前添加完整的包名。
例如:
java.tiie.LocalDate today = java.tine.Local Date.now();
第二种方式:
可以使用 import 语句导人一个特定的类或者整个包。import 语句应该位于源文件的顶部
(但位于 package 语句的后面)。
例如, 可以使用下面这条语句导人 java.util 包中所有的类。
import java.util .*;
然后, 就可以使用
LocalDate today = Local Date.now();
import java.time.LocalDate;
参考
【1】:《Java疯狂讲义》
【2】:《Java核心技术 卷一》
【3】:https://www.runoob.com/java/java-object-classes.html
class Singleton: def __new__: # 关键在于这,每一次实例化的时候,我们都只会返回这同一个instance对象 if not hasattr: cls.instance =