初探设计模式六大原则

前言

我想用贴近生活的语句描述一下本身对六种原则的理解。也就是不作专业性的阐述,而是描述一种本身学习后的理解和感觉,由于能力通常并且水平有限,也许举的例子不尽稳当,还请谅解
 
本来我是想用JavaScript编写的,可是JavaScript到如今尚未提出接口的概念,而用TypeScript写又感受普及度还不算特别高,因此仍是决定用Java语言编写

目录

设计模式有六大原则
  • 单一职责原则linux

  • 里氏替换原则程序员

  • 依赖倒置原则数据库

  • 接口隔离原则编程

  • 迪米特原则ubuntu

  • 开闭原则windows

首先要提的是:六大原则的灵魂是面向接口,以及如何合理地运用接口

P1.单一职责原则(Single Responsibility Principle)

应该有且仅有一个缘由引发类的变动(There should never be more than one reason for a class to change)。
 
为了达到这个目标,咱们须要对类和业务逻辑进行拆分。划分到合适的粒度,让这些各自执行单一职责的类,各司其职。 让每一个类尽可能行使单一的功能,实现“高内聚”,这个结果也使得类和类之间不会有过多冗余的联系,从而“低耦合”。
 
好比咱们如今有了这样一个类
public class People {
    public void playCnBlogs () {
        System.out.println("刷博客");
    }
    public void doSports () {
        System.out.println("打乒乓球");
    }
    public void work () {
        System.out.println("工做");
    }
}

 

如今看起来有点混乱,由于这个类里面混合了三个职责:
  • 刷博客园,这是博主的职责设计模式

  • 打乒乓球,这是业余运动爱好者的职责网络

  • 工做,这是“普普统统上班族”的职责(彷佛暴露了什么)架构

OK,正如你所见,既然咱们要遵循单一职责,那么怎么作呢?固然是要拆分了
 
咱们要根据接口去拆,拆分红三个接口去约束People类(不是把People类拆了哈)
// 知乎er
public interface Blogger {
    public void playCnBlogs();
}
// 上班族
public interface OfficeWorkers {
    public void work();
}
// 业余运动爱好者
public interface AmateurPlayer {
    public void doSports();
}

 

而后在People中继承这几个接口
public class People implements Blogger,AmateurPlayer,OfficeWorkers{
    public void playCnBlogs () {
        System.out.println("刷博客园");
    }
    public void doSports () {
        System.out.println("打乒乓球");
    }
    public void work () {
        System.out.println("工做");
    }
}

 

最后建立实例运行一下
public class Index {
    public static  void main (String args []) {
        People people = new People();
        Blogger blogger = new People();
        blogger.playCnBlogs(); // 输出:刷博客园
        OfficeWorkers workers = new People();
        workers.work(); // 输出: 工做
        AmateurPlayer players = new People();
        players.doSports(); // 输出:打乒乓球
    }
}

  

备注:这个原则不是死的,而是活的,在实际开发中固然还要和业务相结合,不会纯粹为了理论贯彻单一职责,就像数据库开发时候,不会彻底遵循“三大范式”,而是容许必定冗余的

P2.里氏替换原则(liskov substitution principle)

里氏替换原则,一种比较好的理解方式是: 全部引用基类的地方必须能透明地使用其子类的对象。 换句话说,子类必须彻底实现父类的功能。凡是父类出现的地方,就算彻底替换成子类也不会有什么问题。
 
以上描述来自《设计模式之禅》,刚开始看的时候我有些疑惑,由于一开始以为:只要继承了父类不均可以调用父类的方法吗?为何还会有里氏替换所要求的:子类必须彻底实现父类的功能呢, 难不成继承的子类还能够主动“消除”父类的方法?
 
还真能够,请看
父类
public abstract class Father {
    // 认真工做
    public abstract void work();
    // 其余方法
}

ide

public class Son extends Father {
    @Override
    public void work() {
     // 我实现了爸爸的work方法,旦我什么也不作!
    }
}

 

子类虽然表面上实现了父类的方法,可是他实际上并无实现父类要求的逻辑。里氏替换原则要求咱们避免这种“塑料父子情”,若是出现子类不得不脱离父类方法范围的状况, 采起其余方式处理,详情参考《设计模式之禅》

(其实我的以为《禅》的做者其实讲的“父类”其实着重指的是抽象类)

P3.依赖倒置原则 (dependence inversion principle)

不少文章阐述依赖倒置原则都会阐述为三个方面
  • 高层的模块不该该依赖于低层的模块,这二者都应该依赖于其抽象

  • 抽象不该该依赖细节

  • 细节应该依赖抽象

换句话说, 高层次的类不该该依赖于,或耦合于低层次的类,相反,这二者都应该经过相关的接口去实现。要面向接口编程,而不是面向实现编程,因此编程的时候并非按照符合咱们逻辑思考的“依赖关系”去编程掉的,这种不符,就是依赖倒置
 
举个例子, 类比如是道德,接口比如是法律。
 
道德呢,有上层的也有下层的,春秋时代,孔圣人提出了上层道德理论:“仁”的思想,并进一步细化为低层道德理论:“三纲五常”(高层模块和底层模块),想要以此规约众生,实现天下大同。但是奈何民众的道德终究仍是靠不住(没有接口约束的类,可能被混乱修改),况且道德标准是会随物质经济的变化而变化的,孔子时代和咱们今天的已经大有不一样了。(类可能会发生变化) 因此才须要法律来进一步框定和要求道德。(咱们用接口来约束和维护“类”,就比如用法律来维护和规约道德同样。)假如将来道德伦理的标杆发生了变化,确定是先修缮法律,而后再次反向规制和落实道德(面向接口编程,而不是面向实现编程)。
 
咱们看下下面没有遵循依赖倒置原则的代码是怎样的,咱们设计了两个类:Coder类和Linux类,而且让它们之间产生交互:Coder对象的develop方法接收Linux对象而且输出系统名
// 底层模块1:开发者
public class Coder {
    public void develop (Linux linux) {
        System.out.printf("开发者正在%s系统上进行开发%n",linux.getSystemName());
    }
}
// 底层模块2:Linux操做系统
public class Linux {
    public String name;
    public Linux(String name){
        this.name = name;
    }
    public String getSystemName () {
        return this.name;
    }
}
// 高层模块
public class Index {
    public static  void main (String args []) {
        Coder coder = new Coder();
        Linux ubuntu = new Linux("ubuntu系统"); // ubuntu是一种linux操做系统
        coder.develop(ubuntu);
    }
}

 

输出
开发者正在ubuntu系统系统上进行开发 

可是咱们能发现其中的问题:

操做系统不只仅有Linux家族,还有Windows家族,若是咱们如今须要让开发者在windows系统上写代码怎么办呢? 咱们可能要新建一个Windows类,可是问题来了,Code.develop方法的入参数类型是Linux,这样以来改造就变得很麻烦。
 
让咱们利用依赖倒置原则改造一下,咱们定义OperatingSystem接口,将windows/Linux抽象成操做系统,这样,OperatingSystem类型的入参就能够接收Windows或者Linux类型的参数了
// 程序员接口
public interface Programmer {
    public void develop (OperatingSystem OS);
}
// 操做系统接口
public interface OperatingSystem {
    public String getSystemName ();
}
// 低层模块:Linux操做系统
public class Linux implements  OperatingSystem{
    public String name;
    public Linux (String name) {
        this.name = name;
    }
    @Override
    public String getSystemName() {
        return this.name;
    }
}
// 低层模块:Window操做系统
public class Window implements OperatingSystem {
    String name;
    public Window (String name) {
        this.name = name;
    }
    @Override
    public String getSystemName() {
        return this.name;
    }
}
// 低层模块:开发者
public class Coder implements Programmer{
    @Override
    public void develop(OperatingSystem OS) {
        System.out.printf("开发者正在%s系统上进行开发%n",OS.getSystemName());
    }
}
// 高层模块:测试用
public class Index {
    public static  void main (String args []) {
        Programmer coder = new Coder();
        OperatingSystem ubuntu = new Linux("ubuntu系统"); // ubuntu是一种linux操做系统
        OperatingSystem windows10 = new Window("windows10系统"); // windows10
        coder.develop(ubuntu);
        coder.develop(windows10);
    }
}

 

虽然接口的加入让代码多了一些,可是如今扩展性变得良好多了,即便有新的操做系统加入进来,Coder.develop也能处理

P4. 接口隔离原则(interface segregation principle)

接口隔离原则的要求是:类间的依赖关系应该创建在最小的接口上。 这个原则又具体分为两点
  1. 接口要足够细化,固然了,这会让接口的数量变多,可是每一个接口会具备更加明确的功能

  2. 在1的前提下,类应该依赖于“最小”的接口上

 
举个例子, 中秋节其实只过了一个多月,如今假设你有一大盒“五仁月饼”想带回家喂猪,可是无奈的是包包过小放不下,并且一盒沉重的月饼对瘦弱的你是个沉重的负担。这个时候,咱们能够把月饼盒子拆开,选出一部分本身须要(wei zhu)的月饼,放进包包里就好啦,既轻便又灵活。
 
仍是上代码吧,好比咱们有这样一个Blogger的接口,里面涵盖了一些可能的行为。大多数博客用户会保持友善,同时根据本身的专业知识认真写文章。但也有少数的人会把生活中的负面能量带到网络中
public interface Blogger {
    // 认真撰文
    public void seriouslyWrite();
    // 友好评论
    public void friendlyComment();
    // 无脑抬杠
    public void argue();
    // 键盘攻击
    public void keyboardAttack ();
}

 

咱们发现,这个接口能够进一步拆分红两个接口,分别命名为PositiveBlogger,NegativeBlogger。这样,咱们就把接口细化到了一个合理的范围
public interface PositiveBlogger {
    // 认真撰文
    public void seriouslyWrite();
    // 友好评论
    public void friendlyComment();
}

public interface NegativeBlogger {
    // 无脑抬杠
    public void argue();
    // 键盘攻击
    public void keyboardAttack ();
}
 
>> 备注:妥善处理 单一职责原则 和 接口隔离原则的关系
事实上,有两点要说明一下
  1. 单一职责原则和接口隔离原则虽然看起来有点像,好像都是拆分,可是其实侧重点是不同的,“职责”的粒度实际上是比“隔离接口”的粒度要大的

  2. 基于1中阐述的缘由,其实 单一职责原则 和 接口隔离原则是可能会产生冲突的,由于接口隔离原则要求粒度尽量要细,可是单一职责原则却不一样,它要求拆分既不能过粗,但也不能过细,若是把本来单一职责的接口分红了“两个0.5职责的接口”,那么这就是单一职责所不能容许的了。

  3. 当二者冲突时,优先遵循 单一职责原则

P5.迪米特原则 (law of demeter)

迪米特原则又叫最少知道原则,在实现功能的前提下,一个对象接触的其余对象应该尽量少,也即类和类之间的耦合度要低。
 
举个例子, 咱们常常说要“减小无效社交”,不要老是一昧的以交朋友的数量衡量本身的交际能力,不然会让本身很累的,也会难以打理好复杂的人际关系。对于并不很外向的人,多数时候和本身有交集的朋友交往就能够了。
 
咱们看下代码:
有以下场景,如今你和你的朋友想要玩一个活动,也许是斗地主等游戏,这个时候须要再喊一我的,因而你让你的朋友帮你再叫一我的,有代码以下
// 个人直接朋友
public class MyFriend {
    // 找他的朋友
    public void findHisFriend (FriendOfMyFriend fof) {
      System.out.println("这是朋友的朋友:"+ fof.name);
    }
}

// 朋友的朋友,但不是个人朋友
public class FriendOfMyFriend {
    public String name;
    public FriendOfMyFriend(String name) {
      this.name = name;
    }
}

//
public class Me {
    public void findFriend (MyFriend myFriend) {
      System.out.println("我找我朋友");
      // 注意这段代码
      FriendOfMyFriend fmf = new FriendOfMyFriend("陌生人");
      myFriend.findHisFriend(fmf);
    };
}

 

这时咱们发现一个问题,你和你朋友的朋友并不认识,可是他却出如今了你的“找朋友”的动做当中(在findFriend方法内),这个时候,咱们认为这违反了迪米特原则(最少知道原则),迪米特原则咱们对于对象关系的处理,要减小“无效社交”,具体原则是
  • 一个类只和朋友类交流,朋友类指的是出如今成员变量、方法的输入输出参数中的类

  • 一个类不和陌生类交流,即没有出如今成员变量、方法的输入输出参数中的类

所谓的“不交流”,就是不要在代码里看到他们
 
咱们改造一下上面的代码
// 我朋友
public class MyFriend {
    public void findHisFriend () {
        FriendOfMyFriend fmf = new FriendOfMyFriend("陌生人");
        System.out.println("这是朋友的朋友:"+ fmf.name);
    }
}
// 朋友的朋友,但不是个人朋友
public class FriendOfMyFriend {
    public String name;
    public FriendOfMyFriend(String name) {
        this.name = name;
    }
}

//
public class Me {
    public void findFriend (MyFriend myFriend) {
        System.out.println("我找我朋友");
        myFriend.findHisFriend();
    };
}

 

P6. 开闭原则(open closed principle)

开闭原则的意思是,软件架构要:对修改封闭,对扩展开放
 
举个例子
 
好比咱们如今在玩某一款喜欢的游戏,A键攻击,F键闪现。这个时候咱们想,若是游戏能额外给我定制一款“K”键,残血时解锁从而一击OK对手完成5杀,那岂不美哉,这就比如是“对扩展开放”。
 
可是呢,若是游戏忽然搞个活动,把闪现/攻击/技能释放的键盘统统换个位置,给你一个“双十一的惊喜”,这恐怕就给人带来惨痛的回忆了。因此咱们但愿已有的结构不要动,也不能动,要“对修改封闭”
(本人不玩游戏,这些是本身查到的,若是错误还请指正)

总结

  1. 原则不是死板的而是灵活的

  2. 一些原则实际上是存在必定的冲突的,重要的是权衡,是掌握好度

  3. 六大原则是23种设计模式的灵魂,六大原则指导了设计模式,设计模式体现了六大原则

就像不少人说的,其实设计模式是一种思想,关键的仍是怎样和业务结合起来,我也刚学习不久呢,若是前辈们有什么好的看法,还请在评论区指点一下,不胜感激 

相关文章
相关标签/搜索