面向对象的六大原则

1,393 阅读10分钟

前言

我们都知道面向对象有六大原则,23种设计模式。它们可以指导我们如何写出更加优秀的代码。六大原则是基础,我们面向对象编程应该尽量遵从这六大原则,才能写出优秀的代码。

23种设计模式是前人为我们总结出的解决某一类问题的办法,通过使用这些模式,我们可以更好的解决这一类问题。当然 23 种设计模式的设计前提也是建立在六大原则基础之上的。

目录

六大原则概念

六大原则是面向对象的六大原则,也就是说,我们在编写面向对象语言的时候,只有遵守了这六大原则才能写出真正的面向对象。才能拥有面向对象的思想。我们写的代码符合了这六大原则,有助有提高我们代码的质量,写出可扩展、可维护、可读性好、低耦合、高内聚的代码。

当然不符合这些原则,同样也是可以写代码的,只不过写出的代码质量就没有保证了,只能说勉强算是个代码。这好比做人一样,人也是有原则的,符合了这些原则,那就可以做一个堂堂正正的好人,不符合这些原则,也能活着。但是当不断的触犯原则,最后成了一个没有原则的人了,那么结果显然可见。如果随着程序不断的变大,代码不断的没有原则,那么最终的结果就是你的程序无法进行下一步维护了。

总之我们在写代码的时候要尽量符合这些原则!才能写出高质量代码!

单一职责

单一职责是我们优化代码的第一步

概念

概念:就一个类而言,应该仅有一个引起它变化的原因

概念可能不太好懂,简单来说就是一个类中应该是一组相关性很高的函数、数据的封装。

下面举例子:

public class Activity{
  
  // 请求网络加载
  public void requestNet(){
    String url = editText.getText();
    String parmas = editText.getText();
    // 判断是否符合某个条件
    if(xx){
      
    }
    // 继续判断
    if(xxx){
      
    }
    .... 等等省略1000行
  }
  
  class Adapter{
    
  }
  
  // 数据类
  class Data{
    
  }
  
  class Xxx{
    
  }
  
  .....
}

像上面的例子就是一个很好的反例代表,把所有的职责全部放到了 Activity 中,把所有的函数功能都放到了 requestNet 中。这样势必造成 Activity 异常的臃肿,只要一个职责发生变化就能引起 Activity 的变化。比如 Adapter 变化,要去 Activity 中修改,等等都会对 Activity 造成变化。requestNet 函数中的功能不够纯粹,里面又包含了很多其他的功能,也会导致同样的问题。

这也就是概念中提到的,就一个类而言,应该仅有一个引起它变化的原因。

好处

单一职责的好处很明显,让一个类、函数只负责某一项任务或者功能,可以达到很好的复用效果,代码的可读性也会增强,可读性好了,对应的可维护性也会增加。

当然关于职责的划分是一个很抽象的概念,每个人的划分都会不同,单一职责的划分界限并不总是那么清晰,有的时候划分的很细也会带来不方便。这是一个灵活掌握的问题,关键是设计代码的时候有没有考虑到这种思想。

开闭原则

Java 世界里最基础的设计原则,指导我们如何建立一个稳定的、灵活的系统。

概念

软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。

在编写代码的过程中,不是一成不变的,需求的变化、升级、维护等等都需要对代码进行修改,修改的时候就不可避免地将错误引入原本已经测试过的旧代码中,破坏原有系统。因此,当软件需求发生变化的时候,我们应该优先考虑通过扩展的方式来实现变化,而不是通过修改已有代码来实现。

当然实际开发中扩展和修改是同时存在的。应该尽量少的去修改代码,想法去扩展代码。

《面向对象软件构造》一书中提到这一原则---开闭原则。这一想法认为,程序一旦开发完成,程序中的一个类的实现只应该因错误而被修改,新的或者改变的特性应该通过新建不同的类实现,新建的类可以通过继承的方式来重用原有类。

举个简单的例子:

public class Hello{
  BlackPen pen = new BlackPen();
  
  void writeHello(){
    pen.write("hello world");
  }
}

// Pen 类可以写出字
public class BlackPen{
  public void write(String content){
    System.out.println("content");
  }
}

上面这个程序中我们可以通过 BlackPen 类写出字,有一天需求变了要求写出红色的字。

public class Hello{
  BlackPen pen = new BlackPen();
  RedPen redPen = new RedPen();
  void writeHello(String flag){
    switch(flag){
        "XXX":
        	pen.wiite("hello world");
        "YYY":
        	redPen.write("hello world")
    }
  }
}

// BlackPen 类可以写出黑字
public class BlackPen{
  public void write(String content){
    System.out.println(content);
  }
}

// RedPen 类可以写出红字
public class RedPen{
  public void write(String content){
    System.out.println(content);
  }
}

这样写通过 switch 来判断要调用那一个,如果继续添加其他颜色的笔就继续添加。这样貌似不错。但是试想 Hello 是你提供给别人的一个框架,那么别人想要继续添加可以写出黄色的 Hello Wrold ,是不是就没有办法了,非得让你去修改 Hello 方法才可以,没有了扩展性。

现在优化成

public class Hello{
  Pen pen = new BlackPen();
  public void setPen(Pen pen){
    this.pen = pen;
  }
  void writeHello(){
 		pen.write("hello world")
  }
}

public interface Pen{
  write(String content);
}

// BlackPen 类可以写出黑字
public class BlackPen implement Pen{
  public void write(String content){
    System.out.println(content);
  }
}

// RedPen 类可以写出红字
public class RedPen implement Pen{
  public void write(String content){
    System.out.println(content);
  }
}

这样就可以扩展而不用修改 Hello 内的代码了。

开闭原则,对修改关闭,对扩展开放。并不是说完全的不能修改,比如上面内容,一开始只有一个 BlackPen 的时候,你没有想到扩展,可以那样写,但是随着业务变化,出现了不同的 Pen。这个时候就需要考虑 Pen 要有可扩展性。就不能重复的在 Hello 类中不断去修改了。而是换一种思路,让其变得具有可扩展。

好处

可以使用我们的程序更加稳定,避免修改带来的错误,增加可扩展性。当一个类中的业务不断的发生变化需求,不断的增加业务判断,就需要考虑到扩展性了。

里氏替换原则

构建扩展性更好的系统

概念

所用引用基类的地方必须能透明地使用其子类的对象。

只要父类能出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或者异常,使用者可能根本就不需要知道是父类还是子类。

里氏替换原则就是依赖于继承、多态这两大特性。

其实就是将依赖变成抽象,不依赖于具体的实现。

比如:

public class Window{
  public void show(View child){
    child.draw();
  }
}

public abstract class View{
  public abstract void draw();
  
  public void measure(int width,int height){
    // 测量视图大小
  }
}

public class Button extends View{
  public void draw(){
    // 绘制按钮
  }
}

public class TextView extends View{
  public void draw(){
    // 绘制文本
  }
}

Window 是依赖于 View 的,是一个抽象类,Window 是依赖于一个抽象,而不是具体的对象。这个时候传入任何 View 的具体对象都是可以的。

好处

提高扩展性,使其不依赖具体的实现,依赖抽象。

依赖倒置原则

让项目拥有变化的能力

概念

依赖倒置原则指代了一种特定的解耦形式,使得高层次的模块不依赖于低层的模块的实现细节,依赖模块被颠倒了。

依赖倒置的关键点:

  • 高层模块不应该依赖低层模块,两者应该依赖其抽象
  • 抽象不应该依赖细节
  • 细节应该依赖抽象

在 Java 语言中,抽象就是指接口或抽象类。两者都是不能直接被实例化的;细节就是实现类,实现接口或者继承抽象类而产生的类就是细节。

高层模块就是调用端,低层模块就是具体的实现类。也就是调用端不要依赖具体的实现类,而是通过依赖抽象的方式。其实就是面向接口编程。

其实和上面里氏替换原则类似

好处

降低耦合性,不依赖具体细节,依赖抽象,提高可扩展性

接口隔离原则

系统有更好的灵活性

概念

客户端不应该依赖它不需要的接口。另一种定义:类间的依赖关系应该建立在最小的接口上。接口隔离原则将非常庞大、臃肿的接口拆分成更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署。

其实就是让一个接口尽可能的小,方法少,使用户使用起来方便。

比如:一个对象实现了多个接口,有个接口是关闭功能,那么当这个对象想要关闭的时候,调用关闭方法就可以了,因为它实现了多个接口,有多个方法,调用的时候就暴露了其他接口函数。这个时候我们仅需要它暴露关闭的接口就可以了,隐藏其他接口信息。

好处

使用起来更加方便灵活

迪米特原则

概念

迪米特原则也称为最少知道原则。一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对需要耦合或调用的类知道得最少,类的内部如何实现与调用者或者依赖者没有关系,调用者或者依赖者只需要知道它的需要的方法就可以了,其他的可一概不管。类与类之间关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。

迪米特原则还可以解释为:只与直接朋友通信。

也就是说,应该尽可能少的与别的朋友通信,仅与最直接的朋友通信。

两个对象成为朋友的方式有多种:组合、聚合、依赖等等。

好处

降低依赖、使用简单

总结

这六大原则不是相互独立的,而是互相融合,你中有我,我中有你。

单一职责告诉我们要尽量的分离代码,不断的精分代码,不同的模块实现不同的功能。这样不会所有功能都融合在一块,方便阅读、维护代码。

开闭原则、里氏替换原则、依赖倒置原则:本质上都是通过抽象来提高扩展性。不依赖具体的实现而依赖抽象,就会增加很多扩展性,抽象可以有需要不同的实现。

接口隔离原则:和单一职责有类似,就是通过接口细分化,暴露最少的方法。要想有某个功能,只需要实现这个接口就可以了,与其他接口无关。

迪米特原则:尽量依赖更少的类,尽量对外界暴露更少的方法

实现这六大原则主要是通过面向接口编程,面向抽象编程。不依赖具体的实现。每个类都有一个抽象(抽象类、接口)。当高层级模块需要依赖这个类的时候,依赖它的抽象,而不是具体。这个时候就可以灵活的改变其实现了。