设计模式之面向对象七大原则

1. 单一职责原则(Single Responsibility Principle)
2. 里氏替换原则(Liskov Substitution Principle)
3. 依赖倒置原则(Dependence Inversion Principle)
4. 接口隔离原则(Interface Segregation Principle)
5. 迪米特法则(Law Of Demeter)
6. 开闭原则(Open Close Principle)
7. 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)


缩写 原则名称 概述
SPR 单一职责原则 每一个类应该专注于做一件事情。
LSP 里氏替换原则 基类存在的地方,子类是可以替换的
DIP 依赖倒置原则 实现尽量依赖抽象,不依赖具体实现。高层模块不应该直接依赖于低层模块,高层模块和低层模块应该同时依赖一个抽象层。
ISP 接口隔离原则 应当为客户端提供尽可能小的单独的接口,而不是提供大的总的接口。
LOD 迪米特法则 又叫最少知识原则,一个软件实体应当尽可能少的与其他实体发生相互作用。
OCP 开闭原则 面向扩展开放,面向修改关闭。
CARP 组合/聚合复用原则 尽量使用合成/聚合达到复用,少用继承。原则: 一个类中有另一个类的对象。

解析

[1]

1. 单一职责原则(Single Responsibility Principle):
见名知意,这个条职责的潜台词的就是,专注做一个事。单一职责原则可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;提高类的可读性,提高系统的可维护性;变更引起的风险降低,变更是必然的,如果遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用此原则。


[2]

2. 里氏替换原则(Liskov Substitution Principle):
将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常。反之则不成立,因为如果使用的是一个子类对象的话,那么它不一定能够使用基类对象(子类拥有父类未拥有的函数)。
此原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来定义对象,而在运行时再确定其子类类型,用子类对象来替换父类对象。
使用里氏替换原则时需要注意,子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加新的子类来实现。从大局看多态就属于这个原则。

示例代码

public abstract class  Phone
{
    public abstract void Call();
}
 interface Android{ }
 interface IOS{ }
public class OnePlus : Phone, Android
{
    public override void Call()
    {
        Debug.Log($"{nameof(OnePlus)}进行通话。。。。。");
    }
}

public class Pixel : Phone, Android
{
    public override void Call()
    {
        Debug.Log($"{nameof(Pixel)}进行通话。。。。。");
    }
}
public class XiaoMi : Phone, Android
{
    public override void Call()
    {
        Debug.Log($"{nameof(XiaoMi)}进行通话。。。。。");
    }
}

public class Apple : Phone, IOS
{
    public override void Call()
    {
        Debug.Log($"{nameof(Apple)}进行通话。。。。。");
    }
}

不使用里氏替换,调用call函数需要为每个类型的手机写一个函数

    public void WantToCall_0(OnePlus phone)
    {
        phone.Call();
    }

    public void WantToCall_1(Pixel phone)
    {
        phone.Call();
    }

    public void WantToCall_2(XiaoMi phone)
    {
        phone.Call();
    }

    public void WantToCall_3(Apple phone)
    {
        phone.Call();
    }

使用里氏替换,只需要一个函数全部搞定,在后面的【桥接模式】会广泛用到的

    public void WantToCall_4(Phone phone)
    {
        phone.Call();
    }

[3]

3. 依赖倒置原则(Dependence Inversion Principle):
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。 依赖倒置原则的核心思想是面向接口编程。
采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,减少并行开发引起的风险,提高代码的可读性和可维护性。

什么是高层模块,什么是低层模块?
在项目中我们经常会有一些数学函数库,或者工具类(Log日志工具),这些封装好的工具会被我们业务逻辑模块经常调用,那么这些工具函数库就是高层模块,业务逻辑模块就是低层模块。

什么是细节,什么是抽象?
细节的意思就是具体的实现,例如上面里氏替换中打电话的例子,在不使用里氏替换的时候需要定义4种打电话函数,这就是依赖细节,其中的细节就是“OnePlus ”、“Pixel ”、“XiaoMi”、“Apple ”,反之抽象就是Phone 。

简例:华硕和微型都可使用不同型号的显卡,反之不同型号的显卡也可以使用在不同品牌的主板上。利用依赖倒置原则,这种规则的实现变得很简单也很灵活~

示例代码

//显卡
public interface IGraphicsCard
{
    void BeginWork(IMainboard mainboard);
}
//主板
public interface IMainboard
{
    void GetElectricity();
    void DrawPicture(IGraphicsCard graphicsCard);
}


public class NVIDIA_2018Ti : IGraphicsCard
{
    public NVIDIA_2018Ti(IMainboard mainboard)
    {
        BeginWork(mainboard);
    }
    public void BeginWork(IMainboard mainboard)
    {
        mainboard.GetElectricity();
        Debug.Log($"NVIDIA_2018Ti获取{mainboard.GetType()}电量后开始工作");
    }
}
public class NVIDIA_2018 : IGraphicsCard
{
    public NVIDIA_2018(IMainboard mainboard)
    {
        BeginWork(mainboard);
    }
    public void BeginWork(IMainboard mainboard)
    {
        mainboard.GetElectricity();
        Debug.Log($"NVIDIA_2018获取{mainboard.GetType()}电量后开始工作");
    }
}

//华硕主板
public class Asus : IMainboard
{
    public void DrawPicture(IGraphicsCard graphicsCard) { }
    public void GetElectricity() { }
}
//微型主板
public class MSI : IMainboard
{
    public void DrawPicture(IGraphicsCard graphicsCard) { }
    public void GetElectricity() { }
}

实现代码,这种2*2种模式的实现非常简单~

    public void DrawPicture()
    {
        IMainboard aSus = new Asus();
        aSus.DrawPicture(new NVIDIA_2018Ti(aSus));
        aSus.DrawPicture(new NVIDIA_2018(aSus));

        IMainboard mSI = new MSI();
        mSI.DrawPicture(new NVIDIA_2018Ti(mSI));
        mSI.DrawPicture(new NVIDIA_2018(mSI));
    }

[4]

4. 接口隔离原则(Interface Segregation Principle):
提供尽可能小的单独接口,而不要提供大的总接口。具体行为让实现类了解越少越好。
尽量细化接口,接口中的方法尽量少。也就是要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的约定,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
通俗的讲就是定义的接口尽量按照功能细分,比如打电话功能一个接口,上网一个接口,发短信一个接口。接口粒度小不仅职能明确,也不会因为使用某种职能而必须实现一些不必要的功能。

示例代码



[5]

5. 迪米特法则(Law Of Demeter)又称【最少知识原则】:
类与类之间的关系越密切,耦合度越大,只有降低类与类之间的耦合才符合设计模式;对于被依赖的类来说,无论逻辑多复杂都要尽量封装在类的内部。
每个对象都会与其他对象有耦合关系,我们称出现成员变量、方法参数、方法返回值中的类为直接的耦合依赖,而出现在局部变量中的类则不是直接耦合依赖,也就是说,不是直接耦合依赖的类最好不要作为局部变量的形式出现在类的内部。
一个对象对另一个对象知道的越少越好,即一个实体应当尽可能少的与其他实体发生相互作用,在一个类里降低引用其他类,尤其是局部变量的依赖类,能省则省。同时两个类不必彼此直接通信,那么这两个类就不必发生直接的相互作用。如果其中一个类需要调用另一个类的某一方法的话,可以通过第三者转发这个调用。

表达的意思是能用 private、protected的就不要用public,不要过多的暴露自己的内容,而且对应类与类之间的关系,尽量越少越好。后面讲到的门面模式中介者模式想表达的也是这个意思。

迪米特法则其根本思想,是强调了类之间的松耦合。类之间的耦合越弱,一个处于弱耦合的类被修改,对有关类造成波及的影响越小。

示例代码
注:这种情况就违背了迪米特法则。因为其他的类不需要这个Log扩展,这也就破坏了原来的结构,侵入性太强了。如果所有的扩展都是以Object为基准,那么调用函数的时候就会造成下拉函数条目过多。

public static class Exted
{
    public static void CustomerLog_Obj0(this GameObject Obj) { }

    public static void CustomerLog_Obj1(this object Obj) { }
    public static void CustomerLog_Obj2(this object Obj) { }

    public static void CustomerLog_Obj3(this object Obj) { }
    public static void CustomerLog_Obj4(this object Obj) { }
}

[6]

6. 开闭原则(Open Close Principle):
主要体现对扩展开放、对修改封闭,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。软件需求总是变化的,世界上没有一个软件是不变的,因此对软件设计人员来说,在不需要对原有系统进行修改的情况下,实现灵活的系统扩展。
例如通过模板方法模式策略模式进行重构,实现对修改封闭,对扩展开放的设计思路。
封装变化,是实现开闭原则的重要手段,对于经常发生变化的状态,将其封装为一个抽象,但拒绝滥用抽象,只将经常变化的部分进行抽象。
通俗的讲在功能变动的时候,尽量以增量补丁的形式更改,也就是原来代码保持不变的同时进行更改。


[7]

7. 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP):
整个设计模式就是在讲如何合理安排类与类之间的组合/聚合。在一个新的对象里面通过关联关系,使一些已有的对象成为新对象的一部分,新对象通过委派调用已有对象的方法,达到复用其已有功能的目的。也就是,要尽量使用类的合成复用,不要使用继承。
继承复用破坏数据封装性,将基类的实现细节全部暴露给了派生类,基类的内部细节常常对派生类是透明的。白箱复用,虽然简单,但不安全,不能在程序的运行过程中随便改变。基类的实现发生了改变,派生类的实现也不得不改变;从基类继承而来的派生类是固定的,不能在运行时发生改变,因此没有足够的灵活性。
组合/聚合复用原则可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。
核心思想:组合优于继承


  1. 单一职责原则脚注结尾

  2. 里氏替换原则脚注结尾

  3. 依赖倒置原则脚注结尾

  4. 接口隔离原则脚注结尾

  5. 迪米特法则脚注结尾

  6. 开闭原则脚注结尾

  7. 组合/聚合复用原则脚注结尾

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

推荐阅读更多精彩内容

  • 面向对象的3个基本要素: 封装、继承、多态 面向对象的5个基本设计原则: 单一职责原则(Single-Respos...
    badcyc阅读 846评论 0 4
  • 本文出自《Android源码设计模式解析与实战》中的第一章。 1、优化代码的第一步——单一职责原则 单一职责原则的...
    MrSimp1e0阅读 1,757评论 1 13
  • 单一职责原则 (SRP) 全称 SRP , Single Responsibility Principle 单一职...
    米莉_L阅读 1,752评论 2 5
  • 手机和电脑 儿子对手机和电脑有了一定的自觉性和自控力。 今天是腊月二十四,是北方的小年,儿子的晚自习时间和明天即周...
    雪霁晴空喜迎春阅读 197评论 0 2
  • 微习惯是什么呢?它并非是像我这样明明平时七点起床,而今天早晨为了昨天立下的flag五点半就起来了,它其实是每天都比...
    麻麻不完美阅读 383评论 0 0