面向对象的六大原则

面向对象六大原则
  1. 单一职责原则--SRP(Single Responsibility Principles)

    • 每个类都有唯一的职责

      设计一个类,首先要明白这个类要去干什么,即为这个类的职责。

      发动机是一个复杂的系统,但是无论它多复杂,它也只有一个职责:输出动力。

  2. 开闭原则--OCP(Open Close Principles)

    • 对扩展开放,对修改关闭

      即设计的类在满足其职责的基础上,在不修改类中代码的情况下,能够良好的扩展.

      单一职责原则和开闭原则一起,构成了设计一个类的指导思想

  3. 里式替换原则--LSP(Liskov Substitution Principles)

    • 所有的基类出现的地方,都能透明的被其子类替换

      里式替换原则是对扩展开放的基础,A依赖于B,那么A就是可以使用B的子类,来实现功能,从而让A具有了扩展性。

      看下面的例子:

    /**
     * 厨师
     */
     public class Chef {
     private Cai cai;
    ​
     public void cook() {
     if (cai == null) {
     System.out.print("我是厨师,我没菜做,我在和Lance吹牛逼...\n");
     } else {
     System.out.print("我是厨师,我在做" + cai.getNameToCook()+"\n");
     }
     }
    ​
     public void setCai(Cai cai){
     this.cai = cai;
     }
     }
    ​
    ​
     /**
     * 菜的父类
     */
     public class Cai {
     public String getNameToCook() {
     return "菜";
     }
     }
    ​
     /**
     * 辣椒炒肉
     */
     public class LaJiaoChaoRou extends Cai {
     public String getNameToCook() {
     return "辣椒炒肉";
     }
     }
    ​
    ​
     /**
     * 西红柿炒蛋
     */
     public class XiHongShiChaoDan extends Cai {
     public String getNameToCook() {
     return "西红柿炒蛋";
     }
     }
    ​
    ​
     @Test
     public void testChef(){
     Chef chef = new Chef();
     chef.cook();
    ​
     Cai cai = new Cai();
     chef.setCai(cai);
     chef.cook();
    ​
     XiHongShiChaoDan xiHongShiChaoDan = new XiHongShiChaoDan();
     chef.setCai(xiHongShiChaoDan);
     chef.cook();
    ​
     LaJiaoChaoRou laJiaoChaoRou = new LaJiaoChaoRou();
     chef.setCai(laJiaoChaoRou);
     chef.cook();
     }
    ​
     //打印结果如下
     我是厨师,我没菜做,我在和Lance吹牛逼...
     我是厨师,我在做菜
     我是厨师,我在做西红柿炒蛋
     我是厨师,我在做辣椒炒肉

可以看出,由于里式替换原则的存在,厨师类Chef虽然依赖于Cai这个类,但是它一样可以使用Cai的子类,做出西红柿炒蛋,辣椒炒肉等等。

  1. 依赖倒置原则--DIP(Dependence Inversion Principles)

    • 高层模块不应该依赖低层模块,他们都应该依赖其抽象

    • 抽象不应该依赖细节,细节应该依赖抽象

      这是一个解耦原则,依据该原则对类进行设计,能够使类与被其使用的其他类之间最大限度的解耦。

      什么是抽象? ​ 接口或者抽象类是抽象 什么是细节? ​ 实现类是细节

      在里式替换的列子中,类Chef依赖于类Cai,Chef是高层模块,Cai是低层模块,高层模块直接依赖了低层模块。这样做有什么坏处呢?即Chef的cook()方法必须使用Cai或者Cai的子类,其他类即使有getNameToCook()这个方法,但是也一无法提供给A使用,例如下面的类

   public class CaiBaoZi{
     public String getNameToCook(){
     return "菜包子";
     }
    }

CaiBaoZi这个类虽然有getNameOfFood()方法,但是它不是Cai的子类,无法被Chef使用 也就是说,低层模块Cai限制了高层模块Chef的能力,这就是耦合对一个类带来的伤害。 下面我们对上面的例子做一下修改:

   /**
     * 提供菜名
     */
     public interface IName{
     String getNameToCook();
     }
    ​
     /**
     * 厨师
     */
     public class Chef {
     private IName cookedObject;
    ​
     public void cook() {
     if (cookedObject == null) {
     System.out.print("我是厨师,我没菜做,我在和Lance吹牛逼...\n");
     } else {
     System.out.print("我是厨师,我在做" + cookedObject.getNameToCook()+"\n");
     }
     }
    ​
     public void setCai(IName cookedObjec){
     this.cookedObject = cookedObjec;
     }
     }
    ​
    ​
     /**
     * 菜的父类
     */
     public class Cai implements IName {
     public String getNameToCook() {
     return "菜";
     }
     }
    ​
     /**
     * 辣椒炒肉
     */
     public class LaJiaoChaoRou extends Cai {
     public String getNameToCook() {
     return "辣椒炒肉";
     }
     }
    ​
    ​
     /**
     * 西红柿炒蛋
     */
     public class XiHongShiChaoDan extends Cai {
     public String getNameToCook() {
     return "西红柿炒蛋";
     }
     }
    ​
     /**
     * 菜包子
     */
     public class CaiBaoZi implements IName{
     @Override
     public String getNameToCook() {
     return "菜包子";
     }
     }
    ​
     @Test
     public void testChef(){
           Chef chef = new Chef();
           chef.cook();
    ​
           Cai cai = new Cai();
           chef.setCai(cai);
           chef.cook();
    ​
           XiHongShiChaoDan xiHongShiChaoDan = new XiHongShiChaoDan();
           chef.setCai(xiHongShiChaoDan);
           chef.cook();
    ​
           LaJiaoChaoRou laJiaoChaoRou = new LaJiaoChaoRou();
           chef.setCai(laJiaoChaoRou);
           chef.cook();
    ​
           CaiBaoZi caiBaoZi = new CaiBaoZi();
           chef.setCai(caiBaoZi);
           chef.cook();
     }
    ​
     //打印结果如下:
     我是厨师,我没菜做,我在和Lance吹牛逼...
     我是厨师,我在做菜
     我是厨师,我在做西红柿炒蛋
     我是厨师,我在做辣椒炒肉
     我是厨师,我在做菜包子

通过上面的更改,Chef可以使用CaiBaoZi了,显然CaiBaoZi不是Cai的子类,Chef的扩展性得到更大的发展,对应到依赖倒置原则的定义: Chef是高层模块,是细节 Cai是低层模块,是细节 CaiBaoZi也是低层模块,是细节 IName就是抽象 Chef、Cai、CaiBaoZi都依赖于IName 有了IName这个抽象,A就再也不会受到具体的对象的限制了,只要是实现了IName接口的类,都能够被Chef使用,Chef具有了无限可能,可以说是可以使用任意的类来完成其cook()功能。

那有同学就说了,我有一个第三方SDK提供的类HongJiu:

   public class HongJiu{
       public String getNameOfDrink(){
           return "红酒";
       }
   }

这个类封装在SDK中,我没办法去更改,怎么让被Chef使用呢?我们这么干:

    public class HongJiuForChef implements IName{
           HongJiu hongJiu = new HongJiu();
    ​
           @Override
           public String getNameToCook() {
                 return hongJiu.getNameOfDrink();
           }
     }
    ​
     @Test
     public void testChef() {
           Chef chef = new Chef();
           chef.cook();
    ​
           Cai cai = new Cai();
           chef.setCai(cai);
           chef.cook();
    ​
           XiHongShiChaoDan xiHongShiChaoDan = new XiHongShiChaoDan();
           chef.setCai(xiHongShiChaoDan);
           chef.cook();
    ​
           LaJiaoChaoRou laJiaoChaoRou = new LaJiaoChaoRou();
           chef.setCai(laJiaoChaoRou);
           chef.cook();
    ​
           CaiBaoZi caiBaoZi = new CaiBaoZi();
           chef.setCai(caiBaoZi);
           chef.cook();
    ​
           HongJiuForChef hongJiuForChef = new HongJiuForChef();
           chef.setCai(hongJiuForChef);
           chef.cook();
     }
    ​
     //打印结果
     我是厨师,我没菜做,我在和Lance吹牛逼...
     我是厨师,我在做菜
     我是厨师,我在做西红柿炒蛋
     我是厨师,我在做辣椒炒肉
     我是厨师,我在做菜包子
     我是厨师,我在做红酒

牛逼了,我们的厨师能够做红酒了,这就是高层模块和低层模块的解耦。

那么问题来了,我怎么知道我要使用一个什么样的抽象呢? 答案是:根据高层模块完成功能H,需要低层模块提供什么样的功能L,要使用的抽象,就是这个功能L的抽象。 拿上面的例子来说,Chef要顺利的完成cook()方法需要什么?需要外界提供一个名字,那么我们对这个需求进行抽象,就抽象出来了一个接口IName,只要实现了这个接口的模块,就都可以被Chef作为低层模块使用了

  1. 接口隔离原则--ISP(Interface Segregation Principles)

    • 类不应该依赖它不需要的接口。

    • 类间的依赖关系,应该建立在最小接口上。

      了解了以上四个原则之后,我们已经能够确定,接口即抽象对一个良好的设计的重要性。而接口隔离原则就是设计一个良好的抽象的指导原则。

看下面的例子:

     public interface B {
           void hear();
           void talk();
           void read();
           void write();
     }
    ​
     public class A implements B{
    ​
           @Override
           public void hear() {
                 System.out.print("A hear");
           }
    ​
           @Override
           public void talk() {
                 System.out.print("A talk");
           }
    ​
           @Override
           public void read() {
                 //A 不需要这个方法
           }
    ​
           @Override
           public void write() {
                 //A 不需要这个方法
           }
     }
    ​
     public class C implements B{
    ​
           @Override
           public void hear() {
                 System.out.print("C hear");
           }
    ​
           @Override
           public void talk() {
                 //C 不需要这个方法
           }
    ​
           @Override
           public void read() {
                 System.out.print("C read");
           }
    ​
           @Override
           public void write() {
                 System.out.print("C write");
           }
     }

可以看出,A被迫实现了它不需要的方法read、write,而C被迫实现它不需要的方法talk。方法就是实现的功能,本来A是没有read和write的功能的,但是由于实现了接口B,它就多出了这两个功能,这是和设计的初衷所不符的,也容易造成使用者的误解,对于后期的必要的修改也会造成干扰,例如我要对A增加一项功能run,如果将该功能抽象到B接口中,那么C也会被迫跟着修改,这显然不是一个良好的设计。

下面是修改后的设计:

     public interface B {
           void hear();
     }
    ​
     public interface B2A extends B{
           void talk();
           void run();
     }

     public interface B2C extends B{
           void read();
           void write();
     }

     public class A implements B2A{
    ​
           @Override
           public void hear() {
                 System.out.print("A hear");
           }
    ​
           @Override
           public void talk() {
                 System.out.print("A talk");
           }
    ​
           @Override
           public void run() {
                 System.out.print("A run");
           }
     }
    ​
     public class C implements B2C{
    ​
           @Override
           public void hear() {
                  System.out.print("C hear");
           }
    ​
           @Override
           public void read() {
                  System.out.print("C read");
           }  
    ​
           @Override
           public void write() {
                  System.out.print("C write");
           }

     }

修改之后,将接口进行了拆分隔离,再需要修改的时候,公共方法就在接口B中修改,其他的根据需要分别在接口B2A和接口B2C中修改,再也不会对其他的类造成不必要的影响;虽然接口的数量增加了,但是无论是对于以后代码的维护拓展,还是对于使用者的理解,都是非常友好的。

当然,对于最小接口的设计要注意把控好度,拆分的粒度不够细腻,就会维护困难,拆分的太细又会大量的增加接口数量,也会造成困扰。

  1. 迪米特原则(最少知识原则)--LOD(Law of Demeter)

    • 一个对象,应该对其他的对象有最少的了解。

    • 维基百科上定义如下: 得墨忒耳定律Law of Demeter,缩写LoD)亦称为“最少知识原则(Principle of Least Knowledge)”,是一种软件开发的设计指导原则,特别是面向对象的程序设计。得墨忒耳定律是松耦合的一种具体案例。该原则是美国东北大学在1987年末在发明的,可以简单地以下面任一种方式总结:

      1. 每个单元对于其他的单元只能拥有有限的知识:只是与当前单元紧密联系的单元;

      2. 每个单元只能和它的朋友交谈:不能和陌生单元交谈;

      3. 只和自己直接的朋友交谈。

      这个原理的名称来源于希腊神话中的农业女神,孤独的得墨忒耳。

      很多面向对象程序设计语言用"."表示对象的域的解析算符,因此得墨忒耳定律可以简单地陈述为“只使用一个.算符”。因此,a.b.Method()违反了此定律,而a.Method()不违反此定律。一个简单例子是,人可以命令一条狗行走(walk),但是不应该直接指挥狗的腿行走,应该由狗去指挥控制它的腿如何行走。

    • 百度百科上有如下描述:

      狭义的迪米特法则

      如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

      朋友圈的确定

      “朋友”条件:

      1)当前对象本身(this)

      2)以参量形式传入到当前对象方法中的对象

      3)当前对象的实例变量直接引用的对象

      4)当前对象的实例变量如果是一个聚集,那么聚集中的元素也都是朋友

      5)当前对象所创建的对象

      任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”;否则就是“陌生人”。

      狭义的迪米特法则的缺点:

      在系统里造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的商务逻辑无关。

      遵循类之间的迪米特法则会是一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。

      门面模式和调停者模式实际上就是迪米特法则的应用。

      广义的迪米特法则在类的设计上的体现:

      优先考虑将一个类设置成不变类。

      尽量降低一个类的访问权限。

      谨慎使用Serializable。

      尽量降低成员的访问权限。

你可能感兴趣的:(面向对象的六大原则)