第五课类与面向对象编程1-2

四、面向对象编程

1. 面向对象编程

  讲面向对象编程之前,要先概括一下在使用类之前的编程思维方式,这也有一个名称,叫做“结构化编程”。结构化编程显得很直观,就是不断地根据需要来声明变量,再对这些变量进行逻辑运算,如果出现了可以复用的代码就将其做成方法供多次使用。如果单个文件太大也可以把一些方法代码移到别的文件中。以这样的方式进行编程,大家可以想象一定会出现一个复杂无比的主程序逻辑,它要清楚每一个变量数值的真实含义,也要避免混淆不同的变量,还要明白数据与方法之间的对应关系……因此这样的代码在维护起来有巨大的工作量。它遇上需求变化时,会造成灾难。

  面向对象编程,英文是Object Oriented Programming,简称OOP,相信有的同学已经接触过这个概念。这个概念听起来很玄妙,有的教材或书籍把它拔得非常高,也有的称之为“面向对象思想”。一说到“思想”这个词,就显得很有理论高度了。但我更多地愿意从实用的角度给大家讲解面向对象的编程。

  我们引入“类”这个类型的目的是为更好地组织代码,而在这之前我们用的代码组织形式是方法,而类比方法的进步的地方只是把与数据相关性很高的多个方法组织在了一起,这个看似小小的进步却带来了空前的改变。因为我们用“类”这个模型就可以抽象模拟现实生活的一切个体,在一个程序员的眼里:一切皆对象!


我们可以把人看成一个类,拥有相同的组成部分头、身、手、腿……(成员变量)和相同的功能吃、睡、跑、跳……(成员方法)。而具体的每个人小王小李小张小刘则是同一类实例化的对象,他们之间区别的根源就是成员变量数值的不同:这个人肌肉健壮因而跑得快,那个人胃容量大因而吃得多……。大家顺着思路去想,就可以把更多的真实社会的事物也概括进去。比如学校、公司也可以看成类,学校类有哪些成员变量,有哪些成员方法?公司类有哪些成员变量,有哪些成员方法?这些问题同学们可以自己思考思考。这方面的思考是非常有益的,可以提高大家的抽象思维能力,也对于今后同学们在开展自己的项目时进行程序设计很有帮助,同学们可以自己多找找这方面的一些资料,也可以多做做这方面的练习。

  同学们,我一直强调这个课程是实战编程,而我们学习面向对象的目的也是因为它的实用性。面向对象编程,因为类的使用使得程序的组织结构更加合理,逻辑变得简洁明了。用类把变量与相关方法组织起来,它们之间如何运作,外部调用者不用知道。外部调用者只需要知道要什么功能应该调用哪个方法,方法的参数列表与返回值是什么就可以了。如果编程的过程中发现一些问题,对类的内部的具体实现进行了调整,只要方法的声明不变,外部调用代码完全不需要改变。因此,类的封装不仅简化了外部调用的代码,更方便的是把变化封装了起来,给了外部一个稳定的预期。这对于代码的维护是非常方便的,同学们在以后的编程实践中将能越来越强烈地感受到。

  讲了这么多面向对象的好处,大家最重要的还是要知道如何去用。要用类,主要是知道两点:第一点,知道该把什么样的代码放到类里面去。第二点,知道用什么原则来优化类里面的代码。关于第一点,我们这节课的开头就已经向大家详细说明了,如果发现变量与逻辑有强相关性,就可以把它们组织起来封装到类里。关于第二点,对类进行优化的目的是使可维护性增强,也就是方便以后的修改。要做到这一点,我们可以依据“面向对象的五大原则”。

2. 单一职责原则

  面向对象的五大原则分别是“单一职责原则”、“开放封闭原则”、“里氏替换原则”、“依赖倒置原则”、“接口分离原则”,还有说六大原则与七大原则的。因为内容较多,我们这次课不会一起讲完,只讲第一条原则:单一职责原则,英文是Single Responsibility Principle,简称SRP。它的含义是指一个类的职责要单一,不能身兼多职。职责就是这个类要完成的工作。以公司的组织为例,每个公司都有行政部、人事部、财务部、业务部,这些部门最好都分开,因为各有各的专业,每个部门都是单一职责组织结构就很优化。因为职责就是变化的根源,比如国家人事政策发生变化就只要调整人事部的规则,财税政策发生变化只要动财务部。大家要记住职责就是变化的根源,类分得越细因变化带来的代码调整工作量就会越小。在我们前面的编程中,玩家类Player中本来有对扑克牌点数的解释代码,但是这个职责应该是属于扑克牌类Pokers的,因此我就把这部分代码移走了。这样关于扑克牌实现的细节就与Player无关了,这就是依据的单一职责原则。当然这个原则的应用并不用太死板,在代码量较少的情况下也可以把几个职责混在一个类里,这就好比小公司会把行政财务人事合成一个部。等到公司大了,确实有必要再分开也是可以的。

五、类的概念

1. 实例成员与静态成员

  我们在前面写代码的过程中,为了使Player类的职责单一,把需要扑克牌实现细节的方法移到了Pokers类中。同时又因为那几个方法并不需要依赖Pokers类的成员变量就可以完成其功能,因此在方法前加上了static修饰符,使其成为静态方法。由此我们知道类的成员有实例成员与静态成员之分。不仅成员方法前可以加成员变量前也可以,使其成为静态成员变量。public的静态成员,在外部通过“类名.静态成员名”访问。

  静态成员变量是类的各个实例共享的变量,在内存里是同一个空间。而实例成员变量则在实例化对象时会被分配单独的空间。比如,前面我们做的Student类可以加一个静态成员变量className,即班级名。其相互关系如下图所示:


  因为实例成员与静态成员这种实现机制上的差异,就造成了它们在使用上有如下差异:

  1. 实例成员方法中,可以通过“this.”来访问实例成员变量或调用其它实例成员方法,但是不能通过“this.”来访问静态成员。因为this表示的就是“本实例”的意思,也就是在上图中每个实例除了中间共享部分之外的其它部分。在静态方法中不能使用“this.”,因为它与实例无关。

  2. 静态方法可以访问静态变量与其它静态方法,但是静态方法不能访问实例变量与实例方法。

  3. 实例方法可以访问静态变量与静态方法,访问时可以用“类名.名称”,也可以直接用名称,但不能用“this.名称”。

  4. 类的静态变量发生变化,则该类所有的实例都会受到影响。比如Student类里的className,当班级名从“高一(四)班”变成“高二(四)班”时,所有的学生都会受到影响。

  5. 在类的外部访问公共实例成员,需要将类实例化后通过“实例.”来访问。访问公共静态成员,通过“类名.”即可访问,不需要实例化。

  介绍一下编程经验:静态成员方法是非常有用的,可以把一些工具性质的代码很好地组织起来。静态成员变量加上final成为常量,它的值在初始化后不再改变,可以用于对数值进行有意义的命名,这用得很多。不加final的静态成员变量,因为值会发生变化,不好控制,如非必要尽量少用。对静态成员变量进行初始化,要在声明它的时候就进行,而不要在类的构造方法中。因为如果在构造方法中初始化静态成员变量,因为各个实例是共享的静态成员变量,每个实例被创建时都会对静态成员初始化一遍,这样静态成员变量的值就不能保证是自己想要的结果了。

2. 包与引入

  在创建Java类时,会有一个选项要求填写包(package)的名称,如下图:


  包是一个对类进行组织管理的概念。如果在类文件中第一行写的包的名称相同,这些类就属于同一个包。属于同一个包的类,可以相互使用(即将该类声明为变量,进行实例化)。在包外的类,可以使用访问修饰符为public的类,不加public修饰符的类称为默认类,只能在同一个包里使用。类不一定要声明在单独的类文件里,可以多个类在一个文件中,但一个类文件中只能声明一个public类。在类的内部(也就是类的大括号里)也可以声明类,这种嵌套在里面的类称为“内部类”。有的内部类还可以不用起名,称为“匿名内部类”。内部类非常实用,但这个放到后面再讲。

  包的名称常常是分层的,各层之间以.隔开。命名自己的包时可以只用一层的名称(即中间不加.),多层的名称可以对包中的类进行组织,根据各类的作用将类划分到不同的层次中。

  在类中使用不属于本包中的类时,需要在类名前加上包名,如前面所用的java.util.Random,就是加上了包名java.util。如果想省事,可以在本类的声明之前,包名之后的地方用import引入该类。加入import java.util.Random;则在本类中使用Random类就不需要再在前面加包名。但是如果本包中有同名的类,则包名不能省,否则默认是本包中的类。如果引入外部同一个包的类很多,不用逐一import类名,可以使用*号来引入该包中所有类,如import java.util.*;就引入了java.util所有的类。


第一季 零基础学习Android开发

第一课 第一个Android程序(1)

第一课 第一个Android程序(2)

第二课 Java语言基础1(1)

第二课 Java语言基础1(2)

第三课 Java语言基础2-1

第三课 Java语言基础2-2

第四课 Java语言基础3-1

第四课 Java语言基础3-2

第五课 类与面向对象编程1

文章转载自微信公众号:跟陶叔学编程

类似文章