优雅的设计模式-面向对象基础(上)

如何理解面向对象

面向对象的特性中抽象是封装、继承、多态的前提基础。合理的抽象源于对业务主题合理分析和合理认识。合理的抽象应该是自洽的,易理解的
关于组合和聚合的关系:最核心的区别就是生命周期的区别。组合关系中,整体和个体是一个整体,离开了整体,个体就没有意义,同时产生,同时销毁。而聚合关系中,部分单独个体存在也是具有存在的意义,即使脱离整理,个体也可以单独存在。
别滥用继承:继承的作用更多的时候是为了多态特性服务的
面向对象两个最基础的概念:类和对象

区分什么是"面向对象编程"和什么是"面向对象编程语言"

面向对象就是一个编程规范或编程风格。它是以类和对象为组织代码的基本单元,并通过抽象、封装、继承、多态四个特性最为代码设计和实现的基石

面向对象编程语言是支持类和对象的语法机制,并有现成的语法糖(语法机制),能够方便的实现面向对象编程四大特性的编程语言
区分什么是"面向过程编程"和什么是"面向对象编程"

面向过程编程: 

    以动词为主,分析出解决问题所需要的步奏,然后用函数把这些步奏一步一步实现,使用的时候一个一个依次调用就可以了。

面向对象编程:

    以名词为主,把构成问题事务分解各个对象,建立对象的目的不是为了完成一个步奏,而是为了描述某个事物在整个解决问题的步奏中的行为。

面向对象:狗.吃(骨头) 面向过程:吃.(狗,骨头)

面向对象编程特性基本原则

封装:将抽象出来的属性和方法封装在一起进而达到隐藏信息,保护数据

/*
    * @title 创建对象实现封装有四种方法
    * @method1 对象字面量方式{name: 'Mark_Zhang',key:'value'}  只能创建一次、复用率差、字面量多了代码冗余
    * @method2 内置构造函数创建 var parson =  new Object(); parson.name="内置构造函数创建对象"
    * @method3 简单的工厂函数 function createObj (name){let obj = new Object(); obj.name = name; return obj}
    * @method4  自定义构造函数
  */
  function Person (name, agei) {
    // 构造函数中多包含的属性和方法就可以理解抽象的一部分
    // public  公共属性
    this.name = name; // 实例可以直接通过对象点的方式直接访问
    // private  私有属性
    let age = agei;
    this.getAge = function(){
      return age;
    }
    this.setAge = function(a){
      age = a;
    }
  }
  Person.prototype.show = function(){
    return  this.name + ' 今年' + this.getAge() + '岁';
  }
  // 必须通过 new 关键字创建对象,否则 this 指向的就是 window 对象
  let p1 = new Person('Mark_Zhang',18)
  console.info(p1.name, p1.age) // Mark_Zhang undefined
  // 调用对象方法 (调用对象暴露的接口)
  p1.setAge(30);
  console.info(p1.getAge());
  // 调用原型方法
  console.info(p1.show())
  let p2 = new Person();
  // 利用对象动态性来添加属性
  p1.n = 'sunLine'
  console.info(p2.n) //sunLine
  
  /**
   * @title 通过【构造函数】和【原型法】添加成员的区别
   * @区别一: 通过【原型法】分配的函数(引用类型即可) 所有对象共享
   * @区别二: 通过【原型法】分配的属性( 基本类型)独立
   * @区别2.2: 我们还可以在类的外部通过. 语法进行添加,因为在实例化对象的时候,并不会执行到在类外部通过. 语法添加的属性,所以实例化之后的对象是不能访问到. 语法所添加的对象和属性的,只能通过该类访问。
   * @区别三: 若希望所有对象使用同一个函数,建议使用原型法添加函数,节省内存 
   * @区别四: 通过prototype给所有对象添加方法,不能访问所在类的私有属性和方法
   */

抽象:如何隐藏方法的具体实现,让调用者只需要关系方法提供了那些功能,并不需要知道这些功能具体是怎么实现的。
继承:继承最大的一个好处就是代码复用

继承:子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。

类式继承
所谓的类式继承就是使用的原型方式,将方法添加在父类的原型上,然后子类的原型父类的一个实例化对象

// 声明父类
let SuperClass function  (){
  let id = 1;
  this.name = ["继承"];
  this.superValue = function () {
    console.info("superValue is true")
    console.info('id---->',id)
  }
}
// 通过原型给父类添加方法
SuperClass.prototype.getSuperValue = function() {
  return this.superValue();
}
// 声明子类
let SubClass = function () {
  this.subValue = function () {
    console.info("this is subValue")
  }
}
// 子类继承父类
SubClass.prototype = new SuperClass();
// 为子类添加公有方法
SubClass.prototype.getSubValue = function() {
  return this.subValue();
}
let sub = new SubClass(),
    sub2 = new SubClass();
    
sub.getSuperValue(); // superValue is true
sub.getSubValue(); // this is subValue
​
console.info(sub.id)  // undefined
console.info(sub.name) // 继承
​
sub.name.push('类式继承')
console.info(sub2.name) // ['继承','类式继承']

其中最核心的一句代码是SubClass.prototype = new SuperClass();
类的原型对象prototype对象的作用就是为类的原型添加共有方法的,但是类不能直接访问这些方法,只有将类实例化之后,新创建的对象复制了父类构造函数中的属性和方法,并将原型proto 指向了父类的原型对象。这样子类就可以访问父类的public 和protected 的属性和方法,同时,父类中的private 的属性和方法不会被子类继承。
敲黑板,如上述代码的最后一段,使用类继承的方法,如果父类的构造函数中有引用类型,就会在子类中被所有实例共用,因此一个子类的实例如果更改了这个引用类型,就会影响到其他子类的实例。
构造函数继承

正式因为有了上述的缺点,才有了构造函数继承,构造函数继承的核心思想就是SuperClass.call(this,id),直接改变this的指向,使通过this创建的属性和方法在子类中复制一份,因为是单独复制的,所以各个实例化的子类互不影响。但是会造成内存浪费的问题

// SubClass.prototype = new SuperClass();
function SubClass(id) {
  SuperClass.call(this,id)
}

类继承
构造函数继承
核心思想
子类原型是父类实例化对象
SuperClass.call(this,id)
优点 子类实例化对象的属相和方法都指向父类的原型

每个实例化的子类都是个体互不影响
缺点
子类之间可能相互影响
内存浪费(子列公有的属性和方法通过原型在父类上追加)
组合式继承

针对上面两种继承方式,组合式继承汲取了两者的优点,即避免了内存的浪费,又使得每个实例化的子类互不影响。

// 组合式继承
// 声明父类
let SuperClass = function(name) {
  this.name = name ;
  this.books = ['js','html','css']
}
// 声明父类原型上的f方法
SuperClass.prototype.showBooks = function(){
  console.info(this.books)
}
// 声明子类
let SubClass = function (name) {
  SuperClass.call(this,name);
}
// 子类继承父类(链式继承)
SubClass.prototype = new SuperClass();
​
let subclass1 = new SubClass('java');
let subclass2 = new SubClass('php');
subclass2.showBooks();
subclass1.books.push('ios');    //["js", "html", "css"]
console.log(subclass1.books);  //["js", "html", "css", "ios"]
console.log(subclass2.books);   //["js", "html", "css"]

寄生组合继承

那么问题又来了~组合式继承的方法固然好,但是会导致一个问题,父类的构造函数会被创建两次(call()的时候一遍,new的时候又一遍),所以为了解决这个问题,又出现了寄生组合继承。
刚刚问题的关键是父类的构造函数在类继承和构造函数继承的组合形式中被创建了两遍,但是在类继承中我们并不需要创建父类的构造函数,我们只是要子类继承父类的原型即可。所以说我们先给父类的原型创建一个副本,然后修改子类constructor属性,最后在设置子类的原型就可以了
//原型式继承
//原型式继承其实就是类式继承的封装,实现的功能是返回一个实例,改实例的原型继承了传入的o对象

function inheritObject(o) {
    //声明一个过渡函数对象
    function F() {}
    //过渡对象的原型继承父对象
    F.prototype = o;
    //返回一个过渡对象的实例,该实例的原型继承了父对象
    return new F();
}

//寄生式继承
//寄生式继承就是对原型继承的第二次封装,使得子类的原型等于父类的原型。并且在第二次封装的过程中对继承的对象进行了扩展

function inheritPrototype(subClass, superClass){
    //复制一份父类的原型保存在变量中,使得p的原型等于父类的原型
    var p = inheritObject(superClass.prototype);
    //修正因为重写子类原型导致子类constructor属性被修改
    p.constructor = subClass;
    //设置子类的原型
    subClass.prototype = p;
}
//定义父类
var SuperClass = function (name) {
    this.name = name;
    this.books = ['javascript','html','css']
};
//定义父类原型方法
SuperClass.prototype.getBooks = function () {
    console.log(this.books)
};
​
//定义子类
var SubClass = function (name) {
    SuperClass.call(this,name)
}
​
inheritPrototype(SubClass,SuperClass);
​
var subclass1 = new SubClass('php')

多态:最大的一个好处就是提高代码的可拓展性和复用性
总结:

封装

What:隐藏信息,保护数据访问。
How:暴露有限接口和属性,需要编程语言提供访问控制的语法。
Why:提高代码可维护性;降低接口复杂度,提高类的易用性。

抽象

What: 隐藏具体实现,使用者只需关心功能,无需关心实现。
How: 通过接口类或者抽象类实现,特殊语法机制非必须。
Why: 提高代码的扩展性、维护性;降低复杂度,减少细节负担。

继承

What: 表示 is-a 关系,分为单继承和多继承。
How: 需要编程语言提供特殊语法机制。例如 Java 的 “extends”,C++ 的 “:” 。
Why: 解决代码复用问题。

多态

What: 子类替换父类,在运行时调用子类的实现。
How: 需要编程语言提供特殊的语法机制。比如继承、接口类、duck-typing。
Why: 提高代码扩展性和复用性。

3W 模型的关键在于 Why,没有 Why,其它两个就没有存在的意义。从四大特性可以看出,面向对象的终极目的只有一个:可维护性。易扩展、易复用,降低复杂度等等都属于可维护性的实现方式。


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

推荐阅读更多精彩内容