面向对象之七大基本原则(javaScript)

面向对象编程有本身的特性与原则,若是对于面向对象有一些了解的话,面向对象三大特征,封装、继承、多态,若是对面向对这三个概念不太了解,请参考面向对象之三个基本特征(javaScript)java

单一职责

若是咱们在编写程序的时候,一类或者一个方法里面包含了太多方法,对于代码的可读性来讲,无非是一场灾难,对于咱们来讲。因此为了解决这个问题,出现了单一职责。编程

什么是单一职责

单一职责:又称单一功能原则,面向对象五个基本原则(SOLID)之一。它规定一个类应该只有一个发生变化的缘由。(节选自百度百科)segmentfault

按照上面说的,就是对一个类而言,应该仅有一个引发它变化的缘由。换句话说,一个类的功能要单一,只作与它相关的事情。在类的设计过程当中要按职责进行设计,彼此保持正交,互不干涉。设计模式

单一职责的好处
  1. 类的复杂性下降,实现什么职责都有清晰明确的定义
  2. 可读性提升,复杂性下降,那固然可读性提升了
  3. 可维护性提升,可读性提升,那固然更容易维护了
  4. 变动引发的风险下降,变动是必不可少的,若是接口的单一职责作得好,一个接口修改只对相应的实现类有影响,对其余的接口无影响,这对系统的扩展性、维护性都有很是大的帮助。
实例
class ShoppinCar {
    constructor(){
        this.goods = [];
    }
    addGoods(good){
        this.goods = [good];
    }
    getGoodsList(){
        return this.goods;
    }
}
class Settlement {
    constructor(){
        this.result = 0; 
    }
    calculatePrice(list,key){
        let allPrice = 0;
        list.forEach((el) => {
            allPrice += el[key];
        })
        this.result = allPrice;
    }
    getAllPrice(){
        return this.result;
    }
}

用上面的代码来讲ShoppinCar类存在两个方法addGoodsgetGoodsList,分别是添加商品和获取商品列表。Settlement类中存在两个方法calculatePricegetAllPrice分别作的事情是计算价钱与获取总价钱。ShoppinCarSettlement都是在作本身的事情。添加商品与计算价格,虽然在业务上是相互依赖的,可是在代码中分别用两个类,然他们本身作本身的事情。其中任何一个类更改不会对另外一个类进行更改。框架

开闭原则

在一个类中暴露出去的方法,若这个方法变动了,则会产生很大的后果,可能致使其余依赖于这个方法且有不须要变动的业务形成大面积瘫痪。为了解决这个问题,能够单独再写一个方法,若这个方法与这个类中的其余方法相互依赖。函数

解决办法:工具

  1. 把其中依赖的代码copy一份到新的类中。
  2. 在新类中引用旧类中的方法。

两种方法都不是最好的解决方案。单元测试

第一种方法会致使代码大量的重复,第二种方法会致使类与类之间互相依赖。测试

什么是开闭原则

开闭原则:“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,可是对于修改是封闭的”,这意味着一个实体是容许在不改变它的源代码的前提下变动它的行为。(节选自百度百科)this

开闭原则对扩展开放,对修改关闭,并不意味着不作任何修改,底层模块的变动,必然要有高层模块进行耦合,不然就是一个孤立无心义的代码片断。开闭原则是一个最基本的原则,另外六个原则都是开闭原则的具体形态,是指导设计的工具和方法,而开闭原则才是精神领袖.

开闭原则好处
  1. 开闭原则有利于进行单元测试
  2. 开闭原则能够提升复用性
  3. 开闭原则能够提升可维护性
  4. 面向对象开发的要求
实例
class Drag {
    down(){
        //  ...
    }   
    move(){
        //  ...
        // 对拖拽没有作任何限制能够随意拖拽
    }   
    up(){
        //  ...
    }  
}
class LimitDrag extends Drag {
    move(){
        //  ...
        //  重写该方法对拖拽进行限制处理
    }
}

LimitDrag中重写了move方法,若修改了能够知足两种需求,一种是限制型拖拽,一种是不限制型拖拽,任何一个更改了另一个仍是能够正常运行。

里氏替换

每一个开发人员在使用别人的组件时,只需知道组件的对外裸露的接口,那就是它所有行为的集合,至于内部究竟是怎么实现的,没法知道,也无须知道。因此,对于使用者而言,它只能经过接口实现本身的预期,若是组件接口提供的行为与使用者的预期不符,错误便产生了。里氏替换原则就是在设计时避免出现派生类与基类不一致的行为。

什么是里氏替换

里氏替换原则:OCP做为OO的高层原则,主张使用“抽象(Abstraction)”和“多态(Polymorphism)”将设计中的静态结构改成动态结构,维持设计的封闭性。“抽象”是语言提供的功能。“多态”由继承语义实现。(节选自百度百科)

里氏替换好处
  1. 代码共享,减小建立类的工做量,每一个子类都拥有父类的方法和属性
  2. 提升代码的重用性
  3. 子类能够形似父类,可是又异于父类。
  4. 提升代码的可扩展性,实现父类的方法就能够了。许多开源框架的扩展接口都是经过继承父类来完成。
  5. 提升产品或项目的开放性
实例
//  抽象枪类
class AbstractGun {
    shoot(){
        throw "Abstract methods cannot be called";
    }
}
//  步枪
class Rifle extends AbstractGun {
    shoot(){
        console.log("步枪射击...");
    }
}
//  狙击枪
class AUG extends Rifle {
    zoomOut(){
        console.log("经过放大镜观察");
    }
    shoot(){
        console.log("AUG射击...");
    }
}
//  士兵
class Soldier {
    constructor(){
        this.gun = null;
    }
    setGun(gun){
        this.gun = gun;
    }
    killEnemy(){
        if(!this.gun){
            throw "须要给我一把枪";
            return;
        }
        console.log("士兵开始射击...");
        this.gun.shoot();
    }
}
//  狙击手
class Snipper extends Soldier {
    killEnemy(aug){
        if(!this.gun){
            throw "须要给我一把枪";
            return;
        }
        this.gun.zoomOut();
        this.gun.shoot();
    }
}
let soldier = new Soldier();
soldier.setGun(new Rifle());
soldier.killEnemy();

let snipper = new Snipper();
//  分配狙击枪
snipper.setGun(new AUG());
snipper.killEnemy();

snipper.setGun(new Rifle());
// snipper.killEnemy();  //  this.gun.zoomOut is not a function

从上述代码中能够看出,子类和父类之间关系,子类方法必定是等于或大于父类的方法。子类可以出现的父类不必定能出现,可是父类出现的地方子类必定可以出现。

依赖倒置

若是方法与方法之间或类与类之间,存在太多的依赖关系会致使代码可读性以及可维护性不好。依赖倒置原则可以很好的解决这些问题。

什么是依赖倒置

依赖倒置原则:程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就下降了客户与实现模块间的耦合。(节选自百度百科)

  1. 高层模块不该该依赖低层模块,二者都应该依赖其抽象
  2. 抽象不该该依赖细节
  3. 细节应该依赖抽象
依赖倒置好处
  1. 经过依赖于接口,隔离了具体实现类
  2. 低一层的变更并不会致使高一层的变更
  3. 提升了代码的容错性、扩展性和易于维护
实例
//  抽象枪类
class AbstractGun {
    shoot(){
        throw "Abstract methods cannot be called";
    }
}
//  步枪
class Rifle extends AbstractGun {
    shoot(){
        console.log("步枪射击...");
    }
}
//  狙击枪
class AUG extends AbstractGun {
    shoot(){
        console.log("AUG射击...");
    }
}

从上面的代码能够看出,步枪与狙击枪的shoot所有都是依赖于AbstractGun抽象的枪类,上述编程知足了依赖倒置原则。

接口隔离

什么是接口隔离

接口隔离:客户端不该该依赖它不须要的接口;一个类对另外一个类的依赖应该创建在最小的接口上。(节选自百度百科)

接口隔离原则与单一职责原则的审视角度不相同。单一职责原则要求是类和接口的职责单一,注重的职责,这是业务逻辑上的划分。接口隔离原则要求接口的方法尽可能少。

接口隔离好处
  1. 避免接口污染
  2. 提升灵活性
  3. 提供定制服务
  4. 实现高内聚
实例
function mix(...mixins) {
  class Mix {}
  for (let mixin of mixins) {
    copyProperties(Mix, mixin);
    copyProperties(Mix.prototype, mixin.prototype);
  }
  return Mix;
}
function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== "constructor"&& key !== "prototype"&& key !== "name") {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}
class Behavior {
    eat(){
        throw "Abstract methods cannot be used";
    }   
    call(){
        throw "Abstract methods cannot be used";
    }
}
class Action {
    climbTree(){
        throw "Abstract methods cannot be used";
    }
}
class Dog extends Behavior{
    eat(food){
        console.log(`狗正在吃${food}`);
    }
    hungry(){
        console.log("汪汪汪,我饿了")
    }
}
const CatMin = mix(Behavior,Action);
class Cat extends CatMin{
    eat(food){
        console.log(`猫正在吃${food}`);
    }
    hungry(){
        console.log("喵喵喵,我饿了")
    }
    climbTree(){
        console.log("爬树很开心哦~")
    }
}
let dog = new Dog();
dog.eat("骨头");
dog.hungry();
let cat = new Cat();
cat.eat("鱼");
cat.hungry();
cat.climbTree();

你们必定要好好分析一下上面的代码,共有两个抽象类,分别对应不一样的行为,CatDog类拥有共同的行为,可是Cat又拥有其本身单独的行为,使用抽象(即接口)继承其方法,使用接口隔离使其完成各自的工做,各司其职。

迪米特法则

迪米特法则:最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其余对象有尽量少的了解,不和陌生人说话。英文简写为: LoD.(节选自百度百科)

迪米特法则的作法观念就是类间解耦,弱耦合,只有弱耦合了之后,类的复用率才能够提升。一个类应该对其余对象保持最少的了解。通俗来说,就是一个类对本身依赖的类知道的越少越好。由于类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另外一个类的影响也越大。

迪米特法则好处
  1. 减小对象之间的耦合性
实例
class ISystem {
    close(){
        throw "Abstract methods cannot be used";
    }
}
class System extends ISystem{
    saveCurrentTask(){
        console.log("saveCurrentTask")
    }
    closeService(){
        console.log("closeService")
    }
    closeScreen(){
        console.log("closeScreen")
    }
    closePower(){
        console.log("closePower")
    }
    close(){
        this.saveCurrentTask();
        this.closeService();
        this.closeScreen();
        this.closePower();
    }
}
class IContainer{
    sendCloseCommand(){
        throw "Abstract methods cannot be used";
    }
}
class Container extends IContainer{
    constructor(){
        super()
        this.system = new System();
    }
    sendCloseCommand(){
        this.system.close();
    }
}
class Person extends IContainer{
    constructor(){
        super();
        this.container = new Container();
    }
    clickCloseButton(){
       this.container.sendCloseCommand();
    }
}
let person = new Person();
person.clickCloseButton();

上面代码中Container做为媒介,其调用类不知道其内部是如何实现,用户去触发按钮,Container把消息通知给计算机,计算机去执行相对应的命令。

组合/聚合复用原则

聚合(Aggregation)表示一种弱的‘拥有’关系,体现的是A对象能够包含B对象但B对象不是A对象的一部分。

合成(Composition)则是一种强的'拥有'关系,体现了严格的部分和总体关系,部分和总体的生命周期同样。

组合/聚合:是经过得到其余对象的引用,在运行时刻动态定义的,也就是在一个对象中保存其余对象的属性,这种方式要求对象有良好定义的接口,而且这个接口也不常常发生改变,并且对象只能经过接口来访问,这样咱们并不破坏封装性,因此只要类型一致,运行时还能够经过一个对象替换另一个对象。

优先使用对象的合成/聚合将有助于你保持每一个类被封装,并被集中在单个任务上,这样类和类继承层次会保持较小规模,并且不太可能增加为不可控制的庞然大物。

组合/聚合复用原则好处
  1. 新的实现较为容易,由于超类的大部分功能可经过继承关系自动进入子类;
  2. 修改或扩展继承而来的实现较为容易。
实例
function mix(...mixins) {
  class Mix {}
  for (let mixin of mixins) {
    copyProperties(Mix, mixin);
    copyProperties(Mix.prototype, mixin.prototype);
  }
  return Mix;
}
function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== "constructor"&& key !== "prototype"&& key !== "name") {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}
class Savings {
    saveMoney(){
        console.log("存钱");
    }
    withdrawMoney(){
        console.log("取钱");
    }
}
class Credit {
    overdraft(){
        console.log("透支")
    }
}
const CarMin = mix(Savings,Credit);
class UserCar extends CarMin {
    constructor(num,carUserName){
        super();
        console.log()
        this.carNum = num;
        this.carUserName = carUserName;
    }
    getCarNum(){
        return this.carNum;
    }
    getCarUserName(){
        return this.carUserName;
    }
}
let myCar = new UserCar(123456798,"Aaron");
console.log(myCar.getCarNum());
console.log(myCar.getCarUserName());
myCar.saveMoney();
myCar.withdrawMoney();
myCar.overdraft();

总结

这些原则在设计模式中体现的淋淋尽致,设计模式就是实现了这些原则,从而达到了代码复用、加强了系统的扩展性。因此设计模式被不少人奉为经典。咱们能够经过好好的研究设计模式,来慢慢的体会这些设计原则。

相关文章
相关标签/搜索