二、设计模式 - 面向对象设计原则


面向对象设计原则


在软件设计时,为了提高软件的可扩展性、可复用性、可维护性和灵活性,开发人员要尽量遵守面向对象的设计
原则,从而提升开发效率、节约开发成本和维护成本,同时面向对象设计原则也是学习设计模式的基础

有人说面向对象设计原则共有 6 个,也有人说共有 7 个,具体的数量我们不去追究,因为它们的核心思想都是一
样的,所以本文本着多多益善的原则,将记录面向对象的七个原则

个人建议:面向对象设计原则虽好,但不要在项目中滥用,否则会适得其反,只有那些预测未来经常会变、会经常
扩展的地方,才建议考虑参照面向对象设计原则


1. 开闭原则(OCP - Open Close Principle)


开闭原则是,对新增功能开放,对修改原有代码逻辑关闭

举例:线上运行的项目中已有一个微信支付功能,其对应的模块类是 Pay,该类中内容非常多,逻辑非常复杂,现
在产品要求再新增一个支付宝支付功能,那么我们就不应该在 Pay 中进行修改,而是应该再创建一个新的类,这就
是一个非常简单的满足开闭原则的设计

在设计类似功能时,设计者应该具备提前预判该功能未来会有多种实现方式的能力,在设计初期就以满足开闭原则
的结构来设计功能模块

开闭原则

2. 依赖倒转原则(DIP - Dependence Inversion Principle)


依赖倒转原则是,要关联、组合、聚合、依赖抽象类或者接口,而不是具体实现类

举例:现有电脑类,西部数据硬盘类,因特尔 CPU 类,为了实现未来扩展性,我们不应该让电脑类直接组合西部数
据类和因特尔 CPU 类,而是需要对西部数据类和因特尔 CPU 类进行抽象,提取成接口或抽象类,然后让电脑类
组合抽象类或接口,这样更利于扩展

依赖倒转原则

3. 里氏替换原则(LSP - Liskov Substitution Principle)


里氏替换原则是,子类可以扩展父类的方法,但不要重写父类的方法

如果不遵守里氏替换原则,当子类重写父类方法后,如果重写的内容完全偏离父类的原来意图,可能会出现
意想不到的错误

举一个比较呆的例子:

  1. 设计端:创建一个类 NewList 继承 ArrayList 并将 add 方法重写成移除操作
  2. 使用者:调用了 NewList 的 add 方法,按照常识,本意想做添加操作
  3. 运行:结果却是移除操作

里氏替换原则

4. 单一职责原则(SRP - Single Responsibility Principle)


单一职责原则是,一个类应该只专注一个业务模块,类中只提供该模块相关的功能

举例:一个类只专注一个领域,如果一个类中有查询用户、新增用户、删除用户、新增部门、删除部门等功能,
那这个类就违反了单一职责,应该考虑重构该类为一个用户类,一个部门类

单一职责原则

5. 合成复用原则(CRP - Composite Reuse Principle)


合成复用原则是,尽量避免继承,而是使用聚合和组合的方式来复用其他类,继承的方式实现复用耦合性太强,而
且也不够灵活

举例来一个不算很好的例子,将就着领会一下意思:
合成复用原则

6. 迪米特法则(LOD - Law Of Demeter)


迪米特法则是,类中不应该依赖与其不发生直接关系的类,如果要依赖,应该有个中间类

举例,现在要实现男孩用电脑打游戏,得先缕清关系,男孩用电脑是直接关系,电脑打游戏是直接关系,男孩和游
戏是间接关系,所以男孩类中不应该依赖游戏类,而电脑类恰好适合当作中间类,因为它与二者都是直接关系

迪米特法则
这个例子如果只看 UML 图,可能不太好理解,现在写出代码:

不满足迪米特法则,因为依赖了间接关系的类:

/**
 * 游戏类
 */
class Game {
    /**
     * 游戏名
     */
    public String gameName;
}
/**
 * 电脑类
 */
class Computer {
    /**
     * 用电脑玩游戏
     *
     * @param game 依赖电脑类
     */
    public String play(Game game) {
        return game.gameName;
    }
}
/**
 * 男孩类
 */
class Boy {
    /**
     * 男孩名
     */
    public String boyName;

    /**
     * 用电脑玩游戏
     *
     * @param computer 依赖电脑对象
     * @param game     依赖游戏对象
     */
    public void play(Computer computer, Game game) {
        System.out.println(boyName + " 玩 " + computer.play(game));
    }
}

满足迪米特法则,因为所有的依赖,都是直接关系:

/**
 * 游戏类
 */
class Game {
    /**
     * 游戏名
     */
    public String gameName;
}
/**
 * 男孩类
 */
class Boy {
    /**
     * 男孩名
     */
    public String boyName;
}
/**
 * 电脑类
 */
class Computer {
    /**
     * 玩游戏
     *
     * @param boy  依赖男孩对象
     * @param game 依赖游戏对象
     */
    public void play(Boy boy, Game game) {
        System.out.println(boy.boyName + " 玩 " + game.gameName);
    }
}

7. 接口隔离原则(ISP - Interface Segregation Principle)


接口隔离原则是,接口功能要尽可能少,但是也要有限度

接口如果不提供方法的默认实现的话,实现类就必须得实现那些抽象方法,这就导致有一些实现类并不需要使用
那么多的接口方法,但又却不得不实现

接口隔离原则

代码演示:

不满足接口隔离原则,因为 Person 不会飞,而又不得不实现接口中的 fly 方法

/**
 * 动作接口,接口过于臃肿,复用性不好
 */
interface Action {
    /**
     * 飞
     */
    public void fly();

    /**
     * 走
     */
    public void walk();
}

/**
 * 人类
 */
class Person implements Action {

    /**
     * 飞,人不会飞,但是因为实现了 Action 接口,所以必须实现该方法
     * 违法了接口隔离原则
     */
    @Override
    public void fly() {

    }

    /**
     * 走
     */
    @Override
    public void walk() {

    }
}

/**
 * 鸟类
 */
class Bird implements Action {

    /**
     * 飞
     */
    @Override
    public void fly() {

    }

    /**
     * 走
     */
    @Override
    public void walk() {

    }
}

满足接口隔离原则,每个类只实现其需要实现的接口方法

/**
 * 飞的动作接口
 */
interface Fly {
    /**
     * 飞
     */
    public void fly();
}
/**
 * 走的动作接口
 */
interface Walk {
    /**
     * 走
     */
    public void walk();

}
/**
 * 人类, 只需要实现走的动作
 */
class Person implements Walk {
    /**
     * 走
     */
    @Override
    public void walk() {

    }
}
/**
 * 鸟类,需要实现走和飞两个动作
 */
class Bird implements Fly, Walk {
    /**
     * 飞
     */
    @Override
    public void fly() {
    }
    /**
     * 走
     */
    @Override
    public void walk() {

    }
}

总结


不管是面向对象设计原则,还是设计模式,我们要学会站在设计者和使用者两个角度来看问题,不然很难理解这些
知识,我们参考设计原则或设计模式想要达到的效果其实只有两个:

  1. 站在类库或框架开发者的角度:当要更新类库或框架时,尽量不修改类库或框架中原来的代码逻辑,也不要修改方法签名,
    可能很多人都在用我们的类库或框架,一旦原有逻辑修改错误或方法签名改变,会导致所有使用者都受影响

  2. 站在使用者角度:不管类库或框架怎么更新,都不要影响使用者的原有代码,当使用者自己想变更需求时,
    最好只更换一个实现类就能完成功能

刚开始按照网上的例子学习设计模式或面向对象设计原则时,总有疑惑,开闭原则相关的例子,实现扩展后,客户
端的代码却总得跟着改,这是为什么?

这是因为网上的例子都是站在设计者和使用者双重角度来列举的,客户端代码改变是为了演示使用者自己想变
更需求时,类库或框架恰好扩展了该功能,客户端只要更换实现类即可的效果,如果使用者不想变更需求,客
户端是不需要改变的

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值