设计模式读书笔记(一)设计模式6原则

本文详细介绍了软件设计中的六大基本原则,包括单一职责原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特法则及开闭原则。每项原则均通过实例说明其应用场景与优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 单一职责原则
  2. 里氏替换原则
  3. 依赖倒置原则
  4. 接口隔离原则
  5. 迪米特法则
  6. 开闭原则

1.单一职责原则

适用于接口、类、方法,要求一个接口或类只由一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一件事。

让我们举个栗子

1497408347495

在以上的接口中,定义了拨号、通话、挂掉,三个行为,但通话属于数据传输,与拨号、挂掉职责不同,所以将接口改成如下

1497408506182

单一职责优势:
1. 类的复杂度降低,实现什么职责都有清晰明确的定义;
2. 可读性提高,复杂性降低
3. 可维护性提高
4. 变更引起的风险降低,扩展性、维护性增强

对于接口,设计时一定要做到单一,但对于实现类就需要多方面考虑,尽量做到只有一个原因引起变化。生搬硬套单一职责会引起类的剧增,给维护带来很多麻烦,人为增加系统复杂性。

2.里氏替换原则

首先让我们来总结一下java中继承的优点:
1. 代码共享、减少创建类的工作量
2. 提高代码重用性、扩展性
3. 提高项目的开放性

java中继承的缺点:
1. 侵入性,一旦继承,就必须拥有父类所有属性和方法
2. 降低灵活性,子类约束多
3. 增强耦合性(代码重构)


里氏替换原则两种定义:
1. 如果对每一个类型为S的对象s1,都有类型为T的对象t1,使得以T定义的所有程序P在所有的对应s1都代换成t1时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
2. 所有引用基类的地方必须能透明的使用其他子类的对象



通俗来讲,只要父类能出现的地方子类就能出现,而且替换为子类也不会产生任何错误或者异常,使用者可能根本不需要知道是父类还是子类。但相反就不行,有子类出现的地方,父类未必能适应。

规范:
1. 子类必须完全实现父类的方法
2. 子类可以有自己的特性
3. 覆盖或实现父类方法时参数可以被放大
4. 覆写或实现父类的方法时输出结果可以被缩小

3.依赖倒置原则

相信做web的朋友并不陌生,spring的其中一个核心就是依赖倒置。

含义
1. 高层模块不应该依赖底层模块,两者都应该依赖其抽象
2. 抽象不应该依赖细节
3. 细节应该依赖抽象

依赖倒置在java中的表现:
1. 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的
2. 接口或抽象类不依赖实现类
3. 实现类依赖接口或者抽象类

依赖的三种写法:
1. 构造函数传递依赖对象
2. Setter方法传递依赖对象
3. 接口声明依赖对象

    //构造传递方式
    public interfice IDriver{
        //一个会开车的老司机
        public void drive();
    }
    public class Driver implements IDrvier{
        private ICar car;
        //构造注入
        public Driver(ICar _car){
            this.car = _car;
        }
        //奔跑吧,野驴
        public void drive(){
            this.car.run();
        }
    }
    //Setter方法传递方式
    public interface IDriver{
        //设置车的型号
        public void setCar(ICar car);
        //一个会开车的老司机
        public void drive();
    }
    public class Driver implements IDrvier{
        private ICar car;
        public void setCar(ICar car){
            this.car = car;
        }
        //奔跑吧,野驴
        public void drive(){
            this.car.run();
        }
    }
}

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


规则:
1. 每个类尽量有接口或抽象类,或者两者兼具
2. 变量的表面类型尽量是接口或者抽象类
3. 任何类都不应该从具体类派生
4. 尽量不要覆写基类方法【如果基类是抽象类,而且这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会产生一定的影响】
5. 结合里氏替换原则使用

4.接口隔离原则

首先明确一下接口
* 实例接口(Object Interface): 在java中声明一个类,然后用new产生一个实例,它是对一个类型的事物的描述,这就是一种接口
* 类接口(Class Interface): java中经常使用的关键字定义的接口


那什么又叫隔离?下面给出两种定义【本质上是要求接口的纯净和细化】
* 客户端不应该依赖它不需要的接口
* 类间的依赖关系应该建立在最小的接口上

比如我们来客串一次星探,挖掘漂亮妹纸

1497420563409

以上是对漂亮妹纸最基本的要求,但审美的观点各有不同,每个时代对美的概念也不一样,以上设计显得不足以应对了,这可咋整?

以上的接口中,设计的过于庞大了些,容纳了一些可变的因素,星探们应该依赖于具有部分特质的妹纸,然而我们将这些特质都封装了起来,放入一个接口,属于过度封装

1497421124170

将原来的接口拆分为两个,一种是外形漂亮的妹纸,一种是气质型的妹纸

  • 将一个臃肿的接口变更为两个独立的接口锁依赖的原则就是接口隔离原则。
  • 接口是设计时对外提供的契约,通过分散定义多个接口,可以预防未来变更的扩散,提高系统灵活性和维护性

接口隔离原则是对接口进行规范约束

  • 接口尽量小【根据接口隔离原则拆分接口时,首先必须满足单一职责原则】
  • 接口高内聚【提高接口、类、模块处理能力;减少对外交互】
  • 定制服务【单独为一个个体提供优良的服务;要求:只提供访问中需要的方法】
  • 接口设计是有限度的【根据项目具体情况进设计】



接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽量使用原子接口或原子类来组装。原子的划分可以根据以下规则衡量:

  1. 一个接口只服务于一个子模块或者业务逻辑
  2. 通过业务逻辑压缩接口中的public方法,接口时常回顾,尽量让接口精简而拒绝臃肿
  3. 已经被污染的接口,尽量去修改,若变更风险较大,则采用适配器模式进行转化处理
  4. 了解手中项目的具体环境,拒绝盲从

5.迪米特法则

迪米特法则也被称为最少知识原则:一个对象应该对其他对象有最少的了解。
一个类应该对自己需要耦合或调用的类知道得最少,被耦合或调用的类内部的如何复杂都不管,只管对方提供的方法和自己调用的就行。

迪米特法则对低耦合提出了明确要求:

  • 只和直接的朋友交流

    1497422937090

      public class void main(String[] args){
          Teacher teacher = new Teacher();
          tacher.commond(new GroupLeader());
      }
    

    如上设计中,teacher的直接对象只有GroupLeader
    朋友类的定义:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类,而Girl就算出现在commond方法体内部,因此不属于Teacher类的朋友类。
    迪米特法则告诉我们一个类只和朋友类交流,但刚才定义的commond方法却与gril类有了交流,声明了一个List<Grils>动态数组,也就与陌生的类Girl有了交流,破坏了Teacher的健壮性。
    方法是类的一个行为,类竟然不知道自己的行为与其他类产生依赖关系,这是不允许的,严重违反了迪米特法则。做如下修改。
    1497423727755
    对设计进行简单的修改,降低了系统间耦合,提高健壮性
    一个类只和朋友交流,不与陌生类交流,不要出现方法链且每一个方法返回类型都相同的情况,类与类之间的关系是建立在类间的,而不是方法间,尽量不引入一个类中不存在的对象,当然JDK提供的除外

  • 朋友间也是有距离的(每个方法访问权限尽量小)

    1497424916139

    一个类似安装向导的程序,下一步->下一步....
    每次是否需要进行下一步操作、以及操作调用都在InstallSoftWare类中控制,显然是有问题的,因为耦合变得异常牢固,对此设计进行以下改动
    1497425168080
    将每次是否需要进行下一步操作、以及操作调用都放在Wizard,且三次操作的方法均为私有,仅提供installWizard()方法给调用者
    *尽量不要对外公布太多public方法和非静态public变量,尽量内敛,多使用private、package-private、protected等访问权限

  • 是自己的就是自己的

    如果一个方法放在本类中,既不增加类间关系,也不对本类产生负面影响,可以放置在本类中

  • 谨慎使用serializable



核心:迪米特原则核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才能提高。其要要求的结果就是产生了大量的中转或跳转类,导致系统的复杂性提高,同时也为维护带来了难度。使用时需要反复权衡,做到结构清晰,高内聚,低耦合。

6.开闭原则

定义:一个软件实体如类、模块、函数应该对扩展开发,对修改关闭。

软件实体(逻辑划分的模块、抽象和类、方法)应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。
产品的生命周期内都会发生变化,所以在设计时尽量适应这些变化,提高稳定性和灵活性。

让我们举个栗子:
图书城图书管理中查看书籍信息

1497426896499

但由于某种原因,所有数据需要打折计算,应用开闭原则,不对原有接口代码进行更改的情况下,做如下设计

1497426999544

增加一个子类,覆写了getPrice();调用时改用OffNovelBook类产生新对象。

变化归纳:

  • 逻辑变化
    • 只改变一个逻辑,不涉及其他模块(eg: a+b+c => abc) ,可以通过修改原有类中方法的方式来完成,前提是所有依赖或关联都按照相同的逻辑处理
  • 子模块变化
    • 一个模块的变化必然影响其他模块,特别是一个低层次的模块变化必然引起高层模块的变化,因此通过扩展完成变化时,高层模块的修改是必然的
  • 可见视图的变化

开闭原则的重要性体现:
1. 版本迭代测试
2. 提高复用性
3. 提高可维护性
4. 面向对象开发的要求

如何使用开闭原则?

  1. 抽象约束

    通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开发。

    1. 通过接口或者抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法
    2. 参数类型、引用对象尽量使用接口或抽象类
    3. 抽象层尽量保持稳定,一旦确定即不允许修改
  2. 元数据(metadata)控制模块行为

    尽量使用元数据(配置参数)来控制程序的行为,减少重复开发。

  3. 制定项目章程

  4. 封装变化(受保护的变化,找出预计有变化或不稳定的点,为其创建稳定的接口)

    • 将相同的变化封装到一个接口或抽象类中
    • 将不同变化封装到不同接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值