你真的精通设计模式的七大原则吗

@[TOC]

一、设计模式的目的:

1)代码重复性(即:相同功能的代码,不用多次编写)
2)可读性(即:编程规范怀,便于其他程序员的阅读和理解)
3)可扩展性(即:当需要增加新的功能时,非常的方便,称为可维护)
4)可靠性(即:当我们增加新的功能后,对原来的功能没有影响)
5)使程序呈现高内聚、低耦合的特性

二、设计模式的七大原则:

1.单一职责原则

基本介绍

对类来说,一个类应该只负责一项职责,如果一个类承担的职责太多,就等于把这些职责耦合在一起了,一个职责的变化可能 会削弱或者抑制这个类完成其他职责的能力,这样的耦合会导致在需求发生改变时,会对代码进行重构的可能,这严重违背了可维护性,大大的降低了生产速度与开发周期
应用场景,实现moto和汽车在公路上跑,飞机在天空中飞

  • singleresponsibilit1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class singleresponsibilit1 {

    public static void main(String[] args) {

    Vehicle vehicle = new Vehicle();
    vehicle.run("moto");
    vehicle.run("汽车");
    vehicle.run("飞机");
    }
    }

    //交通工具类
    class Vehicle{
    public void run(String vehicle){
    System.out.println(vehicle+"在公路上运行");
    }
    }
  • 测试

在这里插入图片描述

很显然,这样的做法不满足应用的场景,所以我们要对此操作进行相应的修改

  • singleresponsibilit2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class singleresponsibilit2 {
    public static void main(String[] args) {

    RoadVehicle roadVehicle = new RoadVehicle();
    roadVehicle.run("moto");
    roadVehicle.run("汽车");

    AirVehicle airVehicle = new AirVehicle();
    airVehicle.run("飞机");

    }
    }

    class RoadVehicle{
    public void run(String vehicle){
    System.out.println(vehicle+"在公路上跑");
    }
    }
    class AirVehicle{
    public void run(String vehicle){
    System.out.println(vehicle+"在天空飞");
    }
    }
    class WaterVehicle{
    public void run(String vehicle){
    System.out.println(vehicle+"在水里");
    }
    }
  • 测试

在这里插入图片描述

这次的修改虽然满足了我们的需求,但是这样的做法一般是不提倡使用的,因为它消耗了太多的资源,但是它却满足单一职责的原则,我们也可以再改一下

  • singleresponsibilit3
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class singleresponsibilit3 {
    public static void main(String[] args) {

    Vehicle2 vehicle2 = new Vehicle2();
    vehicle2.run("moto");
    vehicle2.run("汽车");
    vehicle2.runAir("飞机");

    }
    }

    class Vehicle2{
    public void run(String vehicle){
    System.out.println(vehicle+"在公路上运行");
    }
    public void runAir(String vehicle){
    System.out.println(vehicle+"在空中飞");
    }
    public void runWater(String vehicle){
    System.out.println(vehicle+"在水里游");
    }
    }
  • 测试

在这里插入图片描述

这次的改变只用了一个类,就实现了功能,在一个类中,每个方法独自实现自己的职责,它也满足单一职责原则。

注意事项和细节:
1)降低类的复杂度,一个类只负责一项职责
2)提高类的可读性,可维护性
3)降低变更引起的风险
4)当类中的方法比较少时,可以在方法级别保持单一职责原则

2.接口隔离原则

基本介绍:

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。接口的应该独立。
应用场景:A类通过接口Interface1依赖类B,类C通过接口Interfa1依赖类D

  • Interface1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    public class Segregation1 {
    public static void main(String[] args) {

    }
    }


    interface Interface1{
    void operation1();
    void operation2();
    void operation3();
    void operation4();
    void operation5();
    }

    class B implements Interface1{
    public void operation1(){
    System.out.println("B 实现了operation1");
    }
    public void operation2(){
    System.out.println("B 实现了operation2");

    }
    public void operation3(){
    System.out.println("B 实现了operation3");

    }
    public void operation4(){
    System.out.println("B 实现了operation4");

    }
    public void operation5(){
    System.out.println("B 实现了operation5");

    }
    }
    class D implements Interface1{
    public void operation1(){
    System.out.println("D 实现了operation1");
    }
    public void operation2(){
    System.out.println("D 实现了operation2");

    }
    public void operation3(){
    System.out.println("D 实现了operation3");

    }
    public void operation4(){
    System.out.println("D 实现了operation4");

    }
    public void operation5(){
    System.out.println("D 实现了operation5");

    }
    }

    class A{//A类通过接口Interface1依赖B类,但是只会用到1.2.3方法
    public void depend1(Interface1 i){
    i.operation1();
    }
    public void depend2(Interface1 i){
    i.operation2();
    }
    public void depend3(Interface1 i){
    i.operation2();
    }
    }
    class C{//C类通过接口Interface1依赖D类,但是只会用到1.4.5方法
    public void depend1(Interface1 i){
    i.operation1();
    }
    public void depend4(Interface1 i){
    i.operation4();
    }
    public void depend5(Interface1 i){
    i.operation5();
    }
    }
  • 类图表示
    在这里插入图片描述

    从上面Interface1不是最小的接口,如果要增加需求时,改动太大,所以我们可以把Interface1再进行一个细分

  • Segregation1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    public class Segregation2 {
    public static void main(String[] args) {

    A a = new A();
    a.depend1(new B());
    a.depend2(new B());
    a.depend3(new B());

    C c = new C();
    c.depend1(new D());
    c.depend4(new D());
    c.depend5(new D());

    }
    }


    interface Interface1{
    void operation1();
    }
    interface Interface2{
    void operation2();
    void operation3();

    }
    interface Interface3{
    void operation4();
    void operation5();
    }

    class B implements Interface1,Interface2{
    public void operation1(){
    System.out.println("B 实现了operation1");
    }
    public void operation2(){
    System.out.println("B 实现了operation2");

    }
    public void operation3(){
    System.out.println("B 实现了operation3");

    }
    }

    class D implements Interface1,Interface3 {
    public void operation1(){
    System.out.println("D 实现了operation1");
    }
    public void operation4(){
    System.out.println("D 实现了operation4");

    }
    public void operation5(){
    System.out.println("D 实现了operation5");

    }
    }

    class A{//A类通过接口Interface1,Interface2依赖B类,但是只会用到1.2.3方法
    public void depend1(Interface1 i){
    i.operation1();
    }
    public void depend2(Interface2 i){
    i.operation2();
    }
    public void depend3(Interface2 i){
    i.operation2();
    }
    }

    class C{//C类通过接口Interface1,Interface3依赖D类,但是只会用到1.4.5方法
    public void depend1(Interface1 i){
    i.operation1();
    }
    public void depend4(Interface3 i){
    i.operation4();
    }
    public void depend5(Interface3 i){
    i.operation5();
    }
    }
  • 测试结果

在这里插入图片描述

  • 类图形式

在这里插入图片描述
*总结:
隔离原则实质就是我们实现某个类时,它要实现的这个接口必须是最小的,不能包含其他的方法,只能提供给要实现的类使用,一个类与另一个类的依赖是建立在接口之上的。

3.依赖倒转(倒置)原则

基本介绍:

依赖倒转的原则:
1)高层模块不应该依赖低层模块,二者都应该依赖其抽象
2)抽象不应该依赖细节,细节应该依赖抽象
3)依赖倒转(倒置)的中心思想是面向接口编程
4)依赖倒转原则的设计理念:相对于细节的多变性,抽象的东西要稳定的多。心抽象 的其他搭建的架构比心细节为基础的架构要稳定的多。在java中,抽象指的是接口或者是抽象类,细节就是具体的实现类
5)使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成

应用场景:完成Person接收消息的功能

  • DependecyInversion
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class DependecyInversion {
    public static void main(String[] args) {

    Person person = new Person();
    person.receive(new Email());
    }
    }


    class Email{
    public String getInfo(){
    return "电子邮件信息:hello";
    }
    }

    //完成person接收消息的功能
    class Person{
    public void receive(Email email){
    System.out.println(email.getInfo());
    }
    }
  • 测试
    在这里插入图片描述

    这里我们发现在使用Email接收消息时,竟然引用了一个具体的类,这可是面向接口编程的大忌啊。
    狗娃同学:这样的做法简单容易理解,并且我可以直接的去操作Email,没什么毛病吧!
    小编:狗娃,如果我们的需求发生改变,现在要接收微信消息,是不是我们要重新写具体的类,再调用,这样的做法是非常的麻烦,所以下面我们来引用面向接口编程的思路解决问题!

  • DependecyInversion
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    public class DependecyInversion {
    public static void main(String[] args) {

    Person person = new Person();
    //传入Email
    person.receive(new Email());
    System.out.println();
    //传入WeiXin
    person.receive(new WeiXin());
    }
    }


    //定义接口
    interface IReceiver{
    public String getInfo();

    }

    class Email implements IReceiver{
    public String getInfo(){
    return "电子邮件信息:hello";
    }
    }

    //添加了一个新功能
    class WeiXin implements IReceiver{
    @Override
    public String getInfo() {
    return "微信消息:helloWeiXin";
    }
    }

    class Person{
    //现在依赖的是接口
    public void receive(IReceiver receiver){
    System.out.println(receiver.getInfo());
    }
    }
  • 测试

在这里插入图片描述
注意:
1)低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好。
2)变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。
3)继承时遵循里氏替换原则。

依赖关系传递的三种方式

1)接口传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface IOpenAndClose{
public void open(ITv tv);
}

interface ITv{
public void play();
}
class OPenAdnClose implements IOpenAndClose{
@Override
public void open(ITv tv) {
tv.play();
}
}
class ChangHong implements ITv{
@Override
public void play() {
System.out.println("接口传递----长虹电视打开了!");
}
}
  • 测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class DependecyInversion {
    public static void main(String[] args) {

    ChangHong changHong = new ChangHong();
    OPenAdnClose oPenAdnClose = new OPenAdnClose();
    oPenAdnClose.open(changHong);

    }
    }
    • 结果
      在这里插入图片描述
      2)构造方法传递
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      interface IOpenAndClose{
      public void open();
      }

      interface ITv{
      public void play();
      }
      class OPenAdnClose implements IOpenAndClose{
      public ITv tv;
      public OPenAdnClose(ITv iTv){
      this.tv = tv;
      }

      @Override
      public void open() {
      this.tv.play();
      }
      }
      class ChangHong implements ITv{
      @Override
      public void play() {
      System.out.println("构造方法-----长虹电视打开了!");
      }
      }
  • 测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class DependecyInversion {
    public static void main(String[] args) {

    ChangHong changHong = new ChangHong();
    OPenAdnClose oPenAdnClose = new OPenAdnClose(changHong);
    changHong.play();

    }
    }
    • 结果
      在这里插入图片描述

3)setter方式传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface IOpenAndClose{
public void open();
public void setTv(ITv tv);
}

interface ITv{
public void play();
}
class OPenAdnClose implements IOpenAndClose{
public ITv tv;

public void setTv(ITv tv){
this.tv = tv;
}
@Override
public void open() {
this.tv.play();
}
}
class ChangHong implements ITv{
@Override
public void play() {
System.out.println("长虹电视打开了!");
}
}
  • 测试方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class DependecyInversion {
    public static void main(String[] args) {

    ChangHong changHong = new ChangHong();
    OPenAdnClose oPenAdnClose = new OPenAdnClose();
    //使用setter注入电视
    oPenAdnClose.setTv(changHong);
    changHong.play();
    }
    }
  • 结果

在这里插入图片描述

4.里氏替换原则

基本介绍:

1)所有引用基类的地方必须能透明地使用其子类的对象
2)在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
3)里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合、组合、依赖来解决问题。

应用场景:A类定义两数相减操作,B类继承A类,不小心重写了A类的方法,然后newA类,newB类,实现两数相减操作时,异常!

  • Liskov
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class Liskov {
    public static void main(String[] args) {

    A a = new A();
    System.out.println("11-3="+a.func1(11,3));
    System.out.println("1-8="+a.func1(1,8));
    System.out.println("-------------------------");
    B b = new B();
    System.out.println("11-3="+b.func1(11,3));
    System.out.println("1-8="+b.func1(1,8));
    System.out.println("11+3+9="+b.func2(11,3));
    }
    }


    class A{
    public int func1(int num1,int num2){
    return num1- num2;
    }
    }

    class B extends A{
    public int func1(int a,int b){
    return a + b;
    }
    public int func2(int a,int b){
    return func1(a,b)+9;
    }
    }
  • 运行结果

在这里插入图片描述

当然我们也有解决的方法,通用的作法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替。

  • Liskov
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    public class Liskov {
    public static void main(String[] args) {

    A a = new A();
    System.out.println("11-3="+a.func1(11,3));
    System.out.println("1-8="+a.func1(1,8));
    System.out.println("-------------------------");
    B b = new B();
    //B类不再继承A类,因此调用者,不会再func1是求减法
    System.out.println("11+3="+b.func1(11,3));
    System.out.println("1+8="+b.func1(1,8));
    System.out.println("11+3+9="+b.func2(11,3));
    }
    }

    //创建一个更加基础的基类
    class Base{

    }

    class A extends Base{
    public int func1(int num1,int num2){
    return num1- num2;
    }
    }

    class B extends Base {

    private A a = new A();

    public int func1(int a,int b){
    return a + b;
    }
    public int func2(int a,int b){
    return func1(a,b)+9;
    }

    //我们仍然想使用A的方法
    public int func3(int a,int b){
    return this.a.func1(a,b);
    }
    }
  • 测试
    在这里插入图片描述

    现在要实现的功能是正确的,所以在使用继承时,有时候必须借助一个基类,来降低两个类的耦合度,从而实现功能!

    5.开闭原则(OCP)

    基本介绍

1)开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则。
2)一个软件实体如类,模块和函数应该对扩展开放(提供方),对修改关闭(使用方)。用抽象构建框架,用实现扩展细节
3)当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
4)编程中遵循其他原则,使用设计模式的目的就是遵循开闭原则。

应用场景:

一个画图形的设计

  • Shape

    1
    2
    3
    public class Shape {
    int m_type;
    }
  • Circle

    1
    2
    3
    4
    5
    6
    public class Circle extends Shape {

    Circle(){
    super.m_type=2;
    }
    }
  • Rectangle

    1
    2
    3
    4
    5
    6
    7
    public class Rectangle extends Shape {

    Rectangle(){
    super.m_type=1;
    }

    }
  • drawRectangle

    1
    2
    3
    public class drawRectangle {

    }
  • GraphicEditor

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //使用方
    public class GraphicEditor {

    public void drawShape(Shape s){
    if (s.m_type == 1){
    drawRectangle(s);
    }else if (s.m_type == 2){
    drawCircle(s);
    }
    }
    public void drawRectangle(Shape r){
    System.out.println("绘制矩形");
    }
    public void drawCircle(Shape r){
    System.out.println("绘制圆形");
    }
    }
  • 类图表示形式
    在这里插入图片描述

  • 测试

    1
    2
    3
    4
    5
    public static void main(String[] args) {
    GraphicEditor graphicEditor = new GraphicEditor();
    graphicEditor.drawShape(new Rectangle());
    graphicEditor.drawShape(new Circle());
    }
  • 结果
    在这里插入图片描述

    狗娃同学:我觉得上面的做法比较的好理解,简单容易操作。
    小编:狗娃,你觉得可以,但是如果增加一个图形种类,比如三角形,这样的代码就需要很大的改动,首先我们得新建一个三角类,并且在使用方那里要添加绘制三角形的方法,添加判断条件为3时,才能绘制三角形,修改的代码量实在是太多了!由此可以看出上面的做法违反了OCP原则,对扩展开放(提供方),对修改关闭(使用方)。所以我们必须对上面的方法进行改进!

  • Shape

    1
    2
    3
    4
    5
    6
    abstract class Shape {

    int m_type;

    public abstract void draw();//抽象方法
    }
  • Circle

    1
    2
    3
    4
    5
    6
    abstract class Shape {

    int m_type;

    public abstract void draw();//抽象方法
    }
  • Rectangle

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Rectangle extends Shape {

    Rectangle(){
    super.m_type=1;
    }

    @Override
    public void draw() {
    System.out.println("绘制矩形");
    }
    }
  • Triangle

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //新增加的图形
    public class Triangle extends Shape{
    Triangle(){
    super.m_type = 3;
    }

    @Override
    public void draw() {
    System.out.println("绘制三角形");
    }
    }
  • drawRectangle

    1
    2
    3
    public class drawRectangle {

    }
  • GraphicEditor

    1
    2
    3
    4
    5
    6
    7
    //使用方
    public class GraphicEditor {

    public void drawShape(Shape s){
    s.draw();
    }
    }
  • 类图表示形式
    在这里插入图片描述

    我们在更改时,将Shape定义为了abstract的类,并提供了抽象 访求draw,目的是让所有的子类去实现Shape类,并重写draw方法,因此如果有再有新的图形的话我们只需要继承Shape,实现draw方法,使用方(GraphicEditor)的代码并不用改变,只需要变动提供方,从而使得满足了ocp原则!

6.迪米特法则

基本介绍

1)一个对象应该对其他对象保持最少的了解
2)类与类关系越密切,耦合度越大
3)迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的
越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内
部。对外除了提供的public方法,不对外泄露任何信息
4)迪米特法则还有个更简单的定义:只与直接的朋友通信
5)直接的朋友:每个对象都会与其他对象由耦合关系,只要两个对象之间有耦合关系
我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合
等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而
出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量
的形式出现在类的内部。

注意:
1)迪米特法则的核心是降低类之间的耦合
2)由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系。

7.合成复用原则

基本介绍

原则是尽量使用合成/聚合的方式,而不是是使用继承
在这里插入图片描述
类与类之间使用继承会导致类之间的紧密度太度,所以我们通常使用依赖、组合、聚合来代替继承。

三、设计模式的原则总结:

1)找出应用中需要变化处,把它们独立出来,不要和那些不需要变化的代码混在一起。
2)针对接口编程,而不是针对实现编程。
3)为了交互对象之间的松耦合设计而努力。