美文网首页
Java设计模式和原则

Java设计模式和原则

作者: Doooook | 来源:发表于2020-08-09 10:51 被阅读0次

软件开发都不仅仅是编写代码。构建应用程序的方式对软件应用程序的成败有很大影响。当我们谈论一个成功的软件应用程序时,不仅讨论应用程序如何执行它应该做的事情,还讨论在开发它时付出了多少努力,以及它是否易于测试和维护。如果没有以正确的方式完成,那么暴涨的开发成本将会导致没人能接受这个应用程序。

面向对象的设计原则也被称为SOLID。在设计和开发软件时可以应用这些原则,以便创建易于维护和开发的程序。SOLID最初是由RobertC.Martin所提出的,它们是敏捷软件开发过程的一部分。SOLID原则包括单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则

除了前面设计原则外,还有面向对象的设计模式。设计模式是可以应用于常见问题的通用可重用解决方案。

单一职责原则

单一职责原则是一种面向对象的设计原则,该原则指出软件模块应该只有一个被修改的理由。在大多数情况下,编写Java代码时都会将单一职责原则应用于类。
单一职责原则可被视为使封装工作达到最佳状态的良好实践。更改的理由是:需要修改代码。如果类需要更改的原因不止一个,那么每个类都可能引入影响其他类的更改。当这些更改单独管理但影响同一模块时,一系列更改可能会破坏与其他更改原因相关的功能。
另一方面,每个更改的职责/理由都会增加新的依赖关系,使代码不那么健壮,更难以修改。
在示例中,我们将使用数据库来持久保存对象。假设对Car类添加方法来处理增、删、改、查的数据库操作:


image.png

在这种情况下,Car不仅会封装逻辑,还会封装数据库操作(两个职责是改变的两个原因)。这将使我们的类更难维护和测试,因为代码是紧密耦合的。Car类将取决于数据库,如果将来想要更改数据库系统,我们必须更改Car代码,这可能会在Car逻辑中产生错误。相反,更改Car逻辑可能会在数据持久性中产生错误。
解决方案是创建两个类:一个用于封装Car逻辑,另一个用于负责持久性。


image.png

开闭原则

这个原则如下:“模块、类和函数应该对扩展开放,对修改关闭。
应用此原则将有助于我们开发复杂而稳健的软件。我们必须想象:开发的软件正在构建一个复杂的结构,一旦我们完成了它的一部分,不应该再修改它,而是应该在它的基础之上继续建设。软件开发也是一样的。一旦我们开发并测试了一个模块,如果想要改变它,不仅要测试正在改变的功能,还要测试它负责的整个功能。这涉及许多额外的资源,这些资源可能从一开始就没有估算过,也会带来额外的风险。一个模块中的更改可能会影响其他模块或整体上的功能
因此,最好的办法是尝试在完成后保持模块不变,并通过继承和多态扩展来添加新功能。开闭原则是最重要的设计原则之一,是大多数设计模式的基础。

里式替换原则

Barbara Liskov指出,派生类型必须完全可替代其基类型。里氏替换原则(LSP)与子类型多态密切相关。基于面向对象语言中的子类型多态,派生对象可以用其父类型替换。例如,如果有一个Car对象,它可以在代码中用作Vehicle。
里氏替换原则声明,在设计模块和类时,必须确保派生类型从行为的角度来看是可替代的。当派生类型被其父类型替换时,其余代码就像它是子类型那样使用它。从这个角度来看,派生类型应该像其父类型那样表现,不应该破坏它的行为。这称为强行为子类型。
里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
public class A {

    public int func1(int a, int b) {
        return a - b;
    }
}
public class B extends A {

    @Override
    public int func1(int a, int b) {
        return a + b;
    }

    public int func2(int a, int b) {
        return func1(a, b) + 100;
    }
}
/**
     * 里氏替换原则 Liskov Substitution Principle
     * 我们发现原本运行正常的相减功能发生了错误。原因就是类B在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类B重写后的方法,
     * 造成原本运行正常的功能出现了错误。在本例中,引用基类A完成的功能,换成子类B之后,发生了异常。在实际编程中,我们常常会通过重写父类的方法来完成新
     * 的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。
     */
    @Test
    public void testLiskov() {
        B b = new B();
        System.out.println("100-50=" + b.func1(100, 50));
        System.out.println("100-80=" + b.func1(100, 80));
        System.out.println("100+20+100=" + b.func2(100, 20));
    }
image.png
参考:
https://blog.csdn.net/xingjiarong/article/details/50081857
https://blog.csdn.net/Return_head/article/details/82633814

接口隔离原则

“客户端不应该依赖于它所不需要的接口。”
实际应用中,接口隔离原则(InterfaceSegregationPrinciple,ISP)减少了代码耦合,使软件更健壮,更易于维护和扩展。接口隔离原则最初是由RobertMartin提出的,他意识到如果接口隔离原则被破坏,客户端被迫依赖它们不使用的接口时,代码就会变得紧密耦合,几乎不可能为其添加新功能。
在这个例子中,Mechanic类依赖于ICar类,但是,Car类提供的方法超出了Mechanic需要的。


image.png

这是一个糟糕的设计,因为如果我们想把汽车替换为另一辆汽车,需要在Mechanic类中进行更改,这违反了开闭原则。换个思路,我们可以创建一个仅公开Mechanic类所需的相关方法的接口。


image.png

ISP的几个使用原则:

  • 根据接口隔离原则拆分接口时,首先必须满足单一职责原则:没有哪个设计可以十全十美的考虑到所有的设计原则,有些设计原则之间就可能出现冲突,就如同单一职责原则和接口隔离原则,一个考虑的是接口的职责的单一性,一个考虑的是方法设计的专业性(尽可能的少),必然是会出现冲突。在出现冲突时,尽量以单一职责为主,当然这也要考虑具体的情况。
  • 提高高内聚:提高接口,类,模块的处理能力,减少对外的交互。比如你给杀手提交了一个订单,要求他在一周之内杀一个人,一周后杀手完成了任务,这种不讲条件完成任务的表现就是高内聚。具体来说就是:要求在接口中尽量少公布public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险就越小,也有利于降低成本。
  • 定制服务:单独为一个个体提供优良服务(只提供访问者需要的方法)。
  • 接口设计要有限度:根据经验判断

参考:https://blog.csdn.net/king123456man/article/details/81626059

依赖倒置原则

依赖倒置原则(Dependence Inversion Principle)的包含如下的三层含义:

  • 高层模块不应该依赖低层模块,两者都应该依赖其抽象
  • 抽象不应该依赖细节
  • 细节应该依赖抽象
    每一个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块(一般是接口,抽象类),原子逻辑的组装就是高层模块。在Java语言中,抽象就是指接口和或抽象类,两者都不能被直接实例化。细节就是实现类,实现接口或继承抽象类而产生的类就是细节,可以被直接实例化。下面是依赖倒置原则在Java语言中的表现:
  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的
  • 接口或抽象类不依赖于实现类
  • 实现类依赖于接口或抽象类

DIP的好处: 采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。

/**
 * 将汽车模块抽象为一个接口:可以是奔驰汽车,也可以是宝马汽车
 */
public interface ICar {
      /**
       * 是汽车就应该能跑
       */
      void run();
}
public class Benz implements ICar {
    /**
     * 汽车肯定会跑
     */
    @Override
    public void run() {
        System.out.println("奔驰汽车开始运行...");
    }
}
public class BMW implements ICar {
    /**
     * 宝马车当然也可以开动了
     */
    @Override
    public void run() {
        System.out.println("宝马汽车开始运行...");
    }
}
/**
 * 将司机模块抽象为一个接口
 */
public interface IDriver {
     /**
      * 是司机就应该会驾驶汽车
      * @param car
      */
     void drive(ICar car);
}
public class Driver implements IDriver {
    /**
     * 司机的主要职责就是驾驶汽车
     * @param car
     */
    @Override
    public void drive(ICar car){
        car.run();
    }
}
    /**
     * 测试依赖倒置原则
     */
    @Test
    public void testDip() {
        IDriver xiaoLi = new Driver();
        ICar benz = new Benz();
        ICar bmw = new BMW();
        // 小李开奔驰车
        xiaoLi.drive(benz);
        // 小李开宝马车
        xiaoLi.drive(bmw);
    }
image.png

参考:https://blog.csdn.net/king123456man/article/details/81626127

总结

虽然Java从版本8开始引入了新的函数式编程元素,但它的核心仍然是面向对象的语言。为了编写易于扩展和维护的可靠且健壮的代码,我们学习了面向对象编程语言的基本原则。
开发软件的一个重要部分是设计程序组件的结构和所需行为。通过这种方式,我们可以在大型系统、大型团队中工作,在团队内部或团队之间共享面向对象的设计。

相关文章

  • Java 常用设计模式简例

    简述Java常用设计模式 简述Java常用设计模式及设计原则 strate---------策略模式针对接口编程,...

  • Java基础(5)——设计模式

    Java for android基础知识 面向对象的6个设计原则和23经典设计模式 一、设计原则 1.单一职责原则...

  • 【设计模式】依赖倒转原则

    【设计模式】依赖倒转原则 以下内容来自【Java设计模式】 如果说开闭原则是面向对象设计的目标,那么依赖倒转原则就...

  • Java设计模式和原则

    软件开发都不仅仅是编写代码。构建应用程序的方式对软件应用程序的成败有很大影响。当我们谈论一个成功的软件应用程序时,...

  • java设计模式和设计原则(设计模式待续)

    1.开闭原则: 对修改关闭,对拓展开放。如果需要增加新的功能,不修改原有的代码逻辑,只针对原有的代码进行拓展,可以...

  • 设计模式开篇

    设计模式与原则 设计模式原则是为了提高代码的可维护性,可复用性和可扩展性,设计模式是设计模式原则的具体体现。 设计...

  • 行业技能&经验

    技能 精通Java和golang编程,熟练掌握各种设计模式、编码注重设计原则,编写代码易于维护和拓展。 擅长jvm...

  • Java中的24种设计模式与7大原则简要概述

    Java中的24种设计模式与7大原则转 Java中的24种设计模式与7大原则转自宝宝巴士,有些说的还是很模糊,有时...

  • 设计模式

    1 软件设计原则和设计模式概览 1.1 面向对象设计原则 1.2 GOF设计模式 1.3 C嵌入式编程设计模式 ...

  • 【设计模式】合成复用原则

    以下内容来自《Java设计模式》 1 合成复用原则 合成复用原则又称为组合/聚合复用原则(Composition/...

网友评论

      本文标题:Java设计模式和原则

      本文链接:https://www.haomeiwen.com/subject/jdlldktx.html