设计模式-绪论

绪论

学习方法:

​ 应用场景 > 设计模式 > 剖析原理(画出类图) > 分析实现步骤(图解) > 代码实现 > 框架或者项目源码分析

一、设计模式的七大设计原则

  1. 单一职业原则
  2. 接口隔离原则
  3. 依赖倒转原则
  4. 里氏替换原则
  5. 开闭原则
  6. 迪米特法则
  7. 合成复用原则

1. 单一职业原则

定义:一个类只负责一项原则,如果类A负责职责1和职责2,那么当职责1因其需求变更而改变A类的时候,可能会造成指责2执行错误。因此需要将A类按照不同的职责,拆分成A1和A2两个类。

a. 单一职责原则的注意事项和细节:

(1)降低类的复杂度,一个类只负责一项职责

(2)提高类的可读性和可维护性

(3)降低类中方法的变更引起的风险

(4)通常情况下我们需要遵守单一职责原则, 只有在逻辑足够简单,才可以在代码级违反单一职责原则;只有在类中方法足够少的时 候,才可以在方法级别保持单一职责原则。

2. 接口隔离原则

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

a. 传统方法存在的问题和接口隔离原则对其的改进:

(1)传统方法中:类A通过接口Interface1依赖于B,类C通过Interface1依赖于类D,如果接口Interface1对于类A和类C来说不是最小接 口,那么类B和类D就必须实现Interface1的全部方法(而其中的部分方法是类B和类D不需要的)。

​ 比如说,A只会依赖于B使用Interface1的1、2、3三个方法而C只会依赖于D使用Interface1的1、4、5三个方法。那么B和D就 不需要实现Interface1的全部方法,只需要实现其一部分的方法即可!
在这里插入图片描述

(2)在传统方法中的改进:

​ 将接口Interface1拆分成独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系,也就是采用接口的隔离原则。

​ 将Interface1中出现的方法,根据实际的情况,将其拆分成为三个接口,拆分后的结构如下:
在这里插入图片描述

3. 依赖倒置原则

定义:

1)高层模块不依赖于底层模块,两者都依赖于其抽象;

2)抽象不依赖于细节,而细节要依赖于抽象;

3)该原则的中心思想即面向接口编程

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

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

a. 依赖关系传递的三种方式:

(1)接口传递

(2)构造器传递

(3)setter方法传递

代码示例:

/**
 * @Author: ZhangLingRan
 * @Description: 依赖倒置的三种依赖关系传递方法
 * @DateTime: 2021/11/9 9:47
 */
public class ThreePropMethod {

    public static void main(String[] args) {

        IReceiver1 receiver = new Email1();

        // 方式1: 通过接口来传递依赖关系
        Person1 person1 = new Person1();
        person1.receive(receiver);

        // 方式2: 通过构造器来传递依赖关系
        Person2 person2 = new Person2(receiver);
        person2.receive();

        // 方式3: 通过接口来传递依赖关系
        Person3 person3 = new Person3();
        person3.setter(receiver);
        person3.receive();
    }

}

/**
 * 方式1: 通过接口来传递依赖关系
 */
class Person1 {
    public void receive(IReceiver1 receiver) {
        System.out.println(receiver.getInfo());
    }
}

/**
 * 方式2: 通过构造器传递依赖关系
 */
class Person2 {

    private IReceiver1 receiver;

    public Person2(IReceiver1 receiver) {
        this.receiver = receiver;
    }

    public void receive() {
        System.out.println(this.receiver.getInfo());
    }
}


/**
 * 方式3: 通过构造器传递依赖关系【使用方】
 */
class Person3 {

    private IReceiver1 receiver;

    public void setter(IReceiver1 receiver) {
        this.receiver = receiver;
    }

    public void receive() {
        System.out.println(this.receiver.getInfo());
    }
}

/**
 * 接收器接口【这里就是提供方】
 */
interface IReceiver1 {
    /**
     * 接收器返回消息
     * @return
     */
    String getInfo();
}

/**
 * 电子邮件
 */
class Email1 implements IReceiver1 {
    @Override
    public String getInfo() {
        return "接收到了电子邮件信息...";
    }
}

b. 依赖倒置原则的注意事项和细节

(1)低层模块要尽量有抽象类或接口,或两者都有,程序的稳定性更好

(2)变量的声明类型尽量是抽象类或者是接口,这样变量引用和实际对象间就会存在一个缓冲层,利于程序的扩展和优化。

(3)继承时遵循里氏替换原则

4. 里氏替换原则

a. 关于继承的一些思考

  1. 继承的定义:在父类中实现的方法,实际上是在设定规范和契约,虽然它不要求子类必须遵循这些契约,但如果子类对这些已经实现好的方法进行任意的修改,就会对整个继承体系造成破坏。
  2. 继承带来一定便利的同时,也会带来弊端:继承会给程序带来侵入性,降低程序的可移植性,增加对象间的耦合度,如果一个类被其他类继承,则当该类修改的时候,必须要考虑到所有的子类,且父类修改之后,所有涉及到的子类功能都可能会发生故障。
  3. 在编程过程中,该如何正确的使用继承?==> 需要遵循里氏替换原则

b. 里氏替换原则的定义:

  1. 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的程序P在所有的对象o1替换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。 换句话说:所有引用基类的地方必须能够透明的时候其子类!
  2. 在使用继承的时候,子类中尽量不要去重写父类的方法(做到这一点,其实就遵守了里氏替换原则了)
  3. 里氏替换原则告诉我们,遵循里氏替换原则,继承实际上让两个类的耦合度增强了。在适当的情况下,如果一定要在子类中使用父类的方法,可以将原有的继承关系去掉,将原来的子类和父类的公共方法提取并抽象成为一个基类(Base),并使原来的子类和父类共同继承该基类,另外也可以通过聚合、组合、依赖来解决该问题。

5. 开闭原则

定义:

1)一个软件实体(类、模块、函数)应该对扩展是开放的(对提供方而言)对修改是关系的(对使用方而言)。用抽象来构建框架,用实现来扩展细节。

2)软件的需求发生变化的时候,尽量通过扩展软件实体的行为来实现变化,而不是修改已有的代码。

3)编程中使用其他的原则、设计模式的目的就是为了软件设计能够遵循开闭原则(可见开闭原则之重要!)

如上述依赖倒置原则的代码就是符合开闭原则的设计

6. 迪米特法则

定义:

1)一个对象应该对其他的对象保持最少的了解

2)类与类之间的关系越密切,其耦合度越高

3)迪米特法则又叫最少直到原则,即一个类对自己所依赖的类知道的越少越好。也就是说,对于被依赖的类不管有多复杂,都尽量将逻辑封装到类的内部。对外只提供一个public方法即可,而不需要对外泄露任何信息

4)迪米特法则还有一个更加简单的定义:只与直接的朋友进行通信

5)直接的朋友:一个类A与类B之间具有耦合关系,耦合关系有很多种:依赖、关联、组合、聚合等等。那么当B出现在A的成员变量、方法参数或方法返回值中的时候,称B是A的直接朋友。而当B出现在A的局部变量中的时候,B不是A的直接朋友。也即 4)的另一种说法就是,陌生的类最好不要以局部变量的形式出现在类的内部

如果在开发过程中,类B并不是类A的直接朋友,那么需要按照迪米特法则,要避免在类A中出现这样非直接朋友关系的耦合。

a. 那么具体的使用步骤是什么呢?

  1. 检查类A中有哪些直接朋友(在成员变量、方法参数、方法返回值中出现的类)和陌生类(以局部变量的形式出现的类)

  2. 分析使用到了陌生类B的方法F,判断是不是可以将该功能移植到陌生类其他依赖的类中去实现,并在记为F1,并通过将方法参数传入方法F中,并在方法F中调用相关方法(F1)以实现该功能。如下所示:

    class A {
        // B类出现在了方法F的内部,这时候需要根据迪米特法则对F进行修改。
        public void F(BManager manager) {
        	List<B> list = manager.getBs();
            for (B b : list) {
                print(b);
            } 
        }
    }
    
    class BManager {
        public List<B> getBs(){
            return ...;
        }
    }
    
    class B {
        
    }
    

    修改后的代码为:

    class A {
        // 这样修改,A中将不会在方法内部只用陌生类B了。
        public void F(BManager manager) {
        	manager.F1();
        }
    }
    
    class BManager {
        public List<B> getBs(){
            return ...;
        }
        
        public void F1() {
            List<B> list = this.getBs();
            for (B b : list) {
                print(b);
            } 
        }
    }
    
    class B {
        
    }
    

b. 迪米特法则使用的注意事项

(1)迪米特法则的核心是降低类之间的耦合度

(2)迪米特法则只要求降低类(对象)之间的耦合关系,而不是要消除耦合关系。

7. 合成复用原则

基本介绍:尽量使用合成/聚合的方式,而不是继承的方式。

设计原则的核心思想就是:

  1. 找出应用中可能需要变化的地方,把他们独立出来,不要与那些不需要变化的代码混在一起
  2. 面向接口编程而不是面向具体实现编程
  3. 不断地对交互对象进行松耦合设计

二、UML类图设计

在这里插入图片描述

1. 实体

主要包括了类和接口

2. 关系

1)依赖(Dependency)

如下图所示,在PersonServiceBean类中使用到了IDCard类、Person类、PersonDao类、Department类;那么可以说PersonServiceBean类依赖于其他四个类。

关于依赖有几种写法:(1)A类是B类的成员变量、(2)A类是B类中方法的返回值类型、(3)A类是B类中方法的接收参数类型、(4)A类是在B类的方法内部被使用到了,以局部变量的形式。以上四种情况都可以说明,B类依赖于A类。

该关系的UML表达为:(注意:依赖关系是一个虚线+箭头的形式)
在这里插入图片描述

2)泛化(继承)(Generalization)是依赖关系的一种特例

泛化关系就是继承关系,即如果PersonServiceBean类继承了DaoSupport类,就说PersonServiceBean和DaoSupport之间存在泛化关系。其关系的UML表达为:

3)实现(Realization) 也是一个依赖关系的特例

实现也是依赖关系的一个特例,即PersonServiceBean 类实现了PersonService接口。其UML表达如下(注意是虚线):
在这里插入图片描述

4)关联(Association)

关联关系实际上就是类与类之间的关系,具有导航性(即双向关系或者单向关系)、多重性(即一对一、一对多、多对多)等
在这里插入图片描述

5)聚合(Aggregation)

聚合关系表达的是整体与部分的关系整体和部分是可以分开的。聚合关系是关联关系的特例,所以它同样具有导航性和多重性。

Computer类中有Mouse类和Moniter类的成员变量,如果电脑与显示器和鼠标是可以分离的,那么这就是聚合关系。其代码的实现为:

class Computer {
    // 如果是可以分离,将Mouse 和 Moniter类定义之后不去new一个对象的话,就是可分离的聚合关系【与下边的组合进行对比】
    private Mouse mouse; 
    private Moniter moniter;
    
    public void setMouse(Mouse mouse){...}
    public void setMoniter(Moniter moniter){...}
}

class Mouse {
    
}

class Moniter {
    
}

聚合关系的UML表示为:(实线+空心菱形)

6)组合(Composite) 级联删除

同上边 5)中的聚合关系,当整体与部分不可分离的时候,聚合关系升级为组合关系。其代码实现为:

// Computer 类
class Computer {
    // 将Mouse 和 Moniter类定义后new一个对象的话,就是不可分离的状态,
    // 此时为组合关系【Mouse和Moniter类随着Computer类的创建而创建,销毁而销毁】
    private Mouse mouse = new Mouse(); 
    private Moniter moniter = new Moniter();
}

// Mouse 类
class Mouse {
    
}

// Moniter 类
class Moniter {
    
}

组合关系的UML表示为:

组合与聚合的一个案例

class Person {
    // 因为人(Person)与头(Head)是不可分离的,那么这两者是组合的关系,而人(Person)和身份证(IDCard)是可以分离的。
    private Head head = new Head();
    private IDCard idCard; 
}

class Head {
    
}

class IDCard {
    
}

三、设计模式的分类与概述

设计模式分为三种类型共23种

  • 创建型模式:(共5种)

    单例模式(Singleton)— 8种实现方式

    原型模式(Prototype)— 对象的clone,深拷贝、浅拷贝问题

    建造者模式(Builder)

    工厂方法模式(Factory Method)

    抽象工厂模式(Abstract Factory Method)

  • 结构型模式:(共7种)

    适配器模式(Adaptor)

    桥接模式(Bridge)

    装饰模式(Decorator)— 解决类爆炸问题

    组合模式(Composite)

    外观模式、享元模式(Flyweight)

    代理模式(Proxy)

  • 行为型模式:(共11种)(在方法的角度)

    模板方法模式(Template Method)

    命令模式(Command)

    访问者模式(Visitor)

    迭代器模式(Iterator)

    观察者模式(Observer)

    中介者模式(Mediator)

    备忘录模式(Memento)

    解释器模式(Interpreter)

    状态模式(State)

    策略模式(Strategy)

    责任链模式 (Chain of Responsibility)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zhang L.R.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值