设计模式六大原则

引言

本文主要讲解设计模式的设计原则,以及通过一些样例去详解(通篇还是偏稿子的形态,并没有好好整理,而是想到的就写下

六大原则

  • 单一职责原则
  • 开闭原则
  • 里氏替换原则
  • 依赖倒置原则
  • 接口隔离原则
  • 迪米特原则

单一职责

单一职责原则的定义(类、方法、接口)

首先先看概念

单一职责原则(Single Responsibility Principle,SRP)又称单一功能原则。这里的职责是指类变化的原因,单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分(There should never be more than one reason for a class to change)。

归纳总结,就是一个类不应该承担过多的职责,而是专注类自身的职责

优点

  • 降低类的复杂度
  • 提高类的可读性
  • 提高系统的可维护性
  • 降低变更的风险

样例

//user.js

class User {
    ...
    login (username, password) {}
    register (emal, username, password) {}
    logError (msg)
    sendEmail (email)
}

user类存在很大的问题,user既要去负责login行为,又要兼顾log日志和sendEmail,很明显后者的行为明显区别于前者。 因此需要去做拆分:

// user.js
class User {
    ...
    login (username, password) {}
    register (emal, username, password) {}
}

class LogSvc {
    logError (msg)
}

class Email {
    sendEmail (email)
}

直观的可以发现,颗粒度降低后,每个类都在做自己的特征职责。整体的代码逻辑就会非常清晰,并且修改email后,不需要QA再去回顾整个User类,而只需要去检查Email的功能即可

开闭原则

开闭原则(Open-Closed Principle, OCP)是指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

强调的是用抽象构建框架,用实现扩展细节。

开闭原则,是面向对象设计中最基础的设计原则。它指导我们如何建立稳定灵活的系统。

示例

/**  
 * 顶层接口,定义了获取电脑信息的接口方法 
 */
 public interface Computer {  
 
    double getPrice();//价格  
    String getColor();//颜色  
    int getMemory();//内存  
    float getSize();//尺寸  
}

然后定义两个实现类,华硕电脑与苹果Mac

 /**  
  * 华硕
  */
 public class AsusComputer implements Computer {  
  
    private double price;  
    private String color;  
    private int memory;  
    private float size;  
  
 public AsusComputer(double price, String color, int memory, float size) {  
    this.price = price;  
    this.color = color;  
    this.memory = memory;  
    this.size = size;  
  }  
  
  @Override  
  public double getPrice() { return this.price; }  
  @Override  
  public String getColor() { return this.color; }  
  @Override  
  public int getMemory() { return this.memory; }  
  @Override  
  public float getSize() { return this.size; }  
}
/** 
 * Mac
 */
 public class MacComputer implements Computer{  
  
    private double price;  
    private String color;  
    private int memory;  
    private float size;  
  
 public MacComputer(double price, String color, int memory, float size) {  
    this.price = price;  
    this.color = color;  
    this.memory = memory;  
    this.size = size;  
  }  
  
  @Override  
  public double getPrice() { return this.price; }  
  @Override  
  public String getColor() { return this.color; }  
  @Override  
  public int getMemory() { return this.memory; }  
  @Override  
  public float getSize() { return this.size; }  
}

TC 如下

public class Test {  
  
    public static void main(String\[\] args) {  
  
        Computer computer = new AsusComputer(4888.88D,"深蓝",8,14.0F);  
  
        System.out.println(  
            "电脑:华硕\n" +  
            "售价:" + computer.getPrice() + "\n" +  
            "颜色:" + computer.getColor() + "\n" +  
            "内存:" + computer.getMemory() + "\n" +  
            "尺寸:" + computer.getSize()  
        );  
  
  }  
}
电脑:华硕
售价:4888.88
颜色:深蓝
内存:8
尺寸:14.0

如果需要针对某个活动,对某个类型的电脑进行打折销售,需要对 getPrice 进行修改,可能会有如下改动:

@Override  
public double getPrice() {   
    return this.price * 0.6;  
}

这样不符合开闭原则,虽然看起来简单容易修改,但是不易拓展 正确的做法应该:

/**  
 * 华硕电脑打折 
 */
 public class AsusDiscountComputer extends AsusComputer {  
  
    private float discount;  
  
    public AsusDiscountComputer(double price, String color, int memory, float size, float discount) {  
        super(price, color, memory, size);  
        this.discount = discount;  
    }  
  
    public double getDiscountPrice(){  
        return getPrice() * this.discount;  
    }  
}

应该考虑,该活动功能的拓展性,可以基于某一类电脑派生一个子类,实现对应改活动的折扣价格实现。一切要以可扩展出发

里氏替换原则

子类的继承实现,需要行为上保持和父类一致,如果行为不一致进行派生,将会造成程序上的错误

鸵鸟非鸟

根据生物学的定义鸵鸟实实在在是鸟类,有羽毛几乎覆盖全身的卵生脊椎动物,温血卵生,用肺呼吸,几乎全身有羽毛,后肢能行走,前肢变为翅,大多数能飞。定义一个鸟类,定义一个继承鸟类的鸵鸟类。

public class Bird
{
    public virtual int FlySpeed{get;set;}
    public void Fly() { }
}
public class Ostrich : Bird
{
    public override int FlySpeed
    {
        get
        {
            return base.FlySpeed;
        }
        set
        {
            base.FlySpeed = 0;//不会飞 飞不起来
        }
    }
}
public class Bird
{
    public virtual int FlySpeed{get;set;}
    public void Fly() { }
}
public class Ostrich : Bird
{
    public override int FlySpeed
    {
        get
        {
            return base.FlySpeed;
        }
        set
        {
            base.FlySpeed = 0;//不会飞 飞不起来
        }
    }
}

但是鸵鸟飞行速度为0,不能飞 所以程序必然会出错

总结

“LSP所表述的就是在同一个继承体系中的对象应该有共同的行为特征。我们在设计对象时是按照行为进行分类的,只有行为一致的对象才能抽象出一个类来。” 也就是说,行为不同的两个对象,不应该将某一个作为子类基于基类继承。鸵鸟非鸟,能飞是鸟的特性,但鸵鸟是不能飞的,我们强行将其归为鸟类,最终导致代码出错。

所有子类的行为功能必须和其父类持一致,如果子类达不到这一点,那么必然违反里氏替换原则。实际的开发过程中,不正确的派生关系会直接影响程序的运行。

里氏替换原则就是在设计时避免出现派生类与基类不一致的行为。

接口隔离原则

依赖倒置原则

https://www.cnblogs.com/TomXu/archive/2012/02/15/2330143.html

总结:高层模块的实现不应该依赖下层模块的实现,依赖倒置是让底层模块依赖高层模块

其他

多态

记录下多态,以一个例子说明

非多态例子

var makeSound = function(animal) {
    if(animal instanceof Duck) {
        console.log('嘎嘎嘎');
    } else if (animal instanceof Chicken) {
        console.log('咯咯咯');
    }
}
var Duck = function(){}
var Chiken = function() {};
makeSound(new Chicken());
makeSound(new Duck());

多态例子

var makeSound = function(animal) {
    animal.sound();
}

var Duck = function(){}
Duck.prototype.sound = function() {
    console.log('嘎嘎嘎')
}
var Chiken = function() {};
Chiken.prototype.sound = function() {
    console.log('咯咯咯')
}

makeSound(new Chicken());
makeSound(new Duck());

多态的理解:不同的对象做了相同的行为,但是可以得到不同的执行结果