Java设计模式和原则

2020-08-09  本文已影响0人  Doooook

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

面向对象的设计原则也被称为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的几个使用原则:

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

依赖倒置原则

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

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

上一篇 下一篇

猜你喜欢

热点阅读