七大原则+23种设计模式


菜鸟教程更香

设计模式的意义

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

  • 代码复用性
  • 可读性
  • 可扩展性
  • 可靠性 (当我们增加新的功能后,对原来的功能没有影响)
  • 使程序呈现高内聚,低耦合的特性

1. 七大原则

设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础

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

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

1.1 单一职责原则

大白话解释:一个类就是一个最小的功能单位

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

实例
假设有一个“交通工具”类,他的作用只有一个,就是“运行交通工具”,假设它只有一个run方法,打印“交通工具 xx 在地上跑”这句话。

如果我们的交通工具只是车,这个类没有问题,如果交通工具加上“飞机”、“船”,那么“交通工具 飞机 在地上跑”、“交通工具 船 在地上跑”就不符合实际。

由于交通工具有多个,因此这个类不符合“单一职责原则”。
我们可以将其改为3个类,“水上交通工具”、“空中交通工具”、“陆地交通工具”,分别对海陆空负责(单一职责)。

此外,由于这个类的功能比较单一,只有run方法,我们也可以在方法级别上实现单一职责原则,即为该类创建“水上运行”、“空中运行”、“陆地运行”方法。

注意事项与细节

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

1.2 接口隔离原则

大白话解释:实现接口的所有类都应当觉得接口中没有多余的方法

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

实例
在这里插入图片描述
A通过调用B,需要操作1、2、3,
C通过调用D,需要操作1、4、5,
但是B、D都实现了1、2、3、4、5,显然B、D都实现了多余的方法。

在这里插入图片描述
若要符合“接口隔离原则”,只需要让B、D实现必需的接口即可。

然而,实际中我们不一定能确定B是否真的不需要4、5方法,也不能确定D是否真的不需要2、3方法。
因此,不是说学好设计模式就万事大吉的。
实际还得多方面考虑。

1.3 依赖倒转(倒置)原则

大白话解释:我们都是用接口声明一个变量,而不是直接使用具体类(如声明一个ArrayList,最左边用的是List接口;声明一个HashMap,最左边用的是Map)

Dependence Inversion Principle
描述

  • 高层模块不应该依赖低层模块,二者都应该依赖其抽象
  • 抽象不应该依赖细节,细节应该依赖抽象
  • 依赖倒转(倒置)的中心思想是面向接口编程
  • 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。(在java中,抽象指的是接口或抽象类,细节就是具体的实现类)
  • 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成

就是一句话,前期设计应当从最基本、最核心的抽象入手,构建接口。

实例
用户(User类)通过receive方法接收信息(Message类)。
如果用户接受的消息包括Email、QQ、WeChat…等多种方式,那么我们就需要在User类中写多个receive重载函数分别接收不同的消息类。
然而,谁也不知道以后还会有什么消息类,这就导致每次增加一个消息类,我们都得对User类进行修改。

如果Message不是类,而是一个接口,receive接收的是Message接口,就能解决问题。
只需要让各种不同的消息类实现Message接口,receive就能够接收他们;如果出现新的消息类,也只需要增加该消息类并实现Message接口即可,不需要对原有代码进行更改。

依赖关系传递的方式

  • 声明接口传递:在普通方法参数中声明接口
  • 构造方法传递:在构造方法参数中声明接口
  • setter方法传递:类中有个接口成员,该成员通过setter声明

注意事项和细节

  • 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
  • 变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在 一个缓冲层,利于程序扩展和优化
  • 继承时遵循里氏替换原则

1.4 里氏替换原则

大白话解释:少继承、别重写父类方法

OO中的继承,产生的问题

  • 继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
  • 继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承, 则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障

解决以上问题,考虑里氏替换原则。

描述
Liskov Substitution Principle

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

实例
类A的fun1是减法器,类B继承了类A;但是类B不小心将fun1重写成了加法器。
假设极端情况,类A就只有fun1方法,那么类B继承类A就没有必要了,把A唯一的方法都重写了。

实际编程中常常重写父类的方法,但是整个继承体系的复用性、稳定性较差。
一般可以这么做:让原来的父类A和子类B都继承一个更通俗的基类,取消AB继承关系,AB直接采用聚合、组合、依赖的关系实现方法调用。

比如上述实例,A和B都继承一个基础类Base(为了保证一些基本方法),然后B中声明一个成员类A,此时B可以写一个方法调用A的专有方法即可。

1.5 开闭原则

大白话解释:写的系统易于扩展,不允许修改。

Open Closed Principle
描述

  • 开闭原则是编程中最基础、最重要的设计原则
  • 一个软件实体如类,模块和函数应该对扩展开放,对修改关闭用抽象构建框架,用实现扩展细节
  • 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
  • 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则

1.6 迪米特法则

大白话解释:减少类之间的依赖

描述

  • 一个对象应该对其他对象保持最少的了解
  • 类与类关系越密切,耦合度越大

迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部对外除了提供的public 方法,不泄露任何信息

迪米特法则还有个更简单的定义:只与直接的朋友通信

直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系, 我们就说这两个对象之间是朋友关系。
耦合的方式很多,依赖,关联,组合,聚合 等。其中,我们称出现成员变量方法参数方法返回值的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部

此外,有时候我们也会引入了“陌生的朋友”而不自知。
比如较长的调用链,调用链中可能生成了多个陌生的类,这也是不被允许的。

迪米特法则的目的在于降低类之间的耦合度。

1.7 合成复用原则

大白话解释:多用组合、聚合,少用继承

描述
尽量使用合成/聚合的方式,而不是使用继承。

如果仅仅是为了让B类使用A类的方法,就让B继承A,只是徒增耦合。
我们只需要在B中聚合一个A的对象,或者将A作为B的某个方法参数。

小结

  • 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
  • 针对接口编程,而不是针对实现编程。
  • 为了交互对象之间的松耦合设计而努力

2. UML类图

点击此处进行学习

3. 设计模式

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

  • 创建型模式:单例模式、工厂模式、抽象工厂模式、原型模式、建造者模式。
  • 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
  • 行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者 模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)。

大白话解释:
创建型模式:

  • 单例模式:创建一个对象,使得整个系统中,这个对象有且仅有一个。
  • 工厂模式:多个类的创建交给一个工厂,客户端给出所需的类的类型,工厂返回该对象
  • 抽象工厂模式:给每一类产品都创建一个工厂
  • 原型模式:利用一个对象本体(称为原型),克隆出另一个
  • 建造者模式:一个对象的创建过程比较复杂,通过一系列建造过程完成创建

结构型模式:

  • 适配器模式:将类通过一个“转换器”转换某些属性让它变得适合使用
  • 桥接模式:将类的某一可变属性抽出来作为类组合进去(几何图形有三种颜色,将三种颜色抽取出来作为三个类)
  • 装饰模式:把类放进去加工一下,得到具有额外功能的类
  • 组合模式:具有树形结构的类群,使用组合模式让其变成一个树
  • 外观模式:一大堆类的运行放在一个类中(外观),一键调用
  • 享元模式:实现元素共享
  • 代理模式:为了控制对象的访问,加一个代理人

行为型模式:

  • 模板方法模式:把执行流程(算法)都放在一个方法中,形成一个模板
  • 命令模式:将所有命令单独抽出来作为类来调用,而不是作为方法
  • 访问者模式:A进入B中(B某个方法使用了A),调用A的方法访问B(A的方法中又需要传入B)
  • 迭代器模式:遍历类
  • 观察者模式:一对多关系时,这个一想要通知多个对象
  • 中介者模式:让客户与多个类的沟通,都通过一个中介类
  • 备忘录模式:记录一个类的状态
  • 解释器模式:解释一串表达式
  • 状态模式:一个流程存在特别多的状态,使用状态模式
  • 策略模式:把类的某一类方法(策略)抽出来作为类,实现动态改变
  • 职责链模式(责任链模式):拦截链,轮着一个拦一个

3.1单例模式

单例模式有八种实现方法(有一种是错误示范):

  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式(线程不安全)
  • 懒汉式(线程安全,同步方法)
  • 懒汉式(线程安全,同步代码块)
  • 双重检查
  • 静态内部类
  • 枚举

代码以及注释详解

3.2抽象工厂模式

简单工厂模式
在这里插入图片描述
AbstractProduct:抽象类产品,比如说披萨
ConcreteProduct:具体的产品,比如说中国披萨,美国披萨,巴西披萨等
SimpleFactory:用于创建披萨类,依赖于抽象披萨
FacrotyClient:工厂的使用者,通过FactoryClient,调用SimpleFactory生成不同的披萨。

所有代码以及解释

3.3原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。
Spring的bean.xml中配置的bean,scope可以选择单例,也可以选择prototype,即原型模式创建。
如果你的bean中有初始化信息,那么通过prototype模式创建的bean,都会带上这些初始化信息

原理:实现Cloneable接口,利用Object的clone,实现克隆

浅拷贝:使用clone克隆的对象,基本类型是值传递(新的对象),引用类型只是地址传递(依旧是指向旧的对象,如果此时对该对象修改,也会影响原有对象)。

深拷贝:
对于拷贝对象中的引用类型,也实现Cloneable接口,然后对其单独处理。但是这种方式局限性太大,不推荐,直接学习另一种方式,利用反序列化

原理:序列化之后,能够保存所有“值信息”
反序列化得到的是一个新的对象。

public class PrototypePattern {
    public static void main(String[] args) {
        Sheep s1 = new Sheep("duoli","black");
        Sheep friend = new Sheep("friend","red");
        s1.setFriend(friend);
        Sheep s2 = (Sheep) s1.deepClone();

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1.getFriend()==s2.getFriend());
    }
}

class Sheep implements Serializable {
    private String name;
    private String color;

    // 深拷贝测试对象
    private Sheep friend;

    Sheep(String name,String color){
        this.name=name;
        this.color=color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", friend=" + friend +
                '}';
    }

    Object deepClone() {

        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        ByteArrayInputStream bis = null;


        try {
            // 序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            return (Sheep) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }finally {
            try {
                if (bos != null) {
                    bos.close();
                }
                if (oos != null) {
                    oos.close();
                }
                if (bis != null) {
                    bis.close();
                }
                if (ois != null) {
                    ois.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    public void setFriend(Sheep friend) {
        this.friend = friend;
    }

    public Sheep getFriend() {
        return friend;
    }
}
  1. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  2. 不用重新初始化对象,而是动态地获得对象运行时的状态
  3. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
  4. 在实现深克隆的时候可能需要比较复杂的代码
  5. 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则

3.4建造者模式

BuilderPattern
盖房子,需要逐步完成一系列工序才能得到最终的房子。
建造者模式,就是将产品(房子)与产品构造过程(一系列工序)进行解耦。

涉及角色:
产品:Product,比如一栋房子
抽象建造者:Builder,大家一直认为施工队该有的基本操作
具体建造者:ConcreteBuilder,实际开工的工人,只提供动作,指挥者让干嘛就干嘛
指挥者:Director,工头(隔离了老板与工人直接接触,负责控制房子的生产过程)

调用过程:
(客户端)老板告诉(指挥者)工头,我要“这种”房子(指定建造者,
工人),工头对这类房子的建造方式已经记住了,然后告诉工人开始搭建,最后工头将房子交给老板。

此处老板指定建造者,而不是老板定制(指定)房子,是因为:
如果定制房子,老板就得对房子的参数非常熟悉才行
如果有新的建造者加入,就得更改客户端代码

package creationMode._4_builderPattern;

public class BuilderPattern {
    public static void main(String[] args) {
        // 老板跟包工头说要一个小平房
        Director director = new Director(new ConcreteHouseBuilderOne());
        House result1 = director.getResult();
        System.out.println(result1);

        // 老板跟包工头说要一栋高楼
        director = new Director(new ConcreteHouseBuilderTwo());
        House result2 = director.getResult();
        System.out.println(result2);

    }
}

// 产品:房子
class House{
    private String name;
    private Integer height;
    House(){}

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getHeight() {
        return height;
    }

    public void setHeight(Integer height) {
        this.height = height;
    }

    @Override
    public String toString() {
        return "House{" +
                "name='" + name + '\'' +
                ", height=" + height +
                '}';
    }
}

// 抽象建造者
interface AbstractHouseBuilder{
    void buildStepOne();
    void buildStepTwo();
    House getResule();
}

// 具体建造者:工人1号
class ConcreteHouseBuilderOne implements AbstractHouseBuilder{
    private House house = new House();
    @Override
    public void buildStepOne() {
        this.house.setName("小平房");
    }
    @Override
    public void buildStepTwo() {
        this.house.setHeight(10);
    }

    @Override
    public House getResule() {
        return house;
    }
}

// 具体建造者:工人2号
class ConcreteHouseBuilderTwo implements AbstractHouseBuilder{
    private House house = new House();
    @Override
    public void buildStepOne() {
        this.house.setName("大高楼");
    }
    @Override
    public void buildStepTwo() {
        this.house.setHeight(100);
    }

    @Override
    public House getResule() {
        return house;
    }
}

// 指挥者:包工头
class Director{
    private AbstractHouseBuilder builderOne;

    Director(AbstractHouseBuilder builderOne){
        this.builderOne = builderOne;
    }

    House getResult(){
        builderOne.buildStepOne();
        builderOne.buildStepTwo();
        return builderOne.getResule();
    }
}

StringBuilder使用的建造者模式

  • Appendable 接口定义了多个append方法(抽象方法), 即Appendable 为抽象建造者, 定义了抽象方法
  • AbstractStringBuilder 实现了 Appendable 接口方法,这里的AbstractStringBuilder 已经是建造者,只是不能实例化
  • StringBuilder 即充当了指挥者角色,同时充当了具体的建造者,建造方法的 实现是由 AbstractStringBuilder 完成, 而StringBuilder 继承了 AbstractStringBuilder

特点

  • 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
  • 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同 的产品对象
  • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
  • 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程, 系统扩展方便,符合 “开闭原则”
  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化, 导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式.
  • 抽象工厂模式VS建造者模式:抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过完成一系列工序而产生一个新产品

3.5适配器模式

描述

  1. 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
  2. 适配器模式属于结构型模式
  3. 主要分为三类:类适配器模式对象适配器模式接口适配器模式

工作原理

  1. 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼 容
  2. 从用户的角度看不到被适配者,是解耦的
  3. 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
  4. 用户收到反馈结果,感觉只是和目标接口交互,如图
    在这里插入图片描述

类适配器

Adapter类,通过继承 src类,实现 dst 类接口,完成src->dst的适配。

实例
在这里插入图片描述

国家只提供了220V电压,我们的手机需要5V电压,因此需要一个适配器。

适配器继承220V电压(为了拿到电压),然后实现5V的接口(相当于适配器变压标准)。
接着手机充电的时候,只需要遵循5V接口就行了。
然后用户Client左拿适配器(充电器),右拿手机,就可以充电了。

注意事项

  1. Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点, 因为这要求dst必须是接口,有一定局限性;
  2. src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
  3. 由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。

对象适配器

Adapter类,通过持有 src类,实现 dst 类接口, 完成src->dst的适配。

细节

  1. 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承src的 局限性问题,也不再要求dst必须是接口。
  2. 使用成本更低,更灵活。

接口适配器

  1. 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
  2. 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
  3. 适用于一个接口不想使用其所有的方法的情况。

实例
在这里插入图片描述

如上图,接口提供了4个方法,如果我们直接实现接口,将不得不实现全部方法。
但是我们加一个适配器,实现接口中的全部方法(但是都是空方法,没有写具体实现),
然后A继承适配器,就可以有选择的重写自己想要的方法。

Spring源码适配器模式分析

在这里插入图片描述

实现逻辑:前端发起请求,DispatchServlet调用doDispatch()方法,调用controller方法。

为什么应用适配器模式:前端发起的请求有多种,可能需要HttpController进行处理,也可能是SimpleController或者AnnotationController进行处理,但是我们并不知道要用哪个;最简单的方式就是在处理的时候用if-else对请求类别进行甄别,然后调用对应controller;但是,显然这种模式违背了OCP原则。

适配器模式:
处理请求的控制器接口;
分别处理请求的三个具体控制器;
每个具体的控制器都有一个对应的适配器,这些适配器都遵循同一个适配器接口。
dispatchServlet拿到请求之后,通过适配器判断控制器类型,并调用控制器方法。

模拟运行轨迹:
前端发起Http请求==》DispatcherServlet拿到请求,调用doDispatch()
》doDispatch()拿到该请求,交给HandlerAdapter适配器进行类别判断》判断结果为Http请求,此时用HttpHandlerAdapter调用HTTPController中的方法,处理请求。


适配器模式注意事项

  1. 三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。
  2. 类适配器:以类给到,在Adapter里,就是将src当做类,继承
    对象适配器:以对象给到,在Adapter里,将src作为一个对象,持有
    接口适配器:以接口给到,在Adapter里,将src作为一个接口,实现
  3. Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。
  4. 实际开发中,实现起来不拘泥于上述三种经典形式

3.7桥接模式

考虑这么一个问题:有“圆形”“正方形”“三角形”三个形状,然后有“红色”“绿色”“蓝色”三种颜色,现在我们需要“红色正方形”、“绿色正方形”、“蓝色正方形”、“红色三角形”、“绿色三角形”、“蓝色三角形”、“红色圆形”、“绿色圆形”、“蓝色圆形”,需要几个类?
如果是按x色x形分别建类,那么显然需要3*3个类。
这种实现快速简单,但是却难以扩展。比如我们现在多了一个绿色,那么就得再建三个“绿色xx形”,形成类爆炸。

这就是理解桥接模式的方式——属性维度。
将每一维,都单独抽出来,然后通过组合聚合的形式放在一个桥接类中。

在这里插入图片描述
这个“抽象”与“实现”的区别,网上并没有找到结论。
个人觉得,没啥区别,抽象接口直接跟Client交互,并且组合实现类,因此,可以将重要的“属性”作为抽象。
或者说,抽象与实现是主从关系,实现属于抽象,比如说“颜色属于形状,因此形状是抽象,颜色是实现”。

此外,个人觉得这个二维维度可以扩展成多维。

3.8装饰模式

定义
在不改变原有对象的基础之上,将功能附加到对象上。提供了比继承更有弹性的替代方案(扩展原有对象功能)

优点

  • 扩展一个类的功能或者给一个类添加附加职责
  • 给一个对象动态的添加功能,或动态撤销功能。

在这里插入图片描述
类图特点:

  • 被装饰者和装饰器都实现同一个接口(抽象类),该接口中有被装饰者的可装饰属性相关方法

    比如说用调味品装饰咖啡,那么咖啡的描述和价格就是可装饰属性,此时该顶级接口应当带上描述和价格,只有这样,才能在装饰后对这些属性进行动态修改。

3.9组合模式

常见这种形式:

  • 一个用户有多个订单,一个订单有多个订单详情,一个订单详情有多个商品
  • 一个学校有多个学院,一个学院有多个系,一个系有多个班,一个班有多个同学

这种模式,可以抽象为一棵树
学校是一个根节点,同学是叶子节点。

这就是组合模式。

在这里插入图片描述

  • ClientApp是调用方,只跟Component发生关联
  • Component是抽象节点,包含根节点和叶子节点的所有默认行为
  • Composite是非叶子节点
  • Leaf是叶子节点。

注意:

  • Component作为抽象类时,将叶子节点也包含的行为作为抽象方法,让叶子也能实现;将叶子节点不包含的行为(如移除子节点和增加子节点)写一个默认实现,抛出不支持调用的异常,让其他子节点自己实现。
  • 一般情况下,尽管都是非叶子节点,但是也可能实现不一致(比如增加一个学院和增加一个系,实际逻辑不一样),因此不能只写一个非叶子节点,可能存在多个很相似的非叶子节点

其他细节:

  • 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
  • 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系, 客户端不用做出任何改动.
  • 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点 或者叶子从而创建出复杂的树形结构
  • 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
  • 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式

划重点:树形结构适合、能帮助客户端忽略节点和叶子的差异性、如果节点和叶子本身存在较大差异则不适合使用组合模式(比如学校和学生的操作肯定存在较大差异,建议将学生、班级从这个树状结构排除,让系作为叶子节点)

3.10外观模式

考虑:
家庭影院系统

  • 幕布的开关
  • 投影仪的开关,亮度调节,节目选择
  • 躺椅的准备与收起

看电影的步骤:
准备躺椅
打开幕布
打开投影仪,调节亮度,选择节目

结束看电影的步骤:
关闭投影仪
关闭幕布
收起躺椅

如果是用户直接面对躺椅、幕布、投影仪,那他就得一步一步操作(尽管这里看起来不复杂),假设是一个子系统非常多的大系统,那么这些一步一步操作就会非常复杂。

可以这么做:
弄一个Facade类,组合幕布、投影仪、躺椅,用户只需要输入一个电影名,这个影院系统就自动完成所有准备工作。

在这里插入图片描述
外观模式的注意事项和细节

  • 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
  • 外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
  • 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
  • 当系统需要进行分层设计时,可以考虑使用Facade模式
  • 维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口, 让新系统与Facade类交互,提高复用性
  • 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。 要以让系统有层次,利于维护为目的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章