学习笔记-设计原则与思想:面向对象

当我们谈论面对对象的时候,到底在谈论什么?

  • 什么是面向对象的编程和面向对象的编程语言?
    面型对象编程是一种编程规范式或编程风格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石。
  • 如果判定一个编程语言是不是面向对象编程语言?
    面向对象的编程语言是支持类或队形的语法机制,并有线程的语法机制,能方便地实现面向对象编程的四大特性(继承、封装、抽象、多态)
  • 面向对象编程和面向对象编程语言的关系?
    面向对象编程一般使用面向对象编程语言来实现,但是,不使用面向对象编程语言,也可以进行面向对象编程。反过来讲,即时使用面向对象编程语言,写出来的代码也不一定是面向对象编程风格的。
  • 什么是面向对象的分析和设计?
    面向对象分析就是要搞清楚做什么,面向对象设计就是要搞清楚怎么做。两个阶段最终的产出是类的设计,包括程序被拆解为哪些类,每个类有哪些属性犯法、类与类之间如何交互等等。

封装、抽象、继承、多态分别可以解决哪些编程问题

理解面向对象的编程及面向对象的编程语言,关键就是理解其四大特性:封装、抽象、继承、多态。不过对于这四大特性,光知道他们的定义是不够的,我们还要知道每个特性存在的意义和目的,以及他们能解决哪些编程问题。

封装

封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或者数据。
以一个虚拟钱包举一个简单的例子:

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函数。利用多态性,显然提高了代码的可复用性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容