单一职责原则
里氏替换原则
依赖倒置原则
接口隔离原则
迪米特原则
开闭原则
首先要提的是:六大原则的灵魂是面向接口,以及如何合理地运用接口玩知乎,这是知乎er的职责
打乒乓球,这是业余运动爱好者的职责
工作,这是“普普通通上班族”的职责(似乎暴露了什么)
OK,正如你所见,既然我们要遵循单一职责,那么怎么做呢?当然是要拆分了 我们要根据接口去拆,拆分成三个接口去约束People类(不是把People类拆了哈) // 知乎er public interface ZhiHuer { public void playZhiHu(); } // 上班族 public interface OfficeWorkers { public void work(); } // 业余运动爱好者 public interface AmateurPlayer { public void doSports(); } 然后在People中继承这几个接口 public class People implements ZhiHuer,AmateurPlayer,OfficeWorkers{ public void playZhiHu () { 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(); ZhiHuer zhiHuer = new People(); zhiHuer.playZhiHu(); // 输出:玩知乎 OfficeWorkers workers = new People(); workers.work(); // 输出: 工作 AmateurPlayer players = new People(); players.doSports(); // 输出:打乒乓球 } } 备注:这个原则不是死的,而是活的,在实际开发中当然还要和业务相结合,不会纯粹为了理论贯彻单一职责,就像数据库开发时候,不会完全遵循“三大范式”,而是允许一定冗余的子类
public class Son extends Father { @Override public void work() { // 我实现了爸爸的work方法,旦我什么也不做! } } 子类虽然表面上实现了父类的方法,但是他实际上并没有实现父类要求的逻辑。里氏替换原则要求我们避免这种“塑料父子情”,如果出现子类不得不脱离父类方法范围的情况, 采取其他方式处理,详情参考《 设计模式之禅》 (其实个人觉得《禅》的作者其实讲的“父类”其实着重指的是抽象类)高层的模块不应该依赖于低层的模块,这两者都应该依赖于其抽象
抽象不应该依赖细节
细节应该依赖抽象
换句话说, 高层次的类不应该依赖于,或耦合于低层次的类,相反,这两者都应该通过相关的接口去实现。要面向接口编程,而不是面向实现编程,所以编程的时候并不是按照符合我们逻辑思考的“依赖关系”去编程掉的,这种不符,就是依赖倒置 举个例子, 类好比是道德,接口好比是法律。 道德呢,有上层的也有下层的,春秋时代,孔圣人提出了上层道德理论:“仁”的思想,并进一步细化为低层道德理论:“三纲五常”(高层模块和底层模块),想要以此规约众生,实现天下大同。可是奈何民众的道德终究还是靠不住(没有接口约束的类,可能被混乱修改),何况道德标准是会随物质经济的变化而变化的,孔子时代和我们今天的已经大有不同了。(类可能会发生变化) 所以才需要法律来进一步框定和要求道德。(我们用接口来约束和维护“类”,就好比用法律来维护和规约道德一样。)假如未来道德伦理的标杆发生了变化,肯定是先修缮法律,然后再次反向规制和落实道德(面向接口编程,而不是面向实现编程)。 我们看下下面没有遵循依赖倒置原则的代码是怎样的,我们设计了两个类: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也能处理接口要足够细化,当然了,这会让接口的数量变多,但是每个接口会具有更加明确的功能
在1的前提下,类应该依赖于“最小”的接口上
举个例子, 中秋节其实只过了一个多月,现在假设你有一大盒“五仁月饼”想带回家喂猪,但是无奈的是包包太小放不下,而且一盒沉重的月饼对瘦弱的你是个沉重的负担。这个时候,我们可以把月饼盒子拆开,选出一部分自己需要(wei zhu)的月饼,放进包包里就好啦,既轻便又灵活。 还是上代码吧,比如我们有这样一个知乎er的接口,里面涵盖了一些可能的行为。许多知乎用户还会保持友善,同时根据自己的专业知识认真写文章。但也有少数的人会把生活中的负面能量带到网络中 public interface ZhiHuer { // 认真撰文 public void seriouslyWrite(); // 友好评论 public void friendlyComment(); // 无脑抬杠 public void argue(); // 键盘攻击 public void keyboardAttack (); } 我们发现,这个接口可以进一步拆分成两个接口,分别命名为PositiveZhiHuer,NegativeZhihuer。这样,我们就把接口细化到了一个合理的范围 public interface PositiveZhiHuer { // 认真撰文 public void seriouslyWrite(); // 友好评论 public void friendlyComment(); } public interface NegativeZhihuer { // 无脑抬杠 public void argue(); // 键盘攻击 public void keyboardAttack (); } >> 备注:妥善处理 单一职责原则 和 接口隔离原则的关系 事实上,有两点要说明一下单一职责原则和接口隔离原则虽然看起来有点像,好像都是拆分,但是其实侧重点是不一样的,“职责”的粒度其实是比“隔离接口”的粒度要大的
基于1中阐述的原因,其实 单一职责原则 和 接口隔离原则是可能会产生冲突的,因为接口隔离原则要求粒度尽可能要细,但是单一职责原则却不同,它要求拆分既不能过粗,但也不能过细,如果把原本单一职责的接口分成了“两个0.5职责的接口”,那么这就是单一职责所不能允许的了。
当两者冲突时,优先遵循 单一职责原则
一个类只和朋友类交流,朋友类指的是出现在成员变量、方法的输入输出参数中的类
一个类不和陌生类交流,即没有出现在成员变量、方法的输入输出参数中的类
所谓的“不交流”,就是不要在代码里看到他们 我们改造一下上面的代码 // 我朋友 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(); }; }原则不是死板的而是灵活的
一些原则其实是存在一定的冲突的,重要的是权衡,是掌握好度
六大原则是23种设计模式的灵魂,六大原则指导了设计模式,设计模式体现了六大原则