当我们谈论面对对象的时候,到底在谈论什么?
-
什么是面向对象的编程和面向对象的编程语言?
面型对象编程是一种编程规范式或编程风格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石。 -
如果判定一个编程语言是不是面向对象编程语言?
面向对象的编程语言是支持类或队形的语法机制,并有线程的语法机制,能方便地实现面向对象编程的四大特性(继承、封装、抽象、多态) -
面向对象编程和面向对象编程语言的关系?
面向对象编程一般使用面向对象编程语言来实现,但是,不使用面向对象编程语言,也可以进行面向对象编程。反过来讲,即时使用面向对象编程语言,写出来的代码也不一定是面向对象编程风格的。 -
什么是面向对象的分析和设计?
面向对象分析就是要搞清楚做什么,面向对象设计就是要搞清楚怎么做。两个阶段最终的产出是类的设计,包括程序被拆解为哪些类,每个类有哪些属性犯法、类与类之间如何交互等等。
封装、抽象、继承、多态分别可以解决哪些编程问题
理解面向对象的编程及面向对象的编程语言,关键就是理解其四大特性:封装、抽象、继承、多态。不过对于这四大特性,光知道他们的定义是不够的,我们还要知道每个特性存在的意义和目的,以及他们能解决哪些编程问题。
封装
封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或者数据。
以一个虚拟钱包举一个简单的例子:
public class Wallet {
private String id;
private long createTime;
private BigDecimal balance;
private long balanceLastModifiedTime;
// ...省略其他属性...
public Wallet() {
this.id = IdGenerator.getInstance().generate();
this.createTime = System.currentTimeMillis();
this.balance = BigDecimal.ZERO;
this.balanceLastModifiedTime = System.currentTimeMillis();
}
// 注意:下面对get方法做了代码折叠,是为了减少代码所占文章的篇幅
public String getId() { return this.id; }
public long getCreateTime() { return this.createTime; }
public BigDecimal getBalance() { return this.balance; }
public long getBalanceLastModifiedTime() { return this.balanceLastModifiedTime; }
public void increaseBalance(BigDecimal increasedAmount) {
if (increasedAmount.compareTo(BigDecimal.ZERO) < 0) {
throw new InvalidAmountException("...");
}
this.balance.add(increasedAmount);
this.balanceLastModifiedTime = System.currentTimeMillis();
}
public void decreaseBalance(BigDecimal decreasedAmount) {
if (decreasedAmount.compareTo(BigDecimal.ZERO) < 0) {
throw new InvalidAmountException("...");
}
if (decreasedAmount.compareTo(this.balance) > 0) {
throw new InsufficientAmountException("...");
}
this.balance.subtract(decreasedAmount);
this.balanceLastModifiedTime = System.currentTimeMillis();
}
}
例如,对于钱包balance这个属性,从业务角度来说,只能增或者减,不会被重新设置,所以只暴露了increaseBalance()和decreaseBalance()方法,并没有暴露set方法。对于balanceLastModifiedTime属性,它完全跟balance这个属性的修改操作绑定在一起的,所以对于这个属性的修改操作完全封装在了increaseBalance()和decreaseBalance()方法中。
对于封装这个特性,我们需要编程语言本身提供一定的语法机制来支持。这个语法机制就是访问权限控制
。例如java的private、public等关键字。
封装特性的定义讲完了,我们再来看一下,封装的意义是什么?他能解决什么编程问题?
如果我们对类中属性的访问不做限制,那任何代码都可以访问、修改类中的属性,虽然这样看起来更加灵活,但从另一方面来说,过度灵活也意味着不可控。属性可以随意被以各种奇葩的方式修改,而且修改逻辑可能散落在代码中的各个角落,势必影响代码的可读性、可维护性。
除此之外,类仅仅通过有限的方法暴露必要的操作,也能提高类的易用性。如果我们把类的属性都暴露给调用者,调用者想要正确操作这些属性,就势必要对业务细节有足够的了解。而这对于调用者来说也是一种负担。
抽象
封装主要讲的是如何隐藏信息,保护数据,而抽象讲的是如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。
举一个例子:
public interface IPictureStorage {
void savePicture(Picture picture);
Image getPicture(String pictureId);
void deletePicture(String pictureId);
void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo);
}
public class PictureStorage implements IPictureStorage {
// ...省略其他属性...
@Override
public void savePicture(Picture picture) { ... }
@Override
public Image getPicture(String pictureId) { ... }
@Override
public void deletePicture(String pictureId) { ... }
@Override
public void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo) { ... }
}
上面这段代码中,我们利用java的interface接口语法来实现抽象性。调用者在使用图片存储功能时候,只需要了解IPictrueStorage这个接口暴露了哪些方法就可以了,不需要去查看PictureStorage类里的具体实现逻辑。
实际上,抽象这个特性是非常容易实现的,并不需要非得依靠接口类或者抽象类这些特殊的语法机制来支持。类的方法通过编程语言中的“函数”这一语法机制来实现。通过函数包裹具体的实现逻辑,这本身就是一种抽象。
抽象的意义是什么?它解决什么编程问题?
抽象作为一种只关注功能点不关注实现细节的思路,可以过滤掉许多不必要我们关注的信息。
除此之外,抽象作为一个非常宽泛的设计思想,在代码设计中,起到非常重要的指导作用。很多设计原则都体现了这种设计思想,比如基于接口而非实现的编程、开闭原则(对扩展开放、对修改关闭)、代码解耦等。
换一个角度来考虑,我们在定义(或者命名)类的方法的时候,也要有抽象思维,不要在方法的定义中,暴露太多的实现细节,以保证在某个时间点需要改变方法的实现逻辑的时候,不用去修改其定义。举个简单例子,比如getAliyunPictureUrl()就不是一个具有抽象思维的命名,因为某一天如果我们不再把图片存储在阿里云上,而是存储在私有云上,那这个命名也要被随之修改。相反,如果我们定义一个相对抽象的函数,比如getPictureUrl(),那即使内部存储方式改了,我们也不需要重新修改命名。
继承
继承是用来表示is-a的关系,比如猫是一种哺乳动物。从继承关系上来讲,继承分为两种模式:单继承和多继承。单继承表示一个子类只能继承一个父类,多继承表示一个子类可以继承多个父类,比如猫既是哺乳动物又是爬行动物。
为了实现继承这个特性,编程语言需要提供特殊的语法机制。
继承的意义是什么?它能解决哪些编程问题?
继承最大的好处就是代码复用,假如两个类有一些相同的属性和方法,我们就可以将这些属性和方法抽取到父类中。我们也可以通过其他方式来解决代码的复用,比如利用组合关系而不是继承关系。
继承的概念很好理解,也很容易使用。不过过度的使用继承,继承层次过深过复杂,就会导致代码的可读性,可维护性变差。所以,继承这个特性也是非常有争议的一个特性,很多人觉得继承是一种反模式,应该尽量少用甚至不用。
多态
多态是指子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。
多态的实现方式需要语言提供特殊的语法机制,有以下几种方式
- 继承+重写
- 接口类语法
- duck-typing语法
多态存在的意义是什么?他能解决什么问题呢?
多态用来提高代码的可扩展性和可复用性。
为什么这么说呢?
以下面代码为例:
public interface Iterator {
String hasNext();
String next();
String remove();
}
public class Array implements Iterator {
private String[] data;
public String hasNext() { ... }
public String next() { ... }
public String remove() { ... }
//...省略其他方法...
}
public class LinkedList implements Iterator {
private LinkedListNode head;
public String hasNext() { ... }
public String next() { ... }
public String remove() { ... }
//...省略其他方法...
}
public class Demo {
private static void print(Iterator iterator) {
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
public static void main(String[] args) {
Iterator arrayIterator = new Array();
print(arrayIterator);
Iterator linkedListIterator = new LinkedList();
print(linkedListIterator);
}
}
仅用一个函数print()
就可以实现打印不同集合类型的数据,当再增加一种需要遍历打印类型的时候,比如Hashmap,只要让其继承iterator,实现自己的hasNext和next方法即可。完全不用修改print(),所以说提高了代码的可扩展性。
我们不使用多态性,就无法将不同类型的集合(Array、LinkedList)传递给相同的函数(print),我们需要针对每种要遍历打印的集合,实现不同的print函数。利用多态性,显然提高了代码的可复用性。