Java设计模式之七大原则

Java设计模式

文章目录

  • Java设计模式
    • Java设计模式的概述
    • Java设计模式的目的
    • 设计模式七大原则
      • 单一职责原则(Single Responsibility Principle, SRP)
        • 基本介绍
        • 示例
          • **非单一职责(错误示范)**
          • **遵循类单一职责**
        • 使用原则
        • 优点
      • 接口隔离原则(Interface Segregation Principle, ISP)
        • 基本介绍
        • 示例
          • **非接口隔离原则(错误示范)**
          • **遵循接口隔离原则**
        • 使用原则
        • 优点
      • 依赖倒转原则(Dependence Inversion Principle, DIP)
        • **基本介绍**
        • 示例
          • 非依赖倒转(错误示范)
          • **遵循依赖倒转**
        • 依赖倒转的三种方式
          • 接口传递
          • 构造方法传递
          • setter方式传递
        • **好处**
        • 使用原则
      • 里氏替换原则(Dependence Inversion Principle, DIP)
        • 了解继承
        • 基本介绍
        • 示例
          • 非里氏原则(错误示范)
          • 遵循里氏原则
        • 加深理解
        • 多态与LSP是否矛盾
      • 迪米特法则((Law of Demeter, LoD))
        • 基本介绍
        • 示例
          • **非迪米特法则(错误示范)**
          • 遵循迪米特法则
        • 注意事项
      • 合成复用原则(Composite Reuse Principle)
        • **基本介绍**
      • 开闭原则(Open Close Principle, OCP)
        • 基本介绍
        • 示例
        • 注意:
    • 总结每个原则的核心
    • 总结设计原则核心思想
    • 参考文章
    • 总结设计原则核心思想
    • 参考文章

Java设计模式的概述

设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。1995 年,GoF(Gang of Four,四人组/四人帮)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了 23 种设计模式,从此树立了软件设计模式领域的里程碑,人称「GoF设计模式」。这 23 种设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性,以及类的关联关系和组合关系的充分理解。虽然我命名为“Java设计模式”,但是设计模式并不是 Java 的专利,它同样适用于 C++、C#、JavaScript 等其它面向对象的编程语言。

Java设计模式的目的

编写软件过程中,程序员面临着来自 耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战,设计模式是为了让程序(软件),具有更好:

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

设计模式七大原则

设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础(即:设计模式为什么这样设计的依据)

设计模式常用的七大原则有:

  • 单一职责原则
  • 接口隔离原则
  • 依赖倒转(倒置)原则
  • 里氏替换原则
  • 开闭原则
  • 迪米特法则
  • 合成复用原则

单一职责原则(Single Responsibility Principle, SRP)

基本介绍

对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。 当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为 A1,A2.

示例

非单一职责(错误示范)
public class singleton1 {
    public static void main(String[] args) {
        Vehicle vehicle=new Vehicle();
        vehicle.run("汽车");
        vehicle.run("轮船");
        vehicle.run("飞机");
    }
}

//非单一模式
class Vehicle{
    public void  run(String ve){
        System.out.println(ve+"在公路运行");
    }
}
遵循类单一职责
public class singleton3 {
    public static void main(String[] args) {
        RoadVehicle roadVehicle=new RoadVehicle();
        roadVehicle.run("汽车");
        AirVehicle airVehicle=new AirVehicle();
        airVehicle.run("飞机");
        SeaVehicle seaVehicle=new SeaVehicle();
        seaVehicle.run("轮船");
    }
}
//类单一原则
class RoadVehicle{
    public void run(String ve){
        System.out.println(ve+"在陆地跑");
    }
}

class AirVehicle{
    public void run(String ve){
        System.out.println(ve+"在天空飞");
    }
}

class SeaVehicle{
    public void run(String ve){
        System.out.println(ve+"在海航线");
    }
}

方法单一职责(只有在方法少可用)

public class singleton2 {
    public static void main(String[] args) {
        Vehicle2 vehicle2=new Vehicle2();
        vehicle2.runRoad("汽车");
        vehicle2.runAir("飞机");
        vehicle2.runSea("轮船");
    }
}

//非标准单例模式,在方法少的情况下,可以在方法中实现单例
class Vehicle2{
    public void runRoad(String ve){
        System.out.println(ve+"在公路上跑");
    }

    public void runAir(String ve){
        System.out.println(ve+"在天空上飞行");
    }

    public void  runSea(String ve){
        System.out.println(ve+"在大海上航行");
    }
}

使用原则

  • 降低类的复杂度,一个类只负责一项职责。
  • 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违 反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则

优点

  • 类的复杂性降低,对于实现什么职责都有清晰明确的定义。
  • 可读性提高。
  • 可维护性提高。
  • 变更引起的风险降低,一个接口的修改只对相应的实现类有影响,对其他接口无影响,这对系统的扩展性,维护性都有非常大的帮助。

接口隔离原则(Interface Segregation Principle, ISP)

基本介绍

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上

示例

非接口隔离原则(错误示范)

类图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ItR6rclp-1609323889833)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201125141055412.png)]

代码:

可以看出A通过接口使用B只用了三个方法,但是B却要实现五个方法,这样就是不符合接口的隔离原则。

package com.fys.segregation;

public class SegregationTest {

}

interface interface1{
    public void test1();
    public void test2();
    public void test3();
    public void test4();
    public void test5();
}

class B implements  interface1{

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

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

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

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

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

class D implements  interface1{

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

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

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

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

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

class A{
    public void depends1(interface1 i){
        i.test1();
    }
    public void depends2(interface1 i){
        i.test2();
    }
    public void depends3(interface1 i){
        i.test3();
    }
}

class C{
    public void depends1(interface1 i){
        i.test1();
    }
    public void depends2(interface1 i){
        i.test4();
    }
    public void depends3(interface1 i){
        i.test5();
    }
}
遵循接口隔离原则

按隔离原则应当这样处理: 将接口Interface1拆分为独立的几个接口, 类A和类C分别与他们需要的接口建立依赖 关系。也就是采用接口隔离原则

类图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Vx638Iu-1609323889848)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201125143024695.png)]

代码

这样把一个大接口,分到最小接口,就可以满足接口隔离原则

package com.fys.segregation;

public class SegregationTest2{
    public static void main(String[] args) {
        AA aa=new AA();
        aa.depends1(new BB());
        aa.depends2(new BB());
        aa.depends3(new BB());

        CC cc=new CC();
        cc.depends1(new DD());
        cc.depends2(new DD());
        cc.depends3(new DD());
    }
}

interface interface2{
    public void test1();
}
interface interface3{
    public void test2();
    public void test3();
}
interface interface4{
    public void test4();
    public void test5();
}

class BB implements  interface2,interface3{

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

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

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

class DD implements  interface2,interface4{

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

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

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

class AA{
    public void depends1(interface2 i){
        i.test1();
    }
    public void depends2(interface3 i){
        i.test2();
    }
    public void depends3(interface3 i){
        i.test3();
    }
}

class CC{
    public void depends1(interface2 i){
        i.test1();
    }
    public void depends2(interface4 i){
        itest4();
    }
    public void depends3(interface4 i){
        i.test5();
    }
}

使用原则

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

优点

符合我们常说的高内聚,低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性


依赖倒转原则(Dependence Inversion Principle, DIP)

基本介绍

依赖倒转原则(Dependence Inversion Principle)是指:

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

    • 高层就是调用者,底层就是被调用者,例如:

    • //person就是调用者即高层,email就是被调用者即底层
      class Person{
          public void sendEmail(Email email){
             email.send();
          }
      }
      
  • 抽象不应该依赖细节,细节应该依赖抽象

  • 依赖倒转(倒置)的中心思想是面向接口编程

  • 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象 指的是接口或抽象类,细节就是具体的实现类

  • 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的 任务交给他们的实现类去完成

  • 依赖倒置原则的本质就是通过抽象(抽象类或接口)使各个类或模块实现彼此独立,不互相影响,实现模块间的松耦合。

通俗易懂的话就是:

  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的
  • 接口或抽象类不依赖于实现类
  • 实现类依赖于接口或抽象类

示例

非依赖倒转(错误示范)

在这种情况下,person和email之间发生了依赖关系,而且person想增加其它发送方式,又要新建其他方法非常不方便。

package com.fys.inversion;

public class DependencyInversion {
    public static void main(String[] args) {
        Person person=new Person();
        person.sendEmail(new Email());
    }
}

class Email{
    public void send(){
        System.out.println("发送邮件");
    }
}

class Person{
    public void sendEmail(Email email){
       email.send();
    }
}
遵循依赖倒转

让功能类实现接口,person依赖接口,一样也可以完成需求,而且扩展性很高,满足了实现类只依赖接口。

package com.fys.inversion.improve;

public class DependencyInversion {
    public static void main(String[] args) {
        Person person=new Person();
        person.receive(new Email());
        person.receive(new Qq());
    }
}
interface Send{
    public void getInfo();
}


class Email implements Send{

    @Override
    public void getInfo() {
        System.out.println("发送邮件");
    }
}

class Qq implements Send{

    @Override
    public void getInfo() {
        System.out.println("发送qq消息");
    }
}

class Person{
    public void receive(Send send){
        send.getInfo();
    }
}

依赖倒转的三种方式

接口传递
package com.fys.inversion.three;

public class DependencyPass {
    public static void main(String[] args) {
        OpenAndClose openAndClose=new OpenAndClose();
        openAndClose.open(new ChangHong());
    }
}
//打开与关闭的抽象
interface IOpenAndClose{
    public void open(ITV itv);
}
//电视机的抽象
interface ITV{
    public void play();
}
//电视机的实现
class ChangHong implements ITV{

    @Override
    public void play() {
        System.out.println("长虹电视打开");
    }
}
//打开与关闭的实现
class OpenAndClose implements IOpenAndClose{
    @Override
    public void open(ITV itv) {
        itv.play();
    }
}
构造方法传递
package com.fys.inversion.three;

public class DependencyPass {
    public static void main(String[] args) {
        OpenAndClose openAndClose=new OpenAndClose(new ChangHong());
        openAndClose.open();
    }
}
//打开与关闭的抽象
interface IOpenAndClose{
    public void open();
}
//电视机的抽象
interface ITV{
    public void play();
}
//电视机的实现
class ChangHong implements ITV{

    @Override
    public void play() {
        System.out.println("长虹电视打开");
    }
}
//打开与关闭的实现
class OpenAndClose implements IOpenAndClose{

    private ITV itv;

    public OpenAndClose(ITV itv){
        this.itv=itv;
    }

    @Override
    public void open() {
        this.itv.play();
    }
}
setter方式传递
package com.fys.inversion.three;

public class DependencyPass {
    public static void main(String[] args) {
        OpenAndClose openAndClose=new OpenAndClose();
        openAndClose.setTv(new ChangHong());
        openAndClose.open();
    }
}
//打开与关闭的抽象
interface IOpenAndClose{
    public void open();
    public void setTv(ITV itv);
}
//电视机的抽象
interface ITV{
    public void play();


}
//电视机的实现
class ChangHong implements ITV{

    @Override
    public void play() {
        System.out.println("长虹电视打开");
    }
}
//打开与关闭的实现
class OpenAndClose implements IOpenAndClose{

    private ITV itv;

    @Override
    public void open() {
        this.itv.play();
    }

    @Override
    public void setTv(ITV itv) {
        this.itv=itv;
    }
}

好处

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

使用原则

  • 每个类尽量都要有接口或抽象类,或者抽象类和接口都有: 依赖倒置原则的基本要求,有抽象才能依赖倒置
  • 变量的表面类型尽量是接口或者抽象类
  • 任何类都不应该从具体类派生
  • 尽量不要重写基类已经写好的方法(里式替换原则)
  • 结合里式替换原则来使用: 结合里式替换原则和依赖倒置原则我们可以得出一个通俗的规则,接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。

里氏替换原则(Dependence Inversion Principle, DIP)

了解继承

继承是面向对象三大特性之一,是一种非常优秀的语言机制,它有如下有点:

  • 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性
  • 提高代码的重用性
  • 子类可以形似父类
  • 提高代码的可扩展性
  • 提高产品或项目的开放性

继承有它的优点,但是也有一些致命的缺点:

  • 继承具有侵入性,只要子类继承了父类,那么子类必须拥有父类的所有属性和方法
  • 降低了代码的灵活性
  • 增强了耦合性。当父类中发生方法,属性的修改时需要考虑子类是否修改,而且在缺乏规范的情况下,还可能发生大段的代码重构

基本介绍

  • 里氏替换原则(Liskov Substitution Principle)在1988年,由麻省理工学院的以为姓里 的女士提出的。
  • 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序 P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
  • 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
  • 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可 以通过聚合,组合,依赖 来解决问题。

示例

非里氏原则(错误示范)

这个程序,B继承了A,却把A的方法重写了,如果B没有注意,还以为调用的是A的方法,结果就会出错了。

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;
    }
}
遵循里氏原则

对于上面的程序,我们该怎么优化呢?

通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉, 采用依赖,聚合,组合等关系代替.

public class LisKov {
    public static void main(String[] args) {
        B b=new B();
        b.func3(1,1);
    }
}

class C{

}

class A extends C {
    public int func1(int a, int b) {
        return a - b;
    }
}
class B extends C {
    //这里是用组合的方式使用A
    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;
    }
    public int func3(int a, int b){
        return this.a.func1(a,b);
    }
}

加深理解

  • 其实通俗说来,里式替换原则就是:子类可以扩展父类的功能,但不能改变父类原有的功能
  • 当继承不能满足里式替换原则时应该进行重构
    • 把冲突的派生类与基类的公共部分提取出来作为一个抽象基类,然后分别继承这个类。
    • 改变继承关系:从父子关系变为委托关系
  • 在类中调用其他类时务必要使用父类或接口, 如果不能使用父类或接口, 则说明类的设计已经违背了LSP原则
  • 如果子类不能完整地实现父类的方法, 或者父类的某些方法在子类中已经发生“畸变”, 则建议断开父子继承关系, 采用依赖、 聚集、 组合等关系代替继承

多态与LSP是否矛盾

在学习Java里面的多态时,我们知道多态的前提就是要有子类继承父类并且子类重写父类的方法。那这是否和LSP矛盾呢?因为LSP要求我们只可以扩展父类的功能,但不能改变父类原有的功能,也就是不能对父类原有的方法进行重写,只能去实现父类的方法或重载。下面是我在知乎上找到的一种比较合理的解释:

  • 里式替换原则是针对继承而言的,如果继承是为了实现代码的重用,也 就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过添加新的方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时就可以使用子类对象将父类对象替换掉。
  • 如果继承的目的是为了多态,而多态的前提就是子类重写父类的方法,为了符合LSP,我们应该将父类重新定义为抽象类,并定义抽象方法,让子类重新定义这些方法。由于父类是抽象类,所以父类不能被实例化,也就不存在可实例化的父类对象在程序里,就不存在子类替换父类时逻辑不一致的可能。

不符合LSP最常见的情况就是:父类和子类都是非抽象类,且父类的方法被子类重新定义,这样实现继承会造成子类和父类之间的强耦合,将不相关的属性和方法搅和在一起,不利于程序的维护和扩展。所以总结一句:尽量不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承(也就是面向接口和抽象编程)


迪米特法则((Law of Demeter, LoD))

基本介绍

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

示例

非迪米特法则(错误示范)

ming和mark互相不认识,那为什么代表ming类中会有代表mark类呢?这样明显是违背了迪米特法则的。现在我们对上面的代码进行重构,根据迪米特法则的第二点:从依赖者的角度来看,只依赖应该依赖的对象。在本例中,张三只认识李四,那么只能依赖李四

public class Demeter {
    public static void main(String[] args) {
        Ming ming=new Ming();
        ming.work();
    }
}
//ming需要找朋友jack完成工作,jack不会去求助自己的朋友mark,mark完成了
class Ming{

    public Jack getFriend(){//方法返回值,jack是直接朋友
        return new Jack();
    }

    public void work(){
       Jack jack=getFriend();
       Mark mark=jack.getFriend();//局部变量,mark是间接朋友
       mark.work();
    }
}

class Jack{
    public Mark getFriend(){
        return new Mark();
    }
}

class Mark{
    public void work(){
        System.out.println("做完");
    }
}
遵循迪米特法则
public class Demeter {
    public static void main(String[] args) {
        Ming ming=new Ming();
        ming.work();
    }
}
//ming需要找朋友jack完成工作,jack不会去求助自己的朋友mark,mark完成了
class Ming{

    public Jack getFriend(){//方法返回值,jack是直接朋友
        return new Jack();
    }

    public void work(){
       Jack jack=getFriend();
       jack.work();
    }
}

class Jack{
    public Mark getFriend(){
        return new Mark();
    }
    public void work(){
        Mark mark=getFriend();//局部变量,mark是间接朋友
        mark.work();
    }
}

class Mark{
    public void work(){
        System.out.println("做完");
    }
}

注意事项

  • 迪米特法则的目的是让类之间解耦,降低耦合度,提高类的复用性。但是设计原则并非有利无弊,使用迪米特法则会产生大量的中转类或跳转类,导致系统复杂度提高。在实际的项目中,需要适度的考虑这个原则,不能因为套用原则而反而使项目设计变得复杂。
  • 由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系

合成复用原则(Composite Reuse Principle)

基本介绍

原则是尽量使用合成/聚合的方式,而不是使用继承


开闭原则(Open Close Principle, OCP)

基本介绍

  • 开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则
  • 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节
  • 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
  • 开闭原则是一个非常基础的原则,其他的五个原则都是开闭原则的具体,也就是说其他的五个原则是指导设计的工具和方法,而开闭原则才是它们的精神领袖。从另一个角度说,开闭原则就是抽象类,其他五大原则是具体的实现类,开闭原则是一种纲领性的框架,五大原则在这个框架里添砖加瓦。所以这么说吧,只要我们遵守好其他的五大原则,那么我们设计的软件自然就遵守了开闭原则,

示例

非开闭原则(错误示范)

可以看到GraphicEditor是使用方,但是现在新增三角形会修改使用方,不满足开闭原则。

package com.fys.ocp;

public class Ocp {

   public static void main(String[] args) {
      GraphicEditor graphicEditor = new GraphicEditor();
      graphicEditor.drawShape(new Rectangle());
      graphicEditor.drawShape(new Circle());
      graphicEditor.drawShape(new Triangle());
   }

}

//这是一个用于绘图的类 [使用方]
class GraphicEditor {
   //接收Shape对象,然后根据type,来绘制不同的图形
   public void drawShape(Shape s) {
      if (s.m_type == 1)
         drawRectangle(s);
      else if (s.m_type == 2)
         drawCircle(s);
      else if (s.m_type == 3)
         drawTriangle(s);
   }

   //绘制矩形
   public void drawRectangle(Shape r) {
      System.out.println(" 绘制矩形 ");
   }

   //绘制圆形
   public void drawCircle(Shape r) {
      System.out.println("绘制圆形");
   }

   //绘制三角形
   public void drawTriangle(Shape r) {
      System.out.println("绘制三角形");
   }
}

//Shape类,基类
class Shape {
   int m_type;
}

class Rectangle extends Shape {
   Rectangle() {
      super.m_type = 1;
   }
}

class Circle extends Shape {
   Circle() {
      super.m_type = 2;
   }
}

//新增画三角形
class Triangle extends Shape {
   Triangle() {
      super.m_type = 3;
   }
}

遵循开闭原则

修改过后,需要新增图形,只需要继承基类,然后实现方法就可以啦

package com.fys.ocp;

public class Ocp {

   public static void main(String[] args) {
      GraphicEditor graphicEditor = new GraphicEditor();
      graphicEditor.drawShape(new Rectangle());
      graphicEditor.drawShape(new Circle());
      graphicEditor.drawShape(new Triangle());
   }

}

//这是一个用于绘图的类 [使用方]
class GraphicEditor {
   //接收Shape对象,然后根据type,来绘制不同的图形
   public void drawShape(Shape s) {
      s.draw();
   }
}

//Shape类,基类
abstract class Shape {
   int m_type;
   public abstract void draw();
}

class Rectangle extends Shape {
   Rectangle() {
      super.m_type = 1;
   }

   @Override
   public void draw() {
      System.out.println("画矩形");
   }
}

class Circle extends Shape {
   Circle() {
      super.m_type = 2;
   }

   @Override
   public void draw() {
      System.out.println("画圆形");
   }
}

//新增画三角形
class Triangle extends Shape {
   Triangle() {
      super.m_type = 3;
   }

   @Override
   public void draw() {
      System.out.println("画三角形");
   }
}

注意:

把开闭原则应用于实际项目中,我们需要注意至关重要的一点:抽象约束
 抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:

  • 通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法
  • 参数类型、引用对象尽量使用接口或者抽象类,而不是实现类
  • 抽象层尽量保持稳定,一旦确定即不允许修改

总结每个原则的核心

原则 缩写 核心思想
单一职责 SRP 类功能越少越好。一个类所拥有的方法尽可能为最少,便于维护,而且在用这个类的时候,如果该类的方法过多的话,在使用这个类的时候就会不合适,而且会出现不必要的麻烦。
接口隔离 ISP 类似SRP(单一职责),功能越少越好。宁肯多写一个接口,也不用在一个接口里写太多的方法。
依赖倒置原则 DIP 高端类尽量依赖于接口,而不依赖于低端子类。就是电脑主板依赖于接口,而内存条、硬盘等去实现接口。如果主板直接依赖于内存条、硬盘的话,内存条或者硬盘出现问题,整个主板就都用不成了。
里氏替换原则 LSP 表示子类替换父类,父类指向子类引用对象。声明一个父类,new上一个子类,来调用父类的方法,替换父类。
迪米特法则 LOD 类与类之间的关联关系越少越好。如果类与类之间的关联关系多的话,每修改一个类就要去修改与其关联的所有的类,如果有一个“调度中心”的话,任何一个类出现问题,不管是修改还是删除,都是“调度中心”的事儿了,就不用管其他的类了。
复用原则 CARP 原则是尽量使用合成/聚合的方式,而不是使用继承
开放封闭原则 OCP 对外扩展开放,对内修改封闭。

总结设计原则核心思想

  • 每一个原则都在强调的宗旨是什么:解耦,单一,高内聚
  • 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代 码混在一起。
  • 针对接口编程,而不是针对实现编程。
  • 为了交互对象之间的松耦合设计而努力

参考文章

  • www.atguigu.com

  • https://blog.csdn.net/fyj13925475957/article/details/103178579

原则 缩写 核心思想
单一职责 SRP 类功能越少越好。一个类所拥有的方法尽可能为最少,便于维护,而且在用这个类的时候,如果该类的方法过多的话,在使用这个类的时候就会不合适,而且会出现不必要的麻烦。
接口隔离 ISP 类似SRP(单一职责),功能越少越好。宁肯多写一个接口,也不用在一个接口里写太多的方法。
依赖倒置原则 DIP 高端类尽量依赖于接口,而不依赖于低端子类。就是电脑主板依赖于接口,而内存条、硬盘等去实现接口。如果主板直接依赖于内存条、硬盘的话,内存条或者硬盘出现问题,整个主板就都用不成了。
里氏替换原则 LSP 表示子类替换父类,父类指向子类引用对象。声明一个父类,new上一个子类,来调用父类的方法,替换父类。
迪米特法则 LOD 类与类之间的关联关系越少越好。如果类与类之间的关联关系多的话,每修改一个类就要去修改与其关联的所有的类,如果有一个“调度中心”的话,任何一个类出现问题,不管是修改还是删除,都是“调度中心”的事儿了,就不用管其他的类了。
复用原则 CARP 原则是尽量使用合成/聚合的方式,而不是使用继承
开放封闭原则 OCP 对外扩展开放,对内修改封闭。

总结设计原则核心思想

  • 每一个原则都在强调的宗旨是什么:解耦,单一,高内聚
  • 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代 码混在一起。
  • 针对接口编程,而不是针对实现编程。
  • 为了交互对象之间的松耦合设计而努力

参考文章

  • www.atguigu.com

  • https://blog.csdn.net/fyj13925475957/article/details/103178579

你可能感兴趣的:(java学习,java,设计模式)