面向对象是一种软件开发的方法,同类的还有面向过程。
面向对象指的是在程序设计中采用Java的封装、继承、多态、六大原则特性来设计代码。
它其实考验的是你审视代码的角度,运用这些特性,可以写出让人赏心悦目、简单易懂的代码。
不运用这些特性当然也可以进行开发。不过代码的可读性、扩展性、灵活性等会大大下降,冗余度、维护成本等会大大上升。
封装
- 概念
在Java中,封装就是将一些通用、常用的功能方法写到一个新类中,那么当我们用到这些功能时,直接去调用这个新类中的方法即可。这就像是有一个人A,他拥有一些技能,当我用到这些技能时,不需要自己去学习这些技能,只需要去找A即可。 - 优点
提高代码的重用,减少重复代码,提高代码的可读性、方便理解。而且封装的思想也对应了Java中一处编程,到处执行的思想。 - 实例
实例就不用多说了,平时写代码我们总会自己创建一个utils包,存放一些自己或者别人写的utils类。
继承
- 概念
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力(来自百度百科)。
这种官方语言太难讲,而且各位看官也看着费劲。我还是直接说自己的理解吧。
首先继承的含义,就是一直我们口中所说的父类(基类)和子类。子类通过关键字extends
继承父类,就可以拥有父类的非私有的属性和方法。
在Java中,继承是单一继承+多层继承的。 - 优点
提高代码的重用,减少重复的代码。增加了软件开发的灵活性。 - 缺点
由于多层继承的存在,所以无限使用继承特性的话,会造成子类的过度冗余。
多态
- 概念
多态指的是同一个方法,会因为对象的不同导致不同的结果。
没错,就是这样!
多态的三要素一定要知道。这个东西理解了自然就记住了。
继承、重写、父类的引用指向子类的对象。
具体含义,还是在后面举个栗子来解释一下。 - 优点
增加了软件开发的灵活性,简化了编程和修改过程。 - 实例
首先我们定义了一个汽车接口Car
,接口中有一个方法用来获取车的类型:public interface Car { String getCarType(); }
接下来,我们创建了兰博基尼,以及五菱宏光实现了这个接口。
五菱宏光:
public class WuLingHongGuang implements Car {
@Override
public String getCarType() {
return "五菱宏光";
}
}
兰博基尼:
public class LanBoJiNi implements Car {
@Override
public String getCarType() {
return "兰博基尼";
}
}
接下来,我们利用多态的特性,来创建并执行接下来的代码:
public static void main(String[] args){
Car car1 = new LanBoJiNi();
Car car2 = new WuLingHongGuang();
System.out.println("车1的类型:"+car1.getCarType());
System.out.println("车2的类型:"+car2.getCarType());
}
可以看到控制台的结果:
车1的类型:兰博基尼
车2的类型:五菱宏光
- 总结
通过实例,再结合多态三要素:继承、重写、父类的引用指向子类的对象。
兰博基尼和五菱宏光实现了Car
接口(继承),重写了Car
中的getCarType()
(重写),接下来最关键的要素,我们用父类的引用,创建子类的对象。
那么接下来,当你去调用getCarType()
时,Java会首先调用子类中的getCarType()
,而不是父类Car
中的。
其实这个实例,并不能帮你很好地理解多态。它只是很生硬地运用了多态的特性。在项目中运用多态非常的重要,这个需要自己实战才能好好理解。
六大原则
六大原则包括:单一职责、开闭、里氏替换、依赖倒置、接口隔离、迪米特。接下来我们一个一个来理解这些原则的思想。
单一职责原则(Single Responsibility Principle)
- 概念
就一个类而言,应该仅有一个引起它变化的原因。
非常容易理解的一个原则,并且非常重要!但这个原则是一个备受争议的原则,和别人争论这个原则,是屡试不爽的。
因为单一职责原则划分界限并不总是那么清晰,更多的时候是根据个人经验来界定的。
开闭原则(Open Close Principle)
- 概念
就一个类或方法而言,应该对于扩展是开放的,对于修改是关闭的。
软件也有自己的生命周期,越往后迭代,代码越多,冗余度也会随之提升,维护成本也就越来越高,可能一次不经意地bug修改就会破坏之前已经通过测试的代码。因此,当软件需要变化时,我们应该通过扩展的方式去实现,而不是通过修改原有代码来实现。
当然,这是理想愿景,在实际开发中往往是扩展和修改同时存在的,因为再好的代码,终有一天也会有无法适应的场景出现。所以,我们要做的,就是在开发新东西的时候,尽可能地考虑多的场景,尽可能降低修改的可能性。并且当我们发现代码有“腐朽”的味道时,应该尽早地进行代码重构,使代码恢复到正常的“进化”过程。
里氏替换原则(Liskov Substitution Principle)
- 概念
所有引用父类的地方,都可以透明的传递子类对象。
这个原则简直就是多态的完美体现。
这个原则强调的是运用多态时,应该注子类的适配,使之无论传递任何子类对象,都能完美适应父类引用,不会产生异常。 - 实例
我们继续引用多态的那个实例,在那个实例之上做些修改。
现在我们是汽车制造商,你只需要告诉我品牌,我就可以生产出对应品牌的车。
我们目前只能生产兰博基尼
和五菱宏光
,那么接下来我们改变一下main方法,生产一下这2辆车:
public static void main(String[] args) {
//创建兰博基尼
System.out.println("生产了一辆:" + createCar(new LanBoJiNi()));
//创建五菱宏光
System.out.println("生产了一辆:" + createCar(new WuLingHongGuang()));
}
private static String createCar(Car car) {
return car.getCarType();
}
上面当中,我们通过createCar(Car car)
来创建了兰博基尼
和五菱宏光
,虽然createCar()
要求的参数是Car
,但是我们传入了子类对象兰博基尼
和五菱宏光
。并且这2个类都做了很好的适配(一个栗子而已,大家就认为其实我的兰博基尼其实是经过500道独特工序才制造出来的,五菱宏光是另外的500道工序),无论我传入谁,都可以完美生产,不会产生异常。这就是里氏替换原则!
依赖倒置原则(Dependence Inversion Principle)
- 概念
依赖倒置原则指代了一种特定的解耦形式,使得高层次的模块不依赖低层次的模块去实现细节。主要关键点有以下3点:
高层模块不应该依赖低层模块,两者都应该依赖其抽象。
抽象不应该依赖细节。
-
细节应该依赖细节。
在Java语言中,抽象指的就是接口或抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或者继承抽象类而产生的类就是细节,可以直接被实例化。高层模块就是抽象,底层模块就是具体实现类。一句话概括的话就是:面向接口编程。面向接口编程是面向对象的精髓之一。
接口隔离原则(Interface Segregation Principles)
- 概念
类不应该依赖它不需要的接口。
另一种定义是:类依赖的接口都应该是最小单位。
那么接口隔离原则其实就是要求我们将庞大、臃肿的接口按照某种规则,将其拆封成更小的、更具体的接口,这样客户端只需要依赖它需要的接口即可。
接口隔离原则的目的就是使系统解耦,从而更容易进行重构、更改等操作。
迪米特原则(Law of Demeter)
- 概念
一个对象应该对其他对象有最少的依赖。
通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,类的内部如何实现与调用者或者依赖者没关系,调用者或依赖者只需要知道它需要的方法即可,其他的一概不用管。
如果两个对象的关系过于密切,那么当一个对象发生变化时,另一个对象就会产生不可预估的风险。
小结
在应用的开发过程中,最难的不是完成应用的开发工作,而是在后续的升级、维护过程中让应用系统能够拥抱变化。拥抱变化也就意味着在满足需求且不破坏系统稳定性的前提下保持高扩展性、高内聚、低耦合,在经历了各版本的变更之后依然保持清晰、灵活、稳定的系统架构。
当然这是一个理想的情况,但我们必须要朝着这个方向去努力,那么遵循面向对象思想就是我们走向理想的第一步。
感谢
- 《Android源码设计模式解析与实战》 何红辉、关爱民 著
- 百度百科